OSDN Git Service

Mov (#518)
authorPaladz <yzhu101@uottawa.ca>
Mon, 23 Mar 2020 03:02:52 +0000 (11:02 +0800)
committerGitHub <noreply@github.com>
Mon, 23 Mar 2020 03:02:52 +0000 (11:02 +0800)
* magnetic program (#402)

* add dex program

* optimise

* adjust code position

* add DecodeP2DCProgram

* optimise code format

* add contract code annotation

* adjust contract

* optimise

* modify contract name

* optimse

* adjust contract

* optimise

* delete redundant code

* optimse

* optimise

* add contract tx test

* add wrong test and ring contract

* optimise

* optimise

* optimise test

* optimise

* optimise

* modify parameter name (#411)

* fix bug (#412)

* fix bug

* fix

* fix name

* fix func name

* fix

* Mov database iterator (#415)

* mov_database_iterator

* opt log

* match engine (#418)

* match engine

* opt code

* update comment

* opt code

* fix validate trade pairs

* remove magic num

* opt canNotMatched func

* refactor

* fix travis (#425)

* modify crossin transaction (#416)

* modify crossin transaction

* add test

* fix review

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* Modify federation sync (#422)

* Set chain Tx Gas 0 (#409)

* Set chain Tx Gas 0

* Fix test file err

* Revert test file modify

* add filter asset

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix a small bug

* delete unused

* mov core (#421)

* mov core

* bug fix

* opt code

* remove validate contract in validate cancel order

* fix validate

* bug fix

* bug fix

* fix ci

* fix mov database (#427)

* mov joint (#428)

* mov joint

* bug fix

* opt code

* fix ci

* format code

* fix mov (#429)

* add status fail check (#431)

* Mov merge (#430)

* Add MOV subprotol framework support

* opz code format

* opz code format

* Del unused code

* Set chain Tx Gas 0 (#409)

* Set chain Tx Gas 0

* Fix test file err

* Revert test file modify

* Modify interface function

* Opz code format

* MOV add startpoint

* mov merge

* fix ci

* opt code

* fix ci

* opt code

* fix dead lock

* opt code

* bug fix

* fix ci

* revert comment

* fix bug

* remove unuse parameter

* fix validate coinbase

* add calc matchedtx gas func

* opt code

* remove useless code

* opt validate

* block height limit for before proposal block

* merge order from pool (#432)

* merge order from pool

* bug fix order table

* bug fix order table

* bug fix

* rename

* fix order table (#434)

* add mov core test

* add calcMatchedTxFee test

* import order

* fix order table test

* fix match

* opt code

* Check mov store (#437)

* over view code of database/mov_store

* delete the duplicate code

* change the batch read number

* same change while go over the codes (#438)

* same change while go over the codes

* roll back mis edit

* add the comment (#439)

* Modify corssout tx action (#441)

* add program for crossout action

* fix

* fix sync status (#442)

* fix mov dead lock (#443)

* fix mov dead lock

* Delete LOCK

* set proposal have timeout (#444)

* set proposal have timeout

* edit for code review

* fix the roll back dead lock (#446)

* submit tx protocol verify (#447)

* fix request amount zero (#448)

* fix (#449)

* timeout level (#450)

* timeout level

* fix ci

* edit for code review

* edit for code review

* Update testnet config (#451)

* update testnet config

* update testnet param

* rollback federation config

* add op mul fraction to solve the mov exchange issue (#452)

* add op mul fraction to solve the mov exchange issue

* minor

* edit for code review

* no water

* fix mov should pay amount (#453)

* fix mov should pay amount

* opt code

* drop the useless tx (#455)

* drop the useless tx

* fix bug

* try to fix network issue (#456)

* try to fix network issue

* try fix bug

* try fix

* init version for edit sign process (#454)

* init version for edit sign process

* eleagent the code

* elegant the code

* try fix bug (#457)

* fix recursive order (#458)

* fix recursive order

* edit logic

* fix the test

* fix mov infinite loop (#461)

* fix mov infinite loop

* validate equals trade pair

* update

* update

* Fix mov issue (#462)

* fix mov infinite loop

* validate equals trade pair

* cycle match

* fix ci

* opt mov validate match tx (#470)

* opt mov validate match tx

* fix validate trade pair

Co-authored-by: Paladz <yzhu101@uottawa.ca>
* fix_open_federation_issue_asset (#478)

* fix_open_federation_issue_asset

* opt code

* complement mov test (#477)

Co-authored-by: Paladz <yzhu101@uottawa.ca>
* fix decimal (#479)

* fix decimal

* fix decimal

* bug fix

* fix decimal

* opt mov performance (#480)

* opt mov performance

* opt mov performance

* fix ci

* bug fix

* bug fix

* opt code

* remove test

* remove unknow file

* opt code

* opt code

* add comment

* opt code

* bug fix

* opt code

* use int for worker num

* fix match collector (#481)

* fix match collector

* add comment

* opt_mathch_collector (#483)

* Opt mathch collector (#482)

* opt_mathch_collector

* opt_mathch_collector

* final code review (#484)

* final code review

* edit for code review

Co-authored-by: Colt-Z <453256728@qq.com>
* Validate matched tx sequence test (#486)

* validate_matched_tx_sequence_test

* validate_matched_tx_sequence_test

* Validate matched tx sequence test (#487)

* validate_matched_tx_sequence_test

* validate_matched_tx_sequence_test

* Supplementary unit tests

* rename

* fix add order bug (#489)

* fix add order bug

* edit for code review

Co-authored-by: Colt-Z <453256728@qq.com>
* filter txpool by asset whitelis  (#488)

* add AssetWhitelist to FederationConfig

* use []*bc.AssetID for AssetWhitelist

* add (f *FederationAssetFilter) IsDust()

* fix https://github.com/Bytom/vapor/pull/488#discussion_r379226522

* fix https://github.com/Bytom/vapor/pull/488#discussion_r379226868

* fix https://github.com/Bytom/vapor/pull/488/files#r379226149

* fix https://github.com/Bytom/vapor/pull/488/files#r379225830

* convert assetID to lower-case

* golint

* add deleteBlock and then fix some tests (#491)

* add deleteBlock and then fix some tests

* add new test

* delete line

* fix one error

* change test case

* add s to word

* s

* update test cases

* update package and delete one same case

Co-authored-by: Paladz <yzhu101@uottawa.ca>
* fix init mov state (#492)

* fix init mov state

* remove Junk files

* Rollback pr2, rollback 的时候,更新 wallet 的状态, 改内核的下一个版本发 (#493)

* add rollback_func

* add wallet rollback

* update wallet finished

* add new wallet rollback flag

* fix one flag

* new design of new wallet

* update one comment

* rollback status from one wallet best hash start

* change two func name

* fix one test

* change rollback func return info

* remove ugly code

* fix ugly code

* remove ugly code

* re design reorganizeChain (#495)

* Rollback pr3 (#496)

* rollback chain

* fix test ci

* delete irrblock judge

* update rollblock

* delete test case

* fix bad delete

* change rollback

* delete one test

* add tip

* change rollback

* fix one err

* remove fmt

* remove one test

* rename

* rename variable

Co-authored-by: Poseidon <shenao.78@163.com>
* fix_add_order_of_order_book (#497)

* fix_add_order_of_order_book

* fix order book

* opt code

Co-authored-by: Paladz <yzhu101@uottawa.ca>
* fix validate tx (#499)

* add_comment_for_bbft (#502)

* fixed reward address (#504)

* match_fee_strategy (#506)

* match_fee_strategy

* rename variable

* opt code

* rename

* adjust order

* add test case

* validate reward address (#505)

* validate reward address

* fix ci

* opt code

* opt code

* opt code

* Match fee strategy (#507)

* match_fee_strategy

* rename variable

* opt code

* rename

* adjust order

* add test case

* bug fix

* bug fix

* add fee for multiple asset tesetcase

* add comment

* charge 1% fee (#509)

* charge 1% fee

* fix weird space

* edit for code review

Co-authored-by: paladz <453256728@qq.com>
* fix_mov_contract_test (#510)

* one_thousandth_fee (#508)

* one_thousandth_fee

* opt code

* update fee

* bug fix

* fix all testcase

* opt mov (#511)

* ban status fail for flash swap (#512)

* ban status fail for flash swap

* set up init check point

Co-authored-by: paladz <453256728@qq.com>
* no tricks (#513)

Co-authored-by: paladz <453256728@qq.com>
* for publish (#515)

* for publish

* fix test

Co-authored-by: paladz <453256728@qq.com>
* small fix (#517)

* small fix

* prevent timeout

* edit for golint

Co-authored-by: paladz <453256728@qq.com>
* Opt is matched (#516)

* opt is matched

* opt is matched

Co-authored-by: Paladz <yzhu101@uottawa.ca>
* rollback (#503)

* rollback

* add

* rollback test

* rollback xiugai

* delete one line

* s

* rename chainData

* reformat it

* opt code

Co-authored-by: shenao78 <shenao.78@163.com>
Co-authored-by: Paladz <yzhu101@uottawa.ca>
* last review (#519)

* last review

* edit for code review

* fix test case

Co-authored-by: paladz <453256728@qq.com>
Co-authored-by: oysheng <33340252+oysheng@users.noreply.github.com>
Co-authored-by: wz <mars@bytom.io>
Co-authored-by: Poseidon <shenao.78@163.com>
Co-authored-by: Chengcheng Zhang <943420582@qq.com>
Co-authored-by: Colt-Z <453256728@qq.com>
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
Co-authored-by: ipqhjjybj <250657661@qq.com>
93 files changed:
.travis.yml
application/mov/common/type.go
application/mov/common/type_test.go [new file with mode: 0644]
application/mov/common/util.go [new file with mode: 0644]
application/mov/contract/contract.go [new file with mode: 0644]
application/mov/database/mov_iterator.go [new file with mode: 0644]
application/mov/database/mov_iterator_test.go [new file with mode: 0644]
application/mov/database/mov_store.go
application/mov/database/mov_store_test.go
application/mov/match/engine.go [new file with mode: 0644]
application/mov/match/engine_test.go [new file with mode: 0644]
application/mov/match/fee_strategy.go [new file with mode: 0644]
application/mov/match/order_book.go [new file with mode: 0644]
application/mov/match/order_book_test.go [new file with mode: 0644]
application/mov/match_collector.go [new file with mode: 0644]
application/mov/mock/mock.go [new file with mode: 0644]
application/mov/mock/mock_mov_store.go [new file with mode: 0644]
application/mov/mov_core.go [new file with mode: 0644]
application/mov/mov_core_test.go [new file with mode: 0644]
blockchain/txbuilder/actions.go
blockchain/txbuilder/signing_instruction.go
cmd/vapord/commands/rollback_node.go [new file with mode: 0644]
cmd/vapord/commands/root.go
cmd/vapord/commands/run_node.go
common/bytes.go
common/crossin_asset.go [new file with mode: 0644]
common/sort.go [deleted file]
config/config.go
config/federation_test.go
config/toml.go
consensus/general.go
consensus/segwit/segwit.go
database/account_store.go
database/store.go
database/store_test.go
database/utxo_view.go
database/wallet_store.go
docs/federation/sql_dump/federation_shema.sql
math/algorithm.go [new file with mode: 0644]
netsync/chainmgr/block_keeper.go
netsync/chainmgr/block_keeper_test.go
netsync/chainmgr/block_process.go
netsync/chainmgr/fast_sync.go
netsync/chainmgr/handle.go
netsync/chainmgr/msg_fetcher.go
netsync/chainmgr/storage.go
netsync/chainmgr/tx_keeper_test.go
node/node.go
proposal/blockproposer/blockproposer.go
proposal/proposal.go
proposal/proposal_test.go
protocol/asset_filter.go [new file with mode: 0644]
protocol/bbft.go
protocol/bc/bc.pb.go
protocol/bc/bc.proto
protocol/bc/crosschain_input.go
protocol/bc/entry_test.go
protocol/bc/types/block.go
protocol/bc/types/block_witness.go
protocol/bc/types/crosschain_output.go
protocol/bc/types/intrachain_output.go
protocol/bc/types/map.go
protocol/bc/types/vote_output.go
protocol/block.go
protocol/block_test.go
protocol/consensus_node_manager_test.go
protocol/protocol.go
protocol/store.go
protocol/tx.go
protocol/txpool.go
protocol/txpool_test.go
protocol/validation/tx.go
protocol/validation/tx_test.go
protocol/validation/vmcontext.go
protocol/vm/numeric.go
protocol/vm/numeric_test.go
protocol/vm/ops.go
protocol/vm/vmutil/script.go
test/accounts_test.go
test/bench_blockchain_test.go
test/mock/mempool.go
test/performance/mining_test.go
test/rollback_test.go [new file with mode: 0644]
test/util.go
test/wallet_test.go
test/wallet_test_util.go
toolbar/federation/api/handler.go
toolbar/federation/database/orm/asset.go
toolbar/federation/synchron/mainchain_keeper.go
toolbar/federation/synchron/sidechain_keeper.go
toolbar/precognitive/config/config.go
version/version.go
wallet/wallet.go

index f32dc51..436a5c6 100644 (file)
@@ -11,7 +11,7 @@ branches:
     only:
         - master
         - dev
-        - v0.1
+        - mov
 
 script:
     - make ci
index d56dac0..11d89bb 100644 (file)
@@ -1,7 +1,17 @@
 package common
 
-import "github.com/bytom/vapor/protocol/bc"
+import (
+       "encoding/hex"
+       "fmt"
+       "math/big"
 
+       "github.com/bytom/vapor/consensus/segwit"
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+)
+
+// MovUtxo store the utxo information for mov order
 type MovUtxo struct {
        SourceID       *bc.Hash
        SourcePos      uint64
@@ -9,19 +19,150 @@ type MovUtxo struct {
        ControlProgram []byte
 }
 
+// Order store all the order information
 type Order struct {
-       FromAssetID *bc.AssetID
-       ToAssetID   *bc.AssetID
-       Utxo        *MovUtxo
-       Rate        float64
+       FromAssetID      *bc.AssetID
+       ToAssetID        *bc.AssetID
+       Utxo             *MovUtxo
+       RatioNumerator   int64
+       RatioDenominator int64
+}
+
+// Rate return the exchange represented by float64
+func (o *Order) Rate() float64 {
+       if o.RatioDenominator == 0 {
+               return 0
+       }
+       rate := big.NewRat(o.RatioNumerator, o.RatioDenominator)
+       result, _ := rate.Float64()
+       return result
+}
+
+// cmpRate compares rate of x and y and returns -1 if x <  y, 0 if x == y, +1 if x >  y
+func (o *Order) cmpRate(other *Order) int {
+       rate := big.NewRat(o.RatioNumerator, o.RatioDenominator)
+       otherRate := big.NewRat(other.RatioNumerator, other.RatioDenominator)
+       return rate.Cmp(otherRate)
+}
+
+// Cmp first compare the rate, if rate is equals, then compare the utxo hash
+func (o *Order) Cmp(other *Order) int {
+       cmp := o.cmpRate(other)
+       if cmp == 0 {
+               if hex.EncodeToString(o.UTXOHash().Bytes()) < hex.EncodeToString(other.UTXOHash().Bytes()) {
+                       return -1
+               }
+               return 1
+       }
+       return cmp
+}
+
+// OrderSlice is define for order's sort
+type OrderSlice []*Order
+
+func (o OrderSlice) Len() int      { return len(o) }
+func (o OrderSlice) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
+func (o OrderSlice) Less(i, j int) bool {
+       return o[i].Cmp(o[j]) < 0
+}
+
+// NewOrderFromOutput convert txinput to order
+func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) {
+       outputID := tx.OutputID(outputIndex)
+       output, err := tx.IntraChainOutput(*outputID)
+       if err != nil {
+               return nil, err
+       }
+
+       contractArgs, err := segwit.DecodeP2WMCProgram(output.ControlProgram.Code)
+       if err != nil {
+               return nil, err
+       }
+
+       assetAmount := output.Source.Value
+       return &Order{
+               FromAssetID:      assetAmount.AssetId,
+               ToAssetID:        &contractArgs.RequestedAsset,
+               RatioNumerator:   contractArgs.RatioNumerator,
+               RatioDenominator: contractArgs.RatioDenominator,
+               Utxo: &MovUtxo{
+                       SourceID:       output.Source.Ref,
+                       Amount:         assetAmount.Amount,
+                       SourcePos:      uint64(outputIndex),
+                       ControlProgram: output.ControlProgram.Code,
+               },
+       }, nil
+}
+
+// NewOrderFromInput convert txoutput to order
+func NewOrderFromInput(tx *types.Tx, inputIndex int) (*Order, error) {
+       input, ok := tx.Inputs[inputIndex].TypedInput.(*types.SpendInput)
+       if !ok {
+               return nil, errors.New("input is not type of spend input")
+       }
+
+       contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram)
+       if err != nil {
+               return nil, err
+       }
+
+       return &Order{
+               FromAssetID:      input.AssetId,
+               ToAssetID:        &contractArgs.RequestedAsset,
+               RatioNumerator:   contractArgs.RatioNumerator,
+               RatioDenominator: contractArgs.RatioDenominator,
+               Utxo: &MovUtxo{
+                       SourceID:       &input.SourceID,
+                       Amount:         input.Amount,
+                       SourcePos:      input.SourcePosition,
+                       ControlProgram: input.ControlProgram,
+               },
+       }, nil
+}
+
+// Key return the unique key for representing this order
+func (o *Order) Key() string {
+       return fmt.Sprintf("%s:%d", o.Utxo.SourceID, o.Utxo.SourcePos)
 }
 
+// TradePair return the trade pair info
+func (o *Order) TradePair() *TradePair {
+       return &TradePair{FromAssetID: o.FromAssetID, ToAssetID: o.ToAssetID}
+}
+
+// UTXOHash calculate the utxo hash of this order
+func (o *Order) UTXOHash() *bc.Hash {
+       prog := &bc.Program{VmVersion: 1, Code: o.Utxo.ControlProgram}
+       src := &bc.ValueSource{
+               Ref:      o.Utxo.SourceID,
+               Value:    &bc.AssetAmount{AssetId: o.FromAssetID, Amount: o.Utxo.Amount},
+               Position: o.Utxo.SourcePos,
+       }
+       hash := bc.EntryID(bc.NewIntraChainOutput(src, prog, 0))
+       return &hash
+}
+
+// TradePair is the object for record trade pair info
 type TradePair struct {
        FromAssetID *bc.AssetID
        ToAssetID   *bc.AssetID
        Count       int
 }
 
+// Key return the unique key for representing this trade pair
+func (t *TradePair) Key() string {
+       return fmt.Sprintf("%s:%s", t.FromAssetID, t.ToAssetID)
+}
+
+// Reverse return the reverse trade pair object
+func (t *TradePair) Reverse() *TradePair {
+       return &TradePair{
+               FromAssetID: t.ToAssetID,
+               ToAssetID:   t.FromAssetID,
+       }
+}
+
+// MovDatabaseState is object to record DB image status
 type MovDatabaseState struct {
        Height uint64
        Hash   *bc.Hash
diff --git a/application/mov/common/type_test.go b/application/mov/common/type_test.go
new file mode 100644 (file)
index 0000000..8fe9e81
--- /dev/null
@@ -0,0 +1,27 @@
+package common
+
+import (
+       "testing"
+
+       "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/testutil"
+)
+
+func TestCalcUTXOHash(t *testing.T) {
+       wantHash := "d94acbac0304e054569b0a2c2ab546be293552eb83d2d84af7234a013986a906"
+       controlProgram := testutil.MustDecodeHexString("0014d6f0330717170c838e6ac4c643de61e4c035e9b7")
+       sourceID := testutil.MustDecodeHash("3cada915465af2f08c93911bce7a100498fddb5738e5400269c4d5c2b2f5b261")
+       order := Order{
+               FromAssetID: consensus.BTMAssetID,
+               Utxo: &MovUtxo{
+                       SourceID:       &sourceID,
+                       SourcePos:      1,
+                       Amount:         399551000,
+                       ControlProgram: controlProgram,
+               },
+       }
+
+       if hash := order.UTXOHash(); hash.String() != wantHash {
+               t.Fatal("The function is incorrect")
+       }
+}
diff --git a/application/mov/common/util.go b/application/mov/common/util.go
new file mode 100644 (file)
index 0000000..0077eab
--- /dev/null
@@ -0,0 +1,30 @@
+package common
+
+import (
+       "github.com/bytom/vapor/application/mov/contract"
+       "github.com/bytom/vapor/consensus/segwit"
+       "github.com/bytom/vapor/protocol/bc/types"
+)
+
+// IsMatchedTx check if this transaction has trade mov order input
+func IsMatchedTx(tx *types.Tx) bool {
+       if len(tx.Inputs) < 2 {
+               return false
+       }
+       for _, input := range tx.Inputs {
+               if input.InputType() == types.SpendInputType && segwit.IsP2WMCScript(input.ControlProgram()) && contract.IsTradeClauseSelector(input) {
+                       return true
+               }
+       }
+       return false
+}
+
+// IsCancelOrderTx check if this transaction has cancel mov order input
+func IsCancelOrderTx(tx *types.Tx) bool {
+       for _, input := range tx.Inputs {
+               if input.InputType() == types.SpendInputType && segwit.IsP2WMCScript(input.ControlProgram()) && contract.IsCancelClauseSelector(input) {
+                       return true
+               }
+       }
+       return false
+}
diff --git a/application/mov/contract/contract.go b/application/mov/contract/contract.go
new file mode 100644 (file)
index 0000000..75cd00d
--- /dev/null
@@ -0,0 +1,41 @@
+package contract
+
+import (
+       "encoding/hex"
+
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/protocol/vm"
+)
+
+const (
+       sizeOfCancelClauseArgs       = 3
+       sizeOfPartialTradeClauseArgs = 3
+       sizeOfFullTradeClauseArgs    = 2
+)
+
+// smart contract clause select for differnet unlock method
+const (
+       PartialTradeClauseSelector int64 = iota
+       FullTradeClauseSelector
+       CancelClauseSelector
+)
+
+// IsCancelClauseSelector check if input select cancel clause
+func IsCancelClauseSelector(input *types.TxInput) bool {
+       return len(input.Arguments()) == sizeOfCancelClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(CancelClauseSelector))
+}
+
+// IsTradeClauseSelector check if input select is partial trade clause or full trade clause
+func IsTradeClauseSelector(input *types.TxInput) bool {
+       return IsPartialTradeClauseSelector(input) || IsFullTradeClauseSelector(input)
+}
+
+// IsPartialTradeClauseSelector check if input select partial trade clause
+func IsPartialTradeClauseSelector(input *types.TxInput) bool {
+       return len(input.Arguments()) == sizeOfPartialTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(PartialTradeClauseSelector))
+}
+
+// IsFullTradeClauseSelector check if input select full trade clause
+func IsFullTradeClauseSelector(input *types.TxInput) bool {
+       return len(input.Arguments()) == sizeOfFullTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(FullTradeClauseSelector))
+}
diff --git a/application/mov/database/mov_iterator.go b/application/mov/database/mov_iterator.go
new file mode 100644 (file)
index 0000000..b06debf
--- /dev/null
@@ -0,0 +1,104 @@
+package database
+
+import (
+       log "github.com/sirupsen/logrus"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/protocol/bc"
+)
+
+// TradePairIterator wrap read trade pair from DB action
+type TradePairIterator struct {
+       movStore       MovStore
+       tradePairs     []*common.TradePair
+       tradePairIndex int
+}
+
+// NewTradePairIterator create the new TradePairIterator object
+func NewTradePairIterator(movStore MovStore) *TradePairIterator {
+       return &TradePairIterator{movStore: movStore}
+}
+
+// HasNext check if there are more trade pairs in memory or DB
+func (t *TradePairIterator) HasNext() bool {
+       tradePairSize := len(t.tradePairs)
+       if t.tradePairIndex < tradePairSize {
+               return true
+       }
+
+       var fromAssetID, toAssetID *bc.AssetID
+       if len(t.tradePairs) > 0 {
+               lastTradePair := t.tradePairs[tradePairSize-1]
+               fromAssetID, toAssetID = lastTradePair.FromAssetID, lastTradePair.ToAssetID
+       }
+
+       tradePairs, err := t.movStore.ListTradePairsWithStart(fromAssetID, toAssetID)
+       if err != nil {
+               // If the error is returned, it's an error of program itself,
+               // and cannot be recovered, so panic directly.
+               log.WithField("err", err).Fatal("fail to list trade pairs")
+       }
+
+       if len(tradePairs) == 0 {
+               return false
+       }
+
+       t.tradePairs = tradePairs
+       t.tradePairIndex = 0
+       return true
+}
+
+// Next return the next available trade pair in memory or DB
+func (t *TradePairIterator) Next() *common.TradePair {
+       if !t.HasNext() {
+               return nil
+       }
+
+       tradePair := t.tradePairs[t.tradePairIndex]
+       t.tradePairIndex++
+       return tradePair
+}
+
+// OrderIterator wrap read order from DB action
+type OrderIterator struct {
+       movStore  MovStore
+       lastOrder *common.Order
+       orders    []*common.Order
+}
+
+// NewOrderIterator create the new OrderIterator object
+func NewOrderIterator(movStore MovStore, tradePair *common.TradePair) *OrderIterator {
+       return &OrderIterator{
+               movStore:  movStore,
+               lastOrder: &common.Order{FromAssetID: tradePair.FromAssetID, ToAssetID: tradePair.ToAssetID},
+       }
+}
+
+// HasNext check if there are more orders in memory or DB
+func (o *OrderIterator) HasNext() bool {
+       if len(o.orders) == 0 {
+               orders, err := o.movStore.ListOrders(o.lastOrder)
+               if err != nil {
+                       log.WithField("err", err).Fatal("fail to list orders")
+               }
+
+               if len(orders) == 0 {
+                       return false
+               }
+
+               o.orders = orders
+               o.lastOrder = o.orders[len(o.orders)-1]
+       }
+       return true
+}
+
+// NextBatch return the next batch of orders in memory or DB
+func (o *OrderIterator) NextBatch() []*common.Order {
+       if !o.HasNext() {
+               return nil
+       }
+
+       orders := o.orders
+       o.orders = nil
+       return orders
+}
diff --git a/application/mov/database/mov_iterator_test.go b/application/mov/database/mov_iterator_test.go
new file mode 100644 (file)
index 0000000..5df84e1
--- /dev/null
@@ -0,0 +1,166 @@
+package database
+
+import (
+       "testing"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/mock"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/testutil"
+)
+
+var (
+       asset1 = bc.NewAssetID([32]byte{1})
+       asset2 = bc.NewAssetID([32]byte{2})
+       asset3 = bc.NewAssetID([32]byte{3})
+       asset4 = bc.NewAssetID([32]byte{4})
+
+       order1 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 1, RatioDenominator: 10}
+       order2 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 2, RatioDenominator: 10}
+       order3 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 3, RatioDenominator: 10}
+       order4 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 4, RatioDenominator: 10}
+       order5 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 5, RatioDenominator: 10}
+)
+
+func TestTradePairIterator(t *testing.T) {
+       cases := []struct {
+               desc            string
+               storeTradePairs []*common.TradePair
+               wantTradePairs  []*common.TradePair
+       }{
+               {
+                       desc: "normal case",
+                       storeTradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset2,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset3,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset4,
+                               },
+                       },
+                       wantTradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset2,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset3,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset4,
+                               },
+                       },
+               },
+               {
+                       desc: "num of trade pairs more than one return",
+                       storeTradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset2,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset3,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset4,
+                               },
+                               {
+                                       FromAssetID: &asset2,
+                                       ToAssetID:   &asset1,
+                               },
+                       },
+                       wantTradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset2,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset3,
+                               },
+                               {
+                                       FromAssetID: &asset1,
+                                       ToAssetID:   &asset4,
+                               },
+                               {
+                                       FromAssetID: &asset2,
+                                       ToAssetID:   &asset1,
+                               },
+                       },
+               },
+               {
+                       desc:            "store is empty",
+                       storeTradePairs: []*common.TradePair{},
+                       wantTradePairs:  []*common.TradePair{},
+               },
+       }
+
+       for i, c := range cases {
+               store := mock.NewMovStore(c.storeTradePairs, nil)
+               var gotTradePairs []*common.TradePair
+               iterator := NewTradePairIterator(store)
+               for iterator.HasNext() {
+                       gotTradePairs = append(gotTradePairs, iterator.Next())
+               }
+               if !testutil.DeepEqual(c.wantTradePairs, gotTradePairs) {
+                       t.Errorf("#%d(%s):got trade pairs is not equals want trade pairs", i, c.desc)
+               }
+       }
+}
+
+func TestOrderIterator(t *testing.T) {
+       cases := []struct {
+               desc        string
+               tradePair   *common.TradePair
+               storeOrders []*common.Order
+               wantOrders  []*common.Order
+       }{
+               {
+                       desc:        "normal case",
+                       tradePair:   &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+                       storeOrders: []*common.Order{order1, order2, order3},
+                       wantOrders:  []*common.Order{order1, order2, order3},
+               },
+               {
+                       desc:        "num of orders more than one return",
+                       tradePair:   &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+                       storeOrders: []*common.Order{order1, order2, order3, order4, order5},
+                       wantOrders:  []*common.Order{order1, order2, order3, order4, order5},
+               },
+               {
+                       desc:        "only one order",
+                       tradePair:   &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+                       storeOrders: []*common.Order{order1},
+                       wantOrders:  []*common.Order{order1},
+               },
+               {
+                       desc:        "store is empty",
+                       tradePair:   &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+                       storeOrders: []*common.Order{},
+                       wantOrders:  []*common.Order{},
+               },
+       }
+
+       for i, c := range cases {
+               store := mock.NewMovStore(nil, c.storeOrders)
+
+               var gotOrders []*common.Order
+               iterator := NewOrderIterator(store, c.tradePair)
+               for iterator.HasNext() {
+                       gotOrders = append(gotOrders, iterator.NextBatch()...)
+               }
+               if !testutil.DeepEqual(c.wantOrders, gotOrders) {
+                       t.Errorf("#%d(%s):got orders it not equals want orders", i, c.desc)
+               }
+       }
+}
index 252886a..20d3101 100644 (file)
@@ -12,15 +12,27 @@ import (
        "github.com/bytom/vapor/protocol/bc/types"
 )
 
+// MovStore is the interface for mov's persistent storage
+type MovStore interface {
+       GetMovDatabaseState() (*common.MovDatabaseState, error)
+       InitDBState(height uint64, hash *bc.Hash) error
+       ListOrders(orderAfter *common.Order) ([]*common.Order, error)
+       ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error)
+       ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error
+}
+
 const (
-       order byte = iota
+       order byte = iota + 1
        tradePair
        matchStatus
 
-       tradePairsNum = 1024
-       ordersNum     = 10240
-       assetIDLen    = 32
-       rateByteLen   = 8
+       fromAssetIDPos = 0
+       toAssetIDPos   = 1
+       assetIDLen     = 32
+       rateByteLen    = 8
+
+       tradePairsNum = 32
+       ordersNum     = 128
 )
 
 var (
@@ -30,6 +42,12 @@ var (
        bestMatchStore   = append(movStore, matchStatus)
 )
 
+type orderData struct {
+       Utxo             *common.MovUtxo
+       RatioNumerator   int64
+       RatioDenominator int64
+}
+
 func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
        buf := make([]byte, 8)
        binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
@@ -44,27 +62,16 @@ func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
        return append(key, toAssetID.Bytes()...)
 }
 
-func calcUTXOHash(order *common.Order) *bc.Hash {
-       prog := &bc.Program{VmVersion: 1, Code: order.Utxo.ControlProgram}
-       src := &bc.ValueSource{
-               Ref:      order.Utxo.SourceID,
-               Value:    &bc.AssetAmount{AssetId: order.FromAssetID, Amount: order.Utxo.Amount},
-               Position: order.Utxo.SourcePos,
-       }
-       hash := bc.EntryID(bc.NewIntraChainOutput(src, prog, 0))
-       return &hash
-}
-
-func getAssetIDFromTradePairKey(key []byte, prefix []byte, posIndex int) *bc.AssetID {
+func getAssetIDFromTradePairKey(key []byte, posIndex int) *bc.AssetID {
        b := [32]byte{}
-       pos := len(prefix) + assetIDLen*posIndex
+       pos := len(tradePairsPrefix) + assetIDLen*posIndex
        copy(b[:], key[pos:pos+assetIDLen])
        assetID := bc.NewAssetID(b)
        return &assetID
 }
 
-func getRateFromOrderKey(key []byte, prefix []byte) float64 {
-       ratePos := len(prefix) + assetIDLen*2
+func getRateFromOrderKey(key []byte) float64 {
+       ratePos := len(ordersPrefix) + assetIDLen*2
        return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
 }
 
@@ -72,24 +79,40 @@ type tradePairData struct {
        Count int
 }
 
-type MovStore struct {
+// LevelDBMovStore is the LevelDB implementation for MovStore
+type LevelDBMovStore struct {
        db dbm.DB
 }
 
-func NewMovStore(db dbm.DB, height uint64, hash *bc.Hash) (*MovStore, error) {
-       if value := db.Get(bestMatchStore); value == nil {
-               state := &common.MovDatabaseState{Height: height, Hash: hash}
-               value, err := json.Marshal(state)
-               if err != nil {
-                       return nil, err
-               }
+// NewLevelDBMovStore create a new LevelDBMovStore object
+func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
+       return &LevelDBMovStore{db: db}
+}
+
+// GetMovDatabaseState return the current DB's image status
+func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
+       if value := m.db.Get(bestMatchStore); value != nil {
+               state := &common.MovDatabaseState{}
+               return state, json.Unmarshal(value, state)
+       }
+
+       return nil, errors.New("don't find state of mov-database")
+}
 
-               db.Set(bestMatchStore, value)
+// InitDBState set the DB's image status
+func (m *LevelDBMovStore) InitDBState(height uint64, hash *bc.Hash) error {
+       state := &common.MovDatabaseState{Height: height, Hash: hash}
+       value, err := json.Marshal(state)
+       if err != nil {
+               return err
        }
-       return &MovStore{db: db}, nil
+
+       m.db.Set(bestMatchStore, value)
+       return nil
 }
 
-func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
+// ListOrders return n orders after the input order
+func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
        if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
                return nil, errors.New("assetID is nil")
        }
@@ -98,8 +121,8 @@ func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error)
        orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
 
        var startKey []byte
-       if orderAfter.Rate > 0 {
-               startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, calcUTXOHash(orderAfter), orderAfter.Rate)
+       if orderAfter.Rate() > 0 {
+               startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate())
        }
 
        itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
@@ -107,41 +130,71 @@ func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error)
 
        var orders []*common.Order
        for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
-               movUtxo := &common.MovUtxo{}
-               if err := json.Unmarshal(itr.Value(), movUtxo); err != nil {
+               orderData := &orderData{}
+               if err := json.Unmarshal(itr.Value(), orderData); err != nil {
                        return nil, err
                }
 
-               order := &common.Order{
-                       FromAssetID: orderAfter.FromAssetID,
-                       ToAssetID:   orderAfter.ToAssetID,
-                       Rate:        getRateFromOrderKey(itr.Key(), ordersPrefix),
-                       Utxo:        movUtxo,
-               }
-               orders = append(orders, order)
+               orders = append(orders, &common.Order{
+                       FromAssetID:      orderAfter.FromAssetID,
+                       ToAssetID:        orderAfter.ToAssetID,
+                       Utxo:             orderData.Utxo,
+                       RatioNumerator:   orderData.RatioNumerator,
+                       RatioDenominator: orderData.RatioDenominator,
+               })
        }
        return orders, nil
 }
 
-func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common.Order, blockHeader *types.BlockHeader) error {
+// ListTradePairsWithStart return n trade pairs after the input trade pair
+func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
+       var startKey []byte
+       if fromAssetIDAfter != nil && toAssetIDAfter != nil {
+               startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
+       }
+
+       itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
+       defer itr.Release()
+
+       var tradePairs []*common.TradePair
+       for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
+               key := itr.Key()
+               fromAssetID := getAssetIDFromTradePairKey(key, fromAssetIDPos)
+               toAssetID := getAssetIDFromTradePairKey(key, toAssetIDPos)
+
+               tradePairData := &tradePairData{}
+               if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
+                       return nil, err
+               }
+
+               tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
+       }
+       return tradePairs, nil
+}
+
+// ProcessOrders update the DB's image by add new orders, delete the used order
+func (m *LevelDBMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
        if err := m.checkMovDatabaseState(blockHeader); err != nil {
                return err
        }
 
        batch := m.db.NewBatch()
-       tradePairsCnt := make(map[common.TradePair]int)
+       tradePairsCnt := make(map[string]*common.TradePair)
        if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
                return err
        }
 
-       m.deleteOrders(batch, delOreders, tradePairsCnt)
-
+       m.deleteOrders(batch, delOrders, tradePairsCnt)
        if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
                return err
        }
 
-       hash := blockHeader.Hash()
-       if err := m.saveMovDatabaseState(batch, &common.MovDatabaseState{Height: blockHeader.Height, Hash: &hash}); err != nil {
+       state, err := m.calcNextDatabaseState(blockHeader)
+       if err != nil {
+               return err
+       }
+
+       if err := m.saveMovDatabaseState(batch, state); err != nil {
                return err
        }
 
@@ -149,85 +202,103 @@ func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common
        return nil
 }
 
-func (m *MovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) error {
+func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) error {
        for _, order := range orders {
-               data, err := json.Marshal(order.Utxo)
+               orderData := &orderData{
+                       Utxo:             order.Utxo,
+                       RatioNumerator:   order.RatioNumerator,
+                       RatioDenominator: order.RatioDenominator,
+               }
+               data, err := json.Marshal(orderData)
                if err != nil {
                        return err
                }
 
-               key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
+               key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
                batch.Set(key, data)
 
-               tradePair := common.TradePair{
+               tradePair := &common.TradePair{
                        FromAssetID: order.FromAssetID,
                        ToAssetID:   order.ToAssetID,
                }
-               tradePairsCnt[tradePair] += 1
+               if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
+                       tradePairsCnt[tradePair.Key()] = tradePair
+               }
+               tradePairsCnt[tradePair.Key()].Count++
        }
        return nil
 }
 
-func (m *MovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) {
-       for _, order := range orders {
-               key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
-               batch.Delete(key)
+func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
+       hash := blockHeader.Hash()
+       height := blockHeader.Height
 
-               tradePair := common.TradePair{
-                       FromAssetID: order.FromAssetID,
-                       ToAssetID:   order.ToAssetID,
-               }
-               tradePairsCnt[tradePair] -= 1
+       state, err := m.GetMovDatabaseState()
+       if err != nil {
+               return nil, err
        }
-}
 
-func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
-       if value := m.db.Get(bestMatchStore); value != nil {
-               state := &common.MovDatabaseState{}
-               return state, json.Unmarshal(value, state)
+       if *state.Hash == hash {
+               hash = blockHeader.PreviousBlockHash
+               height = blockHeader.Height - 1
        }
 
-       return nil, errors.New("don't find state of mov-database")
+       return &common.MovDatabaseState{Height: height, Hash: &hash}, nil
 }
 
-func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
-       var startKey []byte
-       if fromAssetIDAfter != nil && toAssetIDAfter != nil {
-               startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
+func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
+       state, err := m.GetMovDatabaseState()
+       if err != nil {
+               return err
        }
 
-       itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
-       defer itr.Release()
+       if (*state.Hash == header.PreviousBlockHash && (state.Height+1) == header.Height) || *state.Hash == header.Hash() {
+               return nil
+       }
 
-       var tradePairs []*common.TradePair
-       for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
-               key := itr.Key()
-               fromAssetID := getAssetIDFromTradePairKey(key, tradePairsPrefix, 0)
-               toAssetID := getAssetIDFromTradePairKey(key, tradePairsPrefix, 1)
+       return errors.New("the status of the block is inconsistent with that of mov-database")
+}
 
-               tradePairData := &tradePairData{}
-               if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
-                       return nil, err
+func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) {
+       for _, order := range orders {
+               key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
+               batch.Delete(key)
+
+               tradePair := &common.TradePair{
+                       FromAssetID: order.FromAssetID,
+                       ToAssetID:   order.ToAssetID,
+               }
+               if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
+                       tradePairsCnt[tradePair.Key()] = tradePair
                }
+               tradePairsCnt[tradePair.Key()].Count--
+       }
+}
 
-               tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
+func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
+       value, err := json.Marshal(state)
+       if err != nil {
+               return err
        }
-       return tradePairs, nil
+
+       batch.Set(bestMatchStore, value)
+       return nil
 }
 
-func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.TradePair]int) error {
-       for k, v := range tradePairs {
-               key := calcTradePairKey(k.FromAssetID, k.ToAssetID)
+func (m *LevelDBMovStore) updateTradePairs(batch dbm.Batch, tradePairs map[string]*common.TradePair) error {
+       for _, v := range tradePairs {
+               key := calcTradePairKey(v.FromAssetID, v.ToAssetID)
                tradePairData := &tradePairData{}
                if value := m.db.Get(key); value != nil {
                        if err := json.Unmarshal(value, tradePairData); err != nil {
                                return err
                        }
-               } else if v < 0 {
-                       return errors.New("don't find trade pair")
                }
 
-               tradePairData.Count += v
+               if tradePairData.Count += v.Count; tradePairData.Count < 0 {
+                       return errors.New("negative trade count")
+               }
+
                if tradePairData.Count > 0 {
                        value, err := json.Marshal(tradePairData)
                        if err != nil {
@@ -241,27 +312,3 @@ func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.Trade
        }
        return nil
 }
-
-func (m *MovStore) checkMovDatabaseState(header *types.BlockHeader) error {
-       state, err := m.GetMovDatabaseState()
-       if err != nil {
-               return err
-       }
-
-       blockHash := header.Hash()
-       if (state.Hash.String() == header.PreviousBlockHash.String() && (state.Height+1) == header.Height) || state.Hash.String() == blockHash.String() {
-               return nil
-       }
-
-       return errors.New("the status of the block is inconsistent with that of mov-database")
-}
-
-func (m *MovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
-       value, err := json.Marshal(state)
-       if err != nil {
-               return err
-       }
-
-       batch.Set(bestMatchStore, value)
-       return nil
-}
index f212ae0..aeb9c3f 100644 (file)
@@ -10,10 +10,8 @@ import (
        "github.com/stretchr/testify/require"
 
        "github.com/bytom/vapor/application/mov/common"
-       "github.com/bytom/vapor/consensus"
        "github.com/bytom/vapor/database/leveldb"
        dbm "github.com/bytom/vapor/database/leveldb"
-       chainjson "github.com/bytom/vapor/encoding/json"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/bc/types"
        "github.com/bytom/vapor/testutil"
@@ -28,31 +26,203 @@ var (
        assetID6 = &bc.AssetID{V0: 6}
        assetID7 = &bc.AssetID{V0: 7}
        assetID8 = &bc.AssetID{V0: 8}
-)
 
-func TestCalcUTXOHash(t *testing.T) {
-       wantHash := "7cbaf92f950f2a6bededd6cc5ec08c924505f5365b0a8af963e1d52912c99667"
-       controlProgramStr := "0014ab5acbea076f269bfdc8ededbed7d0a13e6e0b19"
-
-       var controlProgram chainjson.HexBytes
-       controlProgram.UnmarshalText([]byte(controlProgramStr))
-
-       sourceID := testutil.MustDecodeHash("ca2faf5fcbf8ee2b43560a32594f608528b12a1fe79cee85252564f886f91060")
-       order := &common.Order{
-               FromAssetID: consensus.BTMAssetID,
-               Utxo: &common.MovUtxo{
-                       SourceID:       &sourceID,
-                       SourcePos:      0,
-                       Amount:         31249300000,
-                       ControlProgram: controlProgram[:],
+       mockOrders = []*common.Order{
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   100090,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 21},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   90,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 22},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   97,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 23},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   98,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 13},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   98,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 24},
+                               Amount:         10,
+                               SourcePos:      1,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   99,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 24},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   96,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 25},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   95,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 26},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   90,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 1},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID1,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   90,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 2},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID3,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   96,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 33},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID4,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   95,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 34},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID4,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   96,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 36},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID5,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   96,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 37},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
+               },
+               &common.Order{
+                       FromAssetID:      assetID6,
+                       ToAssetID:        assetID2,
+                       RatioNumerator:   98,
+                       RatioDenominator: 100000,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       &bc.Hash{V0: 38},
+                               Amount:         1,
+                               SourcePos:      0,
+                               ControlProgram: []byte("aa"),
+                       },
                },
        }
+)
+
+func TestGetAssetIDFromTradePairKey(t *testing.T) {
+       b := calcTradePairKey(assetID1, assetID2)
+       gotA := getAssetIDFromTradePairKey(b, fromAssetIDPos)
+       gotB := getAssetIDFromTradePairKey(b, toAssetIDPos)
 
-       hash := calcUTXOHash(order)
-       if hash.String() != wantHash {
-               t.Fatal("The function is incorrect")
+       if *gotA != *assetID1 {
+               t.Fatalf("got wrong from asset id got %s, want %s", gotA.String(), assetID1.String())
        }
 
+       if *gotB != *assetID2 {
+               t.Fatalf("got wrong to asset id got %s, want %s", gotB.String(), assetID2.String())
+       }
 }
 
 func TestSortOrderKey(t *testing.T) {
@@ -75,15 +245,16 @@ func TestSortOrderKey(t *testing.T) {
        }
 
        cases := []struct {
-               orders []common.Order
+               orders []*common.Order
                want   []expectedData
        }{
                {
-                       orders: []common.Order{
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        1.00090,
+                       orders: []*common.Order{
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   100090,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 21},
                                                Amount:         1,
@@ -91,10 +262,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00090,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   90,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 22},
                                                Amount:         1,
@@ -102,10 +274,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00097,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   97,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 23},
                                                Amount:         1,
@@ -113,10 +286,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00098,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   98,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 13},
                                                Amount:         1,
@@ -124,10 +298,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00098,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   98,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 24},
                                                Amount:         10,
@@ -135,10 +310,47 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00099,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   98,
+                                       RatioDenominator: 100000,
+                                       Utxo: &common.MovUtxo{
+                                               SourceID:       &bc.Hash{V0: 25},
+                                               Amount:         10,
+                                               SourcePos:      1,
+                                               ControlProgram: []byte("aa"),
+                                       },
+                               },
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   98,
+                                       RatioDenominator: 100000,
+                                       Utxo: &common.MovUtxo{
+                                               SourceID:       &bc.Hash{V0: 26},
+                                               Amount:         10,
+                                               SourcePos:      1,
+                                               ControlProgram: []byte("aa"),
+                                       },
+                               },
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   98,
+                                       RatioDenominator: 100000,
+                                       Utxo: &common.MovUtxo{
+                                               SourceID:       &bc.Hash{V0: 27},
+                                               Amount:         10,
+                                               SourcePos:      1,
+                                               ControlProgram: []byte("aa"),
+                                       },
+                               },
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   99,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 24},
                                                Amount:         1,
@@ -146,10 +358,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00096,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   96,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 25},
                                                Amount:         1,
@@ -157,10 +370,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00095,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   95,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 26},
                                                Amount:         1,
@@ -168,10 +382,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00091,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   91,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 26},
                                                Amount:         1,
@@ -179,10 +394,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00092,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   92,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 27},
                                                Amount:         1,
@@ -190,10 +406,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00093,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   93,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 28},
                                                Amount:         1,
@@ -201,10 +418,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00094,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   94,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 29},
                                                Amount:         1,
@@ -212,10 +430,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00077,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   77,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 30},
                                                Amount:         1,
@@ -223,10 +442,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        0.00088,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   88,
+                                       RatioDenominator: 100000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 31},
                                                Amount:         1,
@@ -234,10 +454,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        999999.9521,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   9999999521,
+                                       RatioDenominator: 10000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 32},
                                                Amount:         1,
@@ -245,10 +466,11 @@ func TestSortOrderKey(t *testing.T) {
                                                ControlProgram: []byte("aa"),
                                        },
                                },
-                               common.Order{
-                                       FromAssetID: &bc.AssetID{V0: 1},
-                                       ToAssetID:   &bc.AssetID{V0: 0},
-                                       Rate:        888888.7954,
+                               &common.Order{
+                                       FromAssetID:      &bc.AssetID{V0: 1},
+                                       ToAssetID:        &bc.AssetID{V0: 0},
+                                       RatioNumerator:   8888887954,
+                                       RatioDenominator: 10000,
                                        Utxo: &common.MovUtxo{
                                                SourceID:       &bc.Hash{V0: 33},
                                                Amount:         1,
@@ -300,10 +522,22 @@ func TestSortOrderKey(t *testing.T) {
                                },
                                expectedData{
                                        rate:     0.00098,
+                                       utxoHash: "14b51a6103f75d9cacdf0f9551467588c687ed3b029e25c646d276720569e227",
+                               },
+                               expectedData{
+                                       rate:     0.00098,
                                        utxoHash: "1fa9fae83d0a5401a4e92f80636966486e763eecca588aa11dff02b415320602",
                                },
                                expectedData{
                                        rate:     0.00098,
+                                       utxoHash: "6687d18ddbe4e7381a844e393ca3032a412285c9da6988eff182106e28ba09ca",
+                               },
+                               expectedData{
+                                       rate:     0.00098,
+                                       utxoHash: "841b1de7c871dfe6e2d1886809d9ae12ec45e570233b03879305232b096fda43",
+                               },
+                               expectedData{
+                                       rate:     0.00098,
                                        utxoHash: "a4bc534c267d35a9eafc25cd66e0cb270a2537a51186605b7f7591bc567ab4c6",
                                },
                                expectedData{
@@ -328,7 +562,7 @@ func TestSortOrderKey(t *testing.T) {
 
        for i, c := range cases {
                for _, order := range c.orders {
-                       key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(&order), order.Rate)
+                       key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
                        data, err := json.Marshal(order.Utxo)
                        if err != nil {
                                t.Fatal(err)
@@ -338,7 +572,6 @@ func TestSortOrderKey(t *testing.T) {
                }
 
                got := []expectedData{}
-
                itr := db.IteratorPrefixWithStart(nil, nil, false)
                for itr.Next() {
                        key := itr.Key()
@@ -347,9 +580,8 @@ func TestSortOrderKey(t *testing.T) {
                        copy(b[:], key[pos+8:])
                        utxoHash := bc.NewHash(b)
 
-                       rate := getRateFromOrderKey(key, ordersPrefix)
                        got = append(got, expectedData{
-                               rate:     rate,
+                               rate:     getRateFromOrderKey(key),
                                utxoHash: utxoHash.String(),
                        })
                }
@@ -358,7 +590,6 @@ func TestSortOrderKey(t *testing.T) {
                if !testutil.DeepEqual(c.want, got) {
                        t.Errorf("case %v: got recovery status, got: %v, want: %v.", i, got, c.want)
                }
-
        }
 }
 
@@ -378,1584 +609,210 @@ func TestMovStore(t *testing.T) {
                {
                        desc: "add order",
                        addOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       blockHeader: &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{V0: 524821139490765641, V1: 2484214155808702787, V2: 9108473449351508820, V3: 7972721253564512122}},
+                       wantOrders: []*common.Order{
+                               mockOrders[1],
+                               mockOrders[7],
+                               mockOrders[6],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[0],
+                       },
+                       wantTradePairs: []*common.TradePair{
+                               &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 8},
+                       },
+                       wantDBState: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+               },
+               {
+                       desc: "del some order",
+                       beforeOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       beforeTradePairs: []*common.TradePair{
+                               &common.TradePair{
                                        FromAssetID: assetID1,
                                        ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
+                                       Count:       8,
                                },
-                               &common.Order{
+                       },
+                       beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+                       delOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                       },
+                       blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+                       wantOrders: []*common.Order{
+                               mockOrders[7],
+                               mockOrders[6],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                       },
+                       wantTradePairs: []*common.TradePair{
+                               &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 5},
+                       },
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
+               },
+               {
+                       desc: "del all order",
+                       beforeOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       beforeTradePairs: []*common.TradePair{
+                               &common.TradePair{
                                        FromAssetID: assetID1,
                                        ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
+                                       Count:       8,
                                },
-                               &common.Order{
+                       },
+                       beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+                       delOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       blockHeader:    &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+                       wantOrders:     []*common.Order{},
+                       wantTradePairs: []*common.TradePair{},
+                       wantDBState:    &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
+               },
+               {
+                       desc: "Add and delete the same trade pair", // Add and delete different transaction pairs
+                       beforeOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       beforeTradePairs: []*common.TradePair{
+                               &common.TradePair{
                                        FromAssetID: assetID1,
                                        ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
+                                       Count:       8,
                                },
                        },
-                       blockHeader: &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{V0: 524821139490765641, V1: 2484214155808702787, V2: 9108473449351508820, V3: 7972721253564512122}},
+                       beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+                       addOrders: []*common.Order{
+                               mockOrders[8],
+                               mockOrders[9],
+                       },
+                       delOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
                        wantOrders: []*common.Order{
-                               &common.Order{
+                               mockOrders[9],
+                               mockOrders[8],
+                       },
+                       wantTradePairs: []*common.TradePair{
+                               &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 2},
+                       },
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
+               },
+               {
+                       desc: "Add and delete different transaction pairs",
+                       beforeOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                               mockOrders[10],
+                               mockOrders[11],
+                       },
+                       beforeTradePairs: []*common.TradePair{
+                               &common.TradePair{
                                        FromAssetID: assetID1,
                                        ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
+                                       Count:       8,
                                },
-                               &common.Order{
-                                       FromAssetID: assetID1,
+                               &common.TradePair{
+                                       FromAssetID: assetID3,
                                        ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
+                                       Count:       1,
                                },
-                               &common.Order{
-                                       FromAssetID: assetID1,
+                               &common.TradePair{
+                                       FromAssetID: assetID4,
                                        ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       wantTradePairs: []*common.TradePair{
-                               &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 8},
-                       },
-                       wantDBState: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
-               },
-               {
-                       desc: "del some order",
-                       beforeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       beforeTradePairs: []*common.TradePair{
-                               &common.TradePair{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Count:       8,
+                                       Count:       1,
                                },
                        },
                        beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+                       addOrders: []*common.Order{
+                               mockOrders[12],
+                               mockOrders[13],
+                               mockOrders[14],
+                       },
                        delOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                               mockOrders[10],
                        },
                        blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
                        wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[11],
+                               mockOrders[12],
+                               mockOrders[13],
+                               mockOrders[14],
                        },
                        wantTradePairs: []*common.TradePair{
-                               &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 5},
+                               &common.TradePair{FromAssetID: assetID4, ToAssetID: assetID2, Count: 2},
+                               &common.TradePair{FromAssetID: assetID5, ToAssetID: assetID2, Count: 1},
+                               &common.TradePair{FromAssetID: assetID6, ToAssetID: assetID2, Count: 1},
                        },
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
                },
-               {
-                       desc: "del all order",
-                       beforeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       beforeTradePairs: []*common.TradePair{
-                               &common.TradePair{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Count:       8,
-                               },
-                       },
-                       beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
-                       delOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       blockHeader:    &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
-                       wantOrders:     []*common.Order{},
-                       wantTradePairs: []*common.TradePair{},
-                       wantDBState:    &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
-               },
-               {
-                       desc: "Add and delete the same trade pair", //Add and delete different transaction pairs
-                       beforeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       beforeTradePairs: []*common.TradePair{
-                               &common.TradePair{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Count:       8,
-                               },
-                       },
-                       beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
-                       addOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 1},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 2},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       delOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
-                       wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 2},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 1},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       wantTradePairs: []*common.TradePair{
-                               &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 2},
-                       },
-                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
-               },
-               {
-                       desc: "Add and delete different transaction pairs",
-                       beforeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID3,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 33},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID4,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 34},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       beforeTradePairs: []*common.TradePair{
-                               &common.TradePair{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Count:       8,
-                               },
-                               &common.TradePair{
-                                       FromAssetID: assetID3,
-                                       ToAssetID:   assetID2,
-                                       Count:       1,
-                               },
-                               &common.TradePair{
-                                       FromAssetID: assetID4,
-                                       ToAssetID:   assetID2,
-                                       Count:       1,
-                               },
-                       },
-                       beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
-                       addOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID4,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 36},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID5,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 37},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID6,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 38},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       delOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID3,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 33},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
-                       wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID4,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 34},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID4,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 36},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID5,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 37},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID6,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 38},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       wantTradePairs: []*common.TradePair{
-                               &common.TradePair{FromAssetID: assetID4, ToAssetID: assetID2, Count: 2},
-                               &common.TradePair{FromAssetID: assetID5, ToAssetID: assetID2, Count: 1},
-                               &common.TradePair{FromAssetID: assetID6, ToAssetID: assetID2, Count: 1},
-                       },
-                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
-               },
-       }
-
-       initBlockHeader := &types.BlockHeader{
-               Height:  0,
-               Version: 1,
-       }
-
-       height := initBlockHeader.Height
-       hash := initBlockHeader.Hash()
-
-       defer os.RemoveAll("temp")
-       for i, c := range cases {
-               testDB := dbm.NewDB("testdb", "leveldb", "temp")
-               movStore, err := NewMovStore(testDB, height, &hash)
-               if err != nil {
-                       t.Fatalf("case %d: NewMovStore error %v.", i, err)
-               }
-
-               batch := movStore.db.NewBatch()
-               tradePairsCnt := make(map[common.TradePair]int)
-               movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
-               if len(c.beforeOrders) > 0 {
-                       tradePairsCnt = make(map[common.TradePair]int)
-                       for _, tradePair := range c.beforeTradePairs {
-                               tradePairsCnt[*tradePair] = tradePair.Count
-                       }
-                       movStore.updateTradePairs(batch, tradePairsCnt)
-                       movStore.saveMovDatabaseState(batch, c.beforeDBStatus)
-               }
-               batch.Write()
-
-               if err := movStore.ProcessOrders(c.addOrders, c.delOrders, c.blockHeader); err != nil {
-                       t.Fatalf("case %d: ProcessOrders error %v.", i, err)
-               }
-
-               var gotOrders []*common.Order
-
-               tmp, err := movStore.ListOrders(&common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0})
-               if err != nil {
-                       t.Fatalf("case %d: ListOrders(assetID1 and assetID2) error %v.", i, err)
-               }
-
-               gotOrders = append(gotOrders, tmp...)
-
-               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID3, ToAssetID: assetID2, Rate: 0})
-               if err != nil {
-                       t.Fatalf("case %d: ListOrders(assetID3 and assetID2)  error %v.", i, err)
-               }
-
-               gotOrders = append(gotOrders, tmp...)
-
-               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID4, ToAssetID: assetID2, Rate: 0})
-               if err != nil {
-                       t.Fatalf("case %d: ListOrders(assetID4 and assetID2)  error %v.", i, err)
-               }
-
-               gotOrders = append(gotOrders, tmp...)
-
-               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID5, ToAssetID: assetID2, Rate: 0})
-               if err != nil {
-                       t.Fatalf("case %d: ListOrders(assetID5 and assetID2)  error %v.", i, err)
-               }
-
-               gotOrders = append(gotOrders, tmp...)
-
-               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID6, ToAssetID: assetID2, Rate: 0})
-               if err != nil {
-                       t.Fatalf("case %d: ListOrders(assetID6 and assetID2)  error %v.", i, err)
-               }
-
-               gotOrders = append(gotOrders, tmp...)
-
-               if !testutil.DeepEqual(gotOrders, c.wantOrders) {
-                       t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
-               }
-
-               gotTradePairs, err := movStore.ListTradePairsWithStart(nil, nil)
-               if err != nil {
-                       t.Fatalf("case %d: ListTradePairsWithStart error %v.", i, err)
-               }
-
-               if !testutil.DeepEqual(gotTradePairs, c.wantTradePairs) {
-                       t.Fatalf("case %d: got tradePairs, gotTradePairs: %v, wantTradePairs: %v.", i, gotTradePairs, c.wantTradePairs)
-               }
-
-               gotDBState, err := movStore.GetMovDatabaseState()
-               if err != nil {
-                       t.Fatalf("case %d: GetMovDatabaseState error %v.", i, err)
-               }
-
-               if !testutil.DeepEqual(gotDBState, c.wantDBState) {
-                       t.Fatalf("case %d: got tradePairs, gotDBState: %v, wantDBStatus: %v.", i, gotDBState, c.wantDBState)
-               }
-
-               testDB.Close()
-               os.RemoveAll("temp")
-       }
-}
-
-func TestListOrders(t *testing.T) {
-       cases := []struct {
-               desc        string
-               storeOrders []*common.Order
-               query       *common.Order
-               wantOrders  []*common.Order
-       }{
-               {
-                       desc:       "empty",
-                       query:      &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
-                       wantOrders: []*common.Order{},
-               },
-               {
-                       desc: "query from first",
-                       storeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       query: &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
-                       wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-               },
-               {
-                       desc: "query from middle",
-                       storeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       query: &common.Order{
-                               FromAssetID: assetID1,
-                               ToAssetID:   assetID2,
-                               Rate:        0.00098,
-                               Utxo: &common.MovUtxo{
-                                       SourceID:       &bc.Hash{V0: 13},
-                                       Amount:         1,
-                                       SourcePos:      0,
-                                       ControlProgram: []byte("aa"),
-                               },
-                       },
-                       wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-               },
        }
 
        initBlockHeader := &types.BlockHeader{
@@ -1969,404 +826,239 @@ func TestListOrders(t *testing.T) {
        defer os.RemoveAll("temp")
        for i, c := range cases {
                testDB := dbm.NewDB("testdb", "leveldb", "temp")
-               movStore, err := NewMovStore(testDB, height, &hash)
-               if err != nil {
-                       t.Fatalf("case %d: NewMovStore error %v.", i, err)
+               movStore := NewLevelDBMovStore(testDB)
+               if err := movStore.InitDBState(height, &hash); err != nil {
+                       t.Fatalf("case %d: InitDBState error %v.", i, err)
                }
 
                batch := movStore.db.NewBatch()
-               tradePairsCnt := make(map[common.TradePair]int)
-               movStore.addOrders(batch, c.storeOrders, tradePairsCnt)
-               movStore.updateTradePairs(batch, tradePairsCnt)
+               tradePairsCnt := make(map[string]*common.TradePair)
+               movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
+               if len(c.beforeOrders) > 0 {
+                       tradePairsCnt = make(map[string]*common.TradePair)
+                       for _, tradePair := range c.beforeTradePairs {
+                               tradePairsCnt[tradePair.Key()] = tradePair
+                       }
+                       movStore.updateTradePairs(batch, tradePairsCnt)
+                       movStore.saveMovDatabaseState(batch, c.beforeDBStatus)
+               }
                batch.Write()
 
-               gotOrders, err := movStore.ListOrders(c.query)
+               if err := movStore.ProcessOrders(c.addOrders, c.delOrders, c.blockHeader); err != nil {
+                       t.Fatalf("case %d: ProcessOrders error %v.", i, err)
+               }
+
+               var gotOrders []*common.Order
+
+               tmp, err := movStore.ListOrders(&common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
                if err != nil {
-                       t.Fatalf("case %d: ListOrders error %v.", i, err)
+                       t.Fatalf("case %d: ListOrders(assetID1 and assetID2) error %v.", i, err)
                }
 
-               if !testutil.DeepEqual(gotOrders, c.wantOrders) {
-                       t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
+               gotOrders = append(gotOrders, tmp...)
+
+               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID3, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+               if err != nil {
+                       t.Fatalf("case %d: ListOrders(assetID3 and assetID2)  error %v.", i, err)
                }
 
-               testDB.Close()
-               os.RemoveAll("temp")
-       }
-}
+               gotOrders = append(gotOrders, tmp...)
 
-func TestAddOrders(t *testing.T) {
-       cases := []struct {
-               desc         string
-               beforeOrders []*common.Order
-               addOrders    []*common.Order
-               wantOrders   []*common.Order
-       }{
-               {
-                       desc: "empty",
-                       addOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                       },
-                       wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID4, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+               if err != nil {
+                       t.Fatalf("case %d: ListOrders(assetID4 and assetID2)  error %v.", i, err)
+               }
+
+               gotOrders = append(gotOrders, tmp...)
+
+               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID5, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+               if err != nil {
+                       t.Fatalf("case %d: ListOrders(assetID5 and assetID2)  error %v.", i, err)
+               }
+
+               gotOrders = append(gotOrders, tmp...)
+
+               tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID6, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+               if err != nil {
+                       t.Fatalf("case %d: ListOrders(assetID6 and assetID2)  error %v.", i, err)
+               }
+
+               gotOrders = append(gotOrders, tmp...)
+
+               if !testutil.DeepEqual(gotOrders, c.wantOrders) {
+                       t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
+               }
+
+               gotTradePairs, err := movStore.ListTradePairsWithStart(nil, nil)
+               if err != nil {
+                       t.Fatalf("case %d: ListTradePairsWithStart error %v.", i, err)
+               }
+
+               if !testutil.DeepEqual(gotTradePairs, c.wantTradePairs) {
+                       t.Fatalf("case %d: got tradePairs, gotTradePairs: %v, wantTradePairs: %v.", i, gotTradePairs, c.wantTradePairs)
+               }
+
+               gotDBState, err := movStore.GetMovDatabaseState()
+               if err != nil {
+                       t.Fatalf("case %d: GetMovDatabaseState error %v.", i, err)
+               }
+
+               if !testutil.DeepEqual(gotDBState, c.wantDBState) {
+                       t.Fatalf("case %d: got tradePairs, gotDBState: %v, wantDBStatus: %v.", i, gotDBState, c.wantDBState)
+               }
+
+               testDB.Close()
+               os.RemoveAll("temp")
+       }
+}
+
+func TestListOrders(t *testing.T) {
+       cases := []struct {
+               desc        string
+               storeOrders []*common.Order
+               query       *common.Order
+               wantOrders  []*common.Order
+       }{
+               {
+                       desc:       "empty",
+                       query:      &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
+                       wantOrders: []*common.Order{},
+               },
+               {
+                       desc: "query from first",
+                       storeOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                               mockOrders[10],
+                       },
+                       query: &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
+                       wantOrders: []*common.Order{
+                               mockOrders[1],
+                               mockOrders[7],
+                               mockOrders[6],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[0],
                        },
                },
                {
-                       desc: "Stored data already exists",
-                       beforeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                       desc: "query from middle",
+                       storeOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       query: mockOrders[3],
+                       wantOrders: []*common.Order{
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[0],
                        },
+               },
+       }
+
+       initBlockHeader := &types.BlockHeader{
+               Height:  0,
+               Version: 1,
+       }
+
+       height := initBlockHeader.Height
+       hash := initBlockHeader.Hash()
+
+       defer os.RemoveAll("temp")
+       for i, c := range cases {
+               testDB := dbm.NewDB("testdb", "leveldb", "temp")
+               movStore := NewLevelDBMovStore(testDB)
+               if err := movStore.InitDBState(height, &hash); err != nil {
+                       t.Fatalf("case %d: InitDBState error %v.", i, err)
+               }
+
+               batch := movStore.db.NewBatch()
+               tradePairsCnt := make(map[string]*common.TradePair)
+               movStore.addOrders(batch, c.storeOrders, tradePairsCnt)
+               movStore.updateTradePairs(batch, tradePairsCnt)
+               batch.Write()
+
+               gotOrders, err := movStore.ListOrders(c.query)
+               if err != nil {
+                       t.Fatalf("case %d: ListOrders error %v.", i, err)
+               }
+
+               if !testutil.DeepEqual(gotOrders, c.wantOrders) {
+                       t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
+               }
+
+               testDB.Close()
+               os.RemoveAll("temp")
+       }
+}
+
+func TestAddOrders(t *testing.T) {
+       cases := []struct {
+               desc         string
+               beforeOrders []*common.Order
+               addOrders    []*common.Order
+               wantOrders   []*common.Order
+       }{
+               {
+                       desc: "empty",
                        addOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
                        },
-                       wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                       wantOrders: []*common.Order{
+                               mockOrders[1],
+                               mockOrders[7],
+                               mockOrders[6],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[0],
+                       },
+               },
+               {
+                       desc: "Stored data already exists",
+                       beforeOrders: []*common.Order{
+                               mockOrders[0],
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                       },
+                       addOrders: []*common.Order{
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
+                       },
+                       wantOrders: []*common.Order{
+                               mockOrders[1],
+                               mockOrders[7],
+                               mockOrders[6],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[0],
                        },
                },
        }
@@ -2382,18 +1074,18 @@ func TestAddOrders(t *testing.T) {
        defer os.RemoveAll("temp")
        for i, c := range cases {
                testDB := dbm.NewDB("testdb", "leveldb", "temp")
-               movStore, err := NewMovStore(testDB, height, &hash)
-               if err != nil {
-                       t.Fatalf("case %d: NewMovStore error %v.", i, err)
+               movStore := NewLevelDBMovStore(testDB)
+               if err := movStore.InitDBState(height, &hash); err != nil {
+                       t.Fatalf("case %d: InitDBState error %v.", i, err)
                }
 
                batch := movStore.db.NewBatch()
-               tradePairsCnt := make(map[common.TradePair]int)
+               tradePairsCnt := make(map[string]*common.TradePair)
                movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
                movStore.updateTradePairs(batch, tradePairsCnt)
                batch.Write()
 
-               tradePairsCnt = make(map[common.TradePair]int)
+               tradePairsCnt = make(map[string]*common.TradePair)
                movStore.addOrders(batch, c.addOrders, tradePairsCnt)
                batch.Write()
 
@@ -2422,28 +1114,8 @@ func TestDelOrders(t *testing.T) {
                {
                        desc: "empty",
                        delOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[0],
+                               mockOrders[1],
                        },
                        wantOrders: []*common.Order{},
                        err:        errors.New("don't find trade pair"),
@@ -2451,260 +1123,40 @@ func TestDelOrders(t *testing.T) {
                {
                        desc: "Delete existing data",
                        beforeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[1],
+                               mockOrders[7],
+                               mockOrders[6],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[0],
                        },
                        delOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         10,
-                                               SourcePos:      1,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[4],
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
                        },
                        wantOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 22},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00097,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 23},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00098,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 13},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        1.00090,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 21},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[1],
+                               mockOrders[2],
+                               mockOrders[3],
+                               mockOrders[0],
                        },
                        err: nil,
                },
                {
                        desc: "Delete all data",
                        beforeOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[7],
+                               mockOrders[6],
+                               mockOrders[5],
                        },
                        delOrders: []*common.Order{
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00099,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 24},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00096,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 25},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
-                               &common.Order{
-                                       FromAssetID: assetID1,
-                                       ToAssetID:   assetID2,
-                                       Rate:        0.00095,
-                                       Utxo: &common.MovUtxo{
-                                               SourceID:       &bc.Hash{V0: 26},
-                                               Amount:         1,
-                                               SourcePos:      0,
-                                               ControlProgram: []byte("aa"),
-                                       },
-                               },
+                               mockOrders[5],
+                               mockOrders[6],
+                               mockOrders[7],
                        },
                        wantOrders: []*common.Order{},
                        err:        nil,
@@ -2722,18 +1174,18 @@ func TestDelOrders(t *testing.T) {
        defer os.RemoveAll("temp")
        for i, c := range cases {
                testDB := dbm.NewDB("testdb", "leveldb", "temp")
-               movStore, err := NewMovStore(testDB, height, &hash)
-               if err != nil {
-                       t.Fatalf("case %d: NewMovStore error %v.", i, err)
+               movStore := NewLevelDBMovStore(testDB)
+               if err := movStore.InitDBState(height, &hash); err != nil {
+                       t.Fatalf("case %d: InitDBState error %v.", i, err)
                }
 
                batch := movStore.db.NewBatch()
-               tradePairsCnt := make(map[common.TradePair]int)
+               tradePairsCnt := make(map[string]*common.TradePair)
                movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
                movStore.updateTradePairs(batch, tradePairsCnt)
                batch.Write()
 
-               tradePairsCnt = make(map[common.TradePair]int)
+               tradePairsCnt = make(map[string]*common.TradePair)
                movStore.deleteOrders(batch, c.delOrders, tradePairsCnt)
                movStore.updateTradePairs(batch, tradePairsCnt)
                batch.Write()
@@ -2755,7 +1207,7 @@ func TestDelOrders(t *testing.T) {
 func TestListTradePairsWithStart(t *testing.T) {
        cases := []struct {
                desc            string
-               storeTradePairs map[common.TradePair]int
+               storeTradePairs map[string]*common.TradePair
                query           *common.TradePair
                wantTradePairs  []*common.TradePair
        }{
@@ -2766,13 +1218,13 @@ func TestListTradePairsWithStart(t *testing.T) {
                },
                {
                        desc: "query from first",
-                       storeTradePairs: map[common.TradePair]int{
-                               common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
-                               common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
-                               common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
-                               common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
+                       storeTradePairs: map[string]*common.TradePair{
+                               (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+                               (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+                               (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+                               (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
                        },
                        query: &common.TradePair{},
                        wantTradePairs: []*common.TradePair{
@@ -2786,14 +1238,14 @@ func TestListTradePairsWithStart(t *testing.T) {
                },
                {
                        desc: "query from middle",
-                       storeTradePairs: map[common.TradePair]int{
-                               common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
-                               common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
-                               common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
-                               common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
-                               common.TradePair{FromAssetID: assetID6, ToAssetID: assetID8}: 7,
+                       storeTradePairs: map[string]*common.TradePair{
+                               (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+                               (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+                               (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+                               (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
+                               (&common.TradePair{FromAssetID: assetID6, ToAssetID: assetID8}).Key(): {FromAssetID: assetID6, ToAssetID: assetID8, Count: 7},
                        },
                        query: &common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
                        wantTradePairs: []*common.TradePair{
@@ -2816,9 +1268,9 @@ func TestListTradePairsWithStart(t *testing.T) {
        defer os.RemoveAll("temp")
        for i, c := range cases {
                testDB := dbm.NewDB("testdb", "leveldb", "temp")
-               movStore, err := NewMovStore(testDB, height, &hash)
-               if err != nil {
-                       t.Fatalf("case %d: NewMovStore error %v.", i, err)
+               movStore := NewLevelDBMovStore(testDB)
+               if err := movStore.InitDBState(height, &hash); err != nil {
+                       t.Fatalf("case %d: InitDBState error %v.", i, err)
                }
 
                batch := movStore.db.NewBatch()
@@ -2842,20 +1294,20 @@ func TestListTradePairsWithStart(t *testing.T) {
 func TestUpdateTradePairs(t *testing.T) {
        cases := []struct {
                desc             string
-               beforeTradePairs map[common.TradePair]int
-               addTradePairs    map[common.TradePair]int
-               delTradePairs    map[common.TradePair]int
+               beforeTradePairs map[string]*common.TradePair
+               addTradePairs    map[string]*common.TradePair
+               delTradePairs    map[string]*common.TradePair
                wantTradePairs   []*common.TradePair
        }{
                {
                        desc: "empty",
-                       addTradePairs: map[common.TradePair]int{
-                               common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
-                               common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
-                               common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
-                               common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
+                       addTradePairs: map[string]*common.TradePair{
+                               (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+                               (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+                               (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+                               (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
                        },
                        wantTradePairs: []*common.TradePair{
                                &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
@@ -2868,15 +1320,15 @@ func TestUpdateTradePairs(t *testing.T) {
                },
                {
                        desc: "Stored data already exists",
-                       beforeTradePairs: map[common.TradePair]int{
-                               common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
-                               common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
-                               common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
+                       beforeTradePairs: map[string]*common.TradePair{
+                               (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+                               (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+                               (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
                        },
-                       addTradePairs: map[common.TradePair]int{
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
-                               common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
+                       addTradePairs: map[string]*common.TradePair{
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+                               (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
                        },
                        wantTradePairs: []*common.TradePair{
                                &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
@@ -2889,19 +1341,19 @@ func TestUpdateTradePairs(t *testing.T) {
                },
                {
                        desc: "delete some data",
-                       beforeTradePairs: map[common.TradePair]int{
-                               common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
-                               common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
-                               common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
-                               common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
-                       },
-                       delTradePairs: map[common.TradePair]int{
-                               common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: -1,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: -4,
-                               common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: -2,
-                               common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: -4,
+                       beforeTradePairs: map[string]*common.TradePair{
+                               (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+                               (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+                               (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+                               (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
+                       },
+                       delTradePairs: map[string]*common.TradePair{
+                               (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: -1},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: -4},
+                               (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: -2},
+                               (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: -4},
                        },
                        wantTradePairs: []*common.TradePair{
                                &common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
@@ -2923,9 +1375,9 @@ func TestUpdateTradePairs(t *testing.T) {
        defer os.RemoveAll("temp")
        for i, c := range cases {
                testDB := dbm.NewDB("testdb", "leveldb", "temp")
-               movStore, err := NewMovStore(testDB, height, &hash)
-               if err != nil {
-                       t.Fatalf("case %d: NewMovStore error %v.", i, err)
+               movStore := NewLevelDBMovStore(testDB)
+               if err := movStore.InitDBState(height, &hash); err != nil {
+                       t.Fatalf("case %d: InitDBState error %v.", i, err)
                }
 
                batch := movStore.db.NewBatch()
@@ -2995,9 +1447,9 @@ func TestCheckMovDatabaseState(t *testing.T) {
        defer os.RemoveAll("temp")
        for i, c := range cases {
                testDB := dbm.NewDB("testdb", "leveldb", "temp")
-               movStore, err := NewMovStore(testDB, height, &hash)
-               if err != nil {
-                       t.Fatalf("case %d: NewMovStore error %v.", i, err)
+               movStore := NewLevelDBMovStore(testDB)
+               if err := movStore.InitDBState(height, &hash); err != nil {
+                       t.Fatalf("case %d: InitDBState error %v.", i, err)
                }
 
                batch := movStore.db.NewBatch()
diff --git a/application/mov/match/engine.go b/application/mov/match/engine.go
new file mode 100644 (file)
index 0000000..77de111
--- /dev/null
@@ -0,0 +1,263 @@
+package match
+
+import (
+       "math/big"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/contract"
+       "github.com/bytom/vapor/consensus/segwit"
+       "github.com/bytom/vapor/errors"
+       vprMath "github.com/bytom/vapor/math"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/protocol/vm"
+)
+
+// Engine is used to generate math transactions
+type Engine struct {
+       orderBook     *OrderBook
+       feeStrategy   FeeStrategy
+       rewardProgram []byte
+}
+
+// NewEngine return a new Engine
+func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
+       return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
+}
+
+// HasMatchedTx check does the input trade pair can generate a match deal
+func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
+       if err := validateTradePairs(tradePairs); err != nil {
+               return false
+       }
+
+       orders := e.orderBook.PeekOrders(tradePairs)
+       if len(orders) == 0 {
+               return false
+       }
+
+       return IsMatched(orders)
+}
+
+// NextMatchedTx return the next matchable transaction by the specified trade pairs
+// the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
+// for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
+func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
+       if !e.HasMatchedTx(tradePairs...) {
+               return nil, errors.New("the specified trade pairs can not be matched")
+       }
+
+       tx, err := e.buildMatchTx(sortOrders(e.orderBook.PeekOrders(tradePairs)))
+       if err != nil {
+               return nil, err
+       }
+
+       for _, tradePair := range tradePairs {
+               e.orderBook.PopOrder(tradePair)
+       }
+
+       if err := e.addPartialTradeOrder(tx); err != nil {
+               return nil, err
+       }
+       return tx, nil
+}
+
+func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, refunds []RefundAssets, fees []*bc.AssetAmount) error {
+       for _, feeAmount := range fees {
+               txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
+       }
+
+       for i, refund := range refunds {
+               // each trading participant may be refunded multiple assets
+               for _, assetAmount := range refund {
+                       contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
+                       if err != nil {
+                               return err
+                       }
+
+                       txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, contractArgs.SellerProgram))
+               }
+       }
+       return nil
+}
+
+func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
+       for i, output := range tx.Outputs {
+               if !segwit.IsP2WMCScript(output.ControlProgram()) || output.AssetAmount().Amount == 0 {
+                       continue
+               }
+
+               order, err := common.NewOrderFromOutput(tx, i)
+               if err != nil {
+                       return err
+               }
+
+               e.orderBook.AddOrder(order)
+       }
+       return nil
+}
+
+func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
+       txData := &types.TxData{Version: 1}
+       for _, order := range orders {
+               input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
+               txData.Inputs = append(txData.Inputs, input)
+       }
+
+       receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
+       allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
+       if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets.Receives); err != nil {
+               return nil, err
+       }
+
+       if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Refunds, allocatedAssets.Fees); err != nil {
+               return nil, err
+       }
+
+       byteData, err := txData.MarshalText()
+       if err != nil {
+               return nil, err
+       }
+
+       txData.SerializedSize = uint64(len(byteData))
+       return types.NewTx(*txData), nil
+}
+
+func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts, deductFeeReceives []*bc.AssetAmount) error {
+       for i, order := range orders {
+               contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
+               if err != nil {
+                       return err
+               }
+
+               requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
+               receivedAmount := receivedAmounts[i].Amount
+               shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
+               isPartialTrade := requestAmount > receivedAmount
+
+               setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmounts[i].Amount)
+               txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, deductFeeReceives[i].Amount, contractArgs.SellerProgram))
+               if isPartialTrade {
+                       txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
+               }
+       }
+       return nil
+}
+
+func calcOppositeIndex(size int, selfIdx int) int {
+       return (selfIdx + 1) % size
+}
+
+// CalcRequestAmount is from amount * numerator / ratioDenominator
+func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
+       res := big.NewInt(0).SetUint64(fromAmount)
+       res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
+       if !res.IsUint64() {
+               return 0
+       }
+       return res.Uint64()
+}
+
+func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
+       res := big.NewInt(0).SetUint64(receiveAmount)
+       res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
+       if !res.IsUint64() {
+               return 0
+       }
+       return res.Uint64()
+}
+
+// CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
+func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, []*bc.AssetAmount) {
+       var receivedAmounts, priceDiffs, shouldPayAmounts []*bc.AssetAmount
+       for i, order := range orders {
+               requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
+               oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
+               receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
+               shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
+               receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
+               shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
+       }
+
+       for i, receivedAmount := range receivedAmounts {
+               oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
+               if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
+                       assetID := oppositeShouldPayAmount.AssetId
+                       amount := oppositeShouldPayAmount.Amount - receivedAmount.Amount
+                       priceDiffs = append(priceDiffs, &bc.AssetAmount{AssetId: assetID, Amount: amount})
+               }
+       }
+       return receivedAmounts, priceDiffs
+}
+
+// IsMatched check does the orders can be exchange
+func IsMatched(orders []*common.Order) bool {
+       sortedOrders := sortOrders(orders)
+       if len(sortedOrders) == 0 {
+               return false
+       }
+
+       product := big.NewRat(1, 1)
+       for _, order := range orders {
+               product.Mul(product, big.NewRat(order.RatioNumerator, order.RatioDenominator))
+       }
+       one := big.NewRat(1, 1)
+       return product.Cmp(one) <= 0
+}
+
+func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
+       var arguments [][]byte
+       if isPartialTrade {
+               arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
+       } else {
+               arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
+       }
+       txInput.SetArguments(arguments)
+}
+
+func sortOrders(orders []*common.Order) []*common.Order {
+       if len(orders) == 0 {
+               return nil
+       }
+
+       orderMap := make(map[bc.AssetID]*common.Order)
+       firstOrder := orders[0]
+       for i := 1; i < len(orders); i++ {
+               orderMap[*orders[i].FromAssetID] = orders[i]
+       }
+
+       sortedOrders := []*common.Order{firstOrder}
+       for order := firstOrder; *order.ToAssetID != *firstOrder.FromAssetID; {
+               nextOrder, ok := orderMap[*order.ToAssetID]
+               if !ok {
+                       return nil
+               }
+
+               sortedOrders = append(sortedOrders, nextOrder)
+               order = nextOrder
+       }
+       return sortedOrders
+}
+
+func validateTradePairs(tradePairs []*common.TradePair) error {
+       if len(tradePairs) < 2 {
+               return errors.New("size of trade pairs at least 2")
+       }
+
+       assetMap := make(map[string]bool)
+       for _, tradePair := range tradePairs {
+               assetMap[tradePair.FromAssetID.String()] = true
+               if *tradePair.FromAssetID == *tradePair.ToAssetID {
+                       return errors.New("from asset id can't equal to asset id")
+               }
+       }
+
+       for _, tradePair := range tradePairs {
+               key := tradePair.ToAssetID.String()
+               if _, ok := assetMap[key]; !ok {
+                       return errors.New("invalid trade pairs")
+               }
+               delete(assetMap, key)
+       }
+       return nil
+}
diff --git a/application/mov/match/engine_test.go b/application/mov/match/engine_test.go
new file mode 100644 (file)
index 0000000..1ac993a
--- /dev/null
@@ -0,0 +1,388 @@
+package match
+
+import (
+       "testing"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/mock"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/protocol/validation"
+)
+
+func TestGenerateMatchedTxs(t *testing.T) {
+       btc2eth := &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+       eth2btc := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.BTC}
+       eth2eos := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.EOS}
+       eos2btc := &common.TradePair{FromAssetID: &mock.EOS, ToAssetID: &mock.BTC}
+
+       cases := []struct {
+               desc            string
+               tradePairs      []*common.TradePair
+               initStoreOrders []*common.Order
+               wantMatchedTxs  []*types.Tx
+       }{
+               {
+                       desc:       "full matched",
+                       tradePairs: []*common.TradePair{btc2eth, eth2btc},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[0],
+                       },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[1],
+                       },
+               },
+               {
+                       desc:       "partial matched",
+                       tradePairs: []*common.TradePair{btc2eth, eth2btc},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[1],
+                       },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[0],
+                       },
+               },
+               {
+                       desc:       "partial matched and continue to match",
+                       tradePairs: []*common.TradePair{btc2eth, eth2btc},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[2],
+                       },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[2],
+                               mock.MatchedTxs[3],
+                       },
+               },
+               {
+                       desc:       "unable to match",
+                       tradePairs: []*common.TradePair{btc2eth, eth2btc},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[0],
+                       },
+                       wantMatchedTxs: []*types.Tx{},
+               },
+               {
+                       desc:       "cycle match",
+                       tradePairs: []*common.TradePair{btc2eth, eth2eos, eos2btc},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Eth2EosOrders[0], mock.Eos2BtcOrders[0],
+                       },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[6],
+                       },
+               },
+               {
+                       desc:       "multiple assets as a fee",
+                       tradePairs: []*common.TradePair{btc2eth, eth2btc},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Eth2BtcOrders[3],
+                       },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[11],
+                       },
+               },
+       }
+
+       for i, c := range cases {
+               movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
+               matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), NewDefaultFeeStrategy(), mock.RewardProgram)
+               var gotMatchedTxs []*types.Tx
+               for matchEngine.HasMatchedTx(c.tradePairs...) {
+                       matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+
+                       gotMatchedTxs = append(gotMatchedTxs, matchedTx)
+               }
+
+               if len(c.wantMatchedTxs) != len(gotMatchedTxs) {
+                       t.Errorf("#%d(%s) the length of got matched tx is not equals want matched tx", i, c.desc)
+                       continue
+               }
+
+               for j, gotMatchedTx := range gotMatchedTxs {
+                       if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
+                               t.Fatal(err)
+                       }
+
+                       c.wantMatchedTxs[j].Version = 1
+                       byteData, err := c.wantMatchedTxs[j].MarshalText()
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+
+                       c.wantMatchedTxs[j].SerializedSize = uint64(len(byteData))
+                       wantMatchedTx := types.NewTx(c.wantMatchedTxs[j].TxData)
+                       if gotMatchedTx.ID != wantMatchedTx.ID {
+                               t.Errorf("#%d(%s) the tx hash of got matched tx: %s is not equals want matched tx: %s", i, c.desc, gotMatchedTx.ID.String(), wantMatchedTx.ID.String())
+                       }
+               }
+       }
+}
+
+func TestValidateTradePairs(t *testing.T) {
+       cases := []struct {
+               desc       string
+               tradePairs []*common.TradePair
+               wantError  bool
+       }{
+               {
+                       desc: "valid case of two trade pairs",
+                       tradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &mock.BTC,
+                                       ToAssetID:   &mock.ETH,
+                               },
+                               {
+                                       FromAssetID: &mock.ETH,
+                                       ToAssetID:   &mock.BTC,
+                               },
+                       },
+                       wantError: false,
+               },
+               {
+                       desc: "invalid case of two trade pairs",
+                       tradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &mock.BTC,
+                                       ToAssetID:   &mock.ETH,
+                               },
+                               {
+                                       FromAssetID: &mock.ETH,
+                                       ToAssetID:   &mock.EOS,
+                               },
+                       },
+                       wantError: true,
+               },
+               {
+                       desc: "valid case of three trade pairs",
+                       tradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &mock.BTC,
+                                       ToAssetID:   &mock.ETH,
+                               },
+                               {
+                                       FromAssetID: &mock.ETH,
+                                       ToAssetID:   &mock.EOS,
+                               },
+                               {
+                                       FromAssetID: &mock.EOS,
+                                       ToAssetID:   &mock.BTC,
+                               },
+                       },
+                       wantError: false,
+               },
+               {
+                       desc: "invalid case of three trade pairs",
+                       tradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &mock.BTC,
+                                       ToAssetID:   &mock.ETH,
+                               },
+                               {
+                                       FromAssetID: &mock.ETH,
+                                       ToAssetID:   &mock.BTC,
+                               },
+                               {
+                                       FromAssetID: &mock.BTC,
+                                       ToAssetID:   &mock.BTC,
+                               },
+                       },
+                       wantError: true,
+               },
+               {
+                       desc: "valid case 2 of three trade pairs",
+                       tradePairs: []*common.TradePair{
+                               {
+                                       FromAssetID: &mock.BTC,
+                                       ToAssetID:   &mock.ETH,
+                               },
+                               {
+                                       FromAssetID: &mock.EOS,
+                                       ToAssetID:   &mock.BTC,
+                               },
+                               {
+                                       FromAssetID: &mock.ETH,
+                                       ToAssetID:   &mock.EOS,
+                               },
+                       },
+                       wantError: false,
+               },
+       }
+
+       for i, c := range cases {
+               err := validateTradePairs(c.tradePairs)
+               if c.wantError && err == nil {
+                       t.Errorf("#%d(%s): want error, got no error", i, c.desc)
+               }
+
+               if !c.wantError && err != nil {
+                       t.Errorf("#%d(%s): want no error, got error (%v)", i, c.desc, err)
+               }
+       }
+}
+
+func TestIsMatched(t *testing.T) {
+       cases := []struct {
+               desc        string
+               orders      []*common.Order
+               wantMatched bool
+       }{
+               {
+                       desc: "ratio is equals",
+                       orders: []*common.Order{
+                               {
+                                       FromAssetID:      &mock.BTC,
+                                       ToAssetID:        &mock.ETH,
+                                       RatioNumerator:   6250,
+                                       RatioDenominator: 3,
+                               },
+                               {
+                                       FromAssetID:      &mock.ETH,
+                                       ToAssetID:        &mock.BTC,
+                                       RatioNumerator:   3,
+                                       RatioDenominator: 6250,
+                               },
+                       },
+                       wantMatched: true,
+               },
+               {
+                       desc: "ratio is equals, and numerator and denominator are multiples of the opposite",
+                       orders: []*common.Order{
+                               {
+                                       FromAssetID:      &mock.BTC,
+                                       ToAssetID:        &mock.ETH,
+                                       RatioNumerator:   6250,
+                                       RatioDenominator: 3,
+                               },
+                               {
+                                       FromAssetID:      &mock.ETH,
+                                       ToAssetID:        &mock.BTC,
+                                       RatioNumerator:   9,
+                                       RatioDenominator: 18750,
+                               },
+                       },
+                       wantMatched: true,
+               },
+               {
+                       desc: "matched with a slight diff",
+                       orders: []*common.Order{
+                               {
+                                       FromAssetID:      &mock.BTC,
+                                       ToAssetID:        &mock.ETH,
+                                       RatioNumerator:   62500000000000000,
+                                       RatioDenominator: 30000000000001,
+                               },
+                               {
+                                       FromAssetID:      &mock.ETH,
+                                       ToAssetID:        &mock.BTC,
+                                       RatioNumerator:   3,
+                                       RatioDenominator: 6250,
+                               },
+                       },
+                       wantMatched: true,
+               },
+               {
+                       desc: "not matched with a slight diff",
+                       orders: []*common.Order{
+                               {
+                                       FromAssetID:      &mock.BTC,
+                                       ToAssetID:        &mock.ETH,
+                                       RatioNumerator:   62500000000000001,
+                                       RatioDenominator: 30000000000000,
+                               },
+                               {
+                                       FromAssetID:      &mock.ETH,
+                                       ToAssetID:        &mock.BTC,
+                                       RatioNumerator:   3,
+                                       RatioDenominator: 6250,
+                               },
+                       },
+                       wantMatched: false,
+               },
+               {
+                       desc: "ring matched",
+                       orders: []*common.Order{
+                               {
+                                       FromAssetID:      &mock.BTC,
+                                       ToAssetID:        &mock.ETH,
+                                       RatioNumerator:   2000000003,
+                                       RatioDenominator: 100000001,
+                               },
+                               {
+                                       FromAssetID:      &mock.ETH,
+                                       ToAssetID:        &mock.EOS,
+                                       RatioNumerator:   400000000001,
+                                       RatioDenominator: 2000000003,
+                               },
+                               {
+                                       FromAssetID:      &mock.EOS,
+                                       ToAssetID:        &mock.BTC,
+                                       RatioNumerator:   100000001,
+                                       RatioDenominator: 400000000001,
+                               },
+                       },
+                       wantMatched: true,
+               },
+               {
+                       desc: "ring matched with a slight diff",
+                       orders: []*common.Order{
+                               {
+                                       FromAssetID:      &mock.BTC,
+                                       ToAssetID:        &mock.ETH,
+                                       RatioNumerator:   2000000003,
+                                       RatioDenominator: 100000001,
+                               },
+                               {
+                                       FromAssetID:      &mock.ETH,
+                                       ToAssetID:        &mock.EOS,
+                                       RatioNumerator:   400000000001,
+                                       RatioDenominator: 2000000003,
+                               },
+                               {
+                                       FromAssetID:      &mock.EOS,
+                                       ToAssetID:        &mock.BTC,
+                                       RatioNumerator:   100000000,
+                                       RatioDenominator: 400000000001,
+                               },
+                       },
+                       wantMatched: true,
+               },
+               {
+                       desc: "ring fail matched with a slight diff",
+                       orders: []*common.Order{
+                               {
+                                       FromAssetID:      &mock.BTC,
+                                       ToAssetID:        &mock.ETH,
+                                       RatioNumerator:   2000000003,
+                                       RatioDenominator: 100000001,
+                               },
+                               {
+                                       FromAssetID:      &mock.ETH,
+                                       ToAssetID:        &mock.EOS,
+                                       RatioNumerator:   400000000001,
+                                       RatioDenominator: 2000000003,
+                               },
+                               {
+                                       FromAssetID:      &mock.EOS,
+                                       ToAssetID:        &mock.BTC,
+                                       RatioNumerator:   100000002,
+                                       RatioDenominator: 400000000001,
+                               },
+                       },
+                       wantMatched: false,
+               },
+       }
+
+       for i, c := range cases {
+               gotMatched := IsMatched(c.orders)
+               if gotMatched != c.wantMatched {
+                       t.Errorf("#%d(%s) got matched:%v, want matched:%v", i, c.desc, gotMatched, c.wantMatched)
+               }
+       }
+}
diff --git a/application/mov/match/fee_strategy.go b/application/mov/match/fee_strategy.go
new file mode 100644 (file)
index 0000000..f3d1dfc
--- /dev/null
@@ -0,0 +1,108 @@
+package match
+
+import (
+       "math"
+
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/protocol/bc"
+)
+
+var (
+       // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
+       ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
+)
+
+// AllocatedAssets represent reallocated assets after calculating fees
+type AllocatedAssets struct {
+       Receives []*bc.AssetAmount
+       Refunds  []RefundAssets
+       Fees     []*bc.AssetAmount
+}
+
+// RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
+type RefundAssets []*bc.AssetAmount
+
+// FeeStrategy used to indicate how to charge a matching fee
+type FeeStrategy interface {
+       // Allocate will allocate the price differential in matching transaction to the participants and the fee
+       // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
+       // @param priceDiffs price differential of matching transaction
+       // @return reallocated assets after calculating fees
+       Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
+
+       // Validate verify that the fee charged for a matching transaction is correct
+       Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
+}
+
+// DefaultFeeStrategy represent the default fee charge strategy
+type DefaultFeeStrategy struct {}
+
+// NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
+func NewDefaultFeeStrategy() *DefaultFeeStrategy {
+       return &DefaultFeeStrategy{}
+}
+
+// Allocate will allocate the price differential in matching transaction to the participants and the fee
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
+       feeMap := make(map[bc.AssetID]uint64)
+       for _, priceDiff := range priceDiffs {
+               feeMap[*priceDiff.AssetId] = priceDiff.Amount
+       }
+
+       var fees []*bc.AssetAmount
+       refunds := make([]RefundAssets, len(receiveAmounts))
+       receives := make([]*bc.AssetAmount, len(receiveAmounts))
+
+       for i, receiveAmount := range receiveAmounts {
+               amount := receiveAmount.Amount
+               minFeeAmount := d.calcMinFeeAmount(amount)
+               receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: amount - minFeeAmount}
+               feeMap[*receiveAmount.AssetId] += minFeeAmount
+
+               maxFeeAmount := d.calcMaxFeeAmount(amount)
+               feeAmount, reminder := feeMap[*receiveAmount.AssetId], uint64(0)
+               if feeAmount > maxFeeAmount {
+                       reminder = feeAmount - maxFeeAmount
+                       feeAmount = maxFeeAmount
+               }
+
+               fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
+
+               // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
+               averageAmount := reminder / uint64(len(receiveAmounts))
+               if averageAmount == 0 {
+                       averageAmount = 1
+               }
+
+               for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
+                       refundAmount := averageAmount
+                       if j == len(receiveAmounts)-1 {
+                               refundAmount = reminder
+                       }
+                       refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
+                       reminder -= averageAmount
+               }
+       }
+       return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
+}
+
+// Validate verify that the fee charged for a matching transaction is correct
+func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+       for _, receiveAmount := range receiveAmounts {
+               feeAmount := feeAmounts[*receiveAmount.AssetId]
+               maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
+               minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
+               if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
+                       return ErrAmountOfFeeOutOfRange
+               }
+       }
+       return nil
+}
+
+func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
+       return uint64(math.Ceil(float64(amount) / 1000))
+}
+
+func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
+       return uint64(math.Ceil(float64(amount) * 0.05))
+}
diff --git a/application/mov/match/order_book.go b/application/mov/match/order_book.go
new file mode 100644 (file)
index 0000000..426fd7d
--- /dev/null
@@ -0,0 +1,200 @@
+package match
+
+import (
+       "sort"
+       "sync"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/database"
+)
+
+// OrderBook is used to handle the mov orders in memory like stack
+type OrderBook struct {
+       movStore database.MovStore
+       // key of tradePair -> []order
+       dbOrders *sync.Map
+       // key of tradePair -> iterator
+       orderIterators *sync.Map
+
+       // key of tradePair -> []order
+       arrivalAddOrders *sync.Map
+       // key of order -> order
+       arrivalDelOrders *sync.Map
+}
+
+func arrangeArrivalAddOrders(orders []*common.Order) *sync.Map {
+       orderMap := make(map[string][]*common.Order)
+       for _, order := range orders {
+               orderMap[order.TradePair().Key()] = append(orderMap[order.TradePair().Key()], order)
+       }
+
+       arrivalOrderMap := &sync.Map{}
+       for key, orders := range orderMap {
+               sort.Sort(sort.Reverse(common.OrderSlice(orders)))
+               arrivalOrderMap.Store(key, orders)
+       }
+       return arrivalOrderMap
+}
+
+func arrangeArrivalDelOrders(orders []*common.Order) *sync.Map {
+       arrivalDelOrderMap := &sync.Map{}
+       for _, order := range orders {
+               arrivalDelOrderMap.Store(order.Key(), order)
+       }
+       return arrivalDelOrderMap
+}
+
+// NewOrderBook create a new OrderBook object
+func NewOrderBook(movStore database.MovStore, arrivalAddOrders, arrivalDelOrders []*common.Order) *OrderBook {
+       return &OrderBook{
+               movStore:       movStore,
+               dbOrders:       &sync.Map{},
+               orderIterators: &sync.Map{},
+
+               arrivalAddOrders: arrangeArrivalAddOrders(arrivalAddOrders),
+               arrivalDelOrders: arrangeArrivalDelOrders(arrivalDelOrders),
+       }
+}
+
+// AddOrder add the in memory temp order to order table, because temp order is what left for the
+// partial trade order, so the price should be lowest.
+func (o *OrderBook) AddOrder(order *common.Order) {
+       tradePairKey := order.TradePair().Key()
+       orders := o.getArrivalAddOrders(tradePairKey)
+       // use binary search to find the insert position
+       i := sort.Search(len(orders), func(i int) bool { return order.Cmp(orders[i]) > 0 })
+       orders = append(orders, order)
+       copy(orders[i+1:], orders[i:])
+       orders[i] = order
+
+       o.arrivalAddOrders.Store(tradePairKey, orders)
+}
+
+// DelOrder mark the order has been deleted in order book
+func (o *OrderBook) DelOrder(order *common.Order) {
+       o.arrivalDelOrders.Store(order.Key(), order)
+}
+
+// PeekOrder return the next lowest order of given trade pair
+func (o *OrderBook) PeekOrder(tradePair *common.TradePair) *common.Order {
+       if len(o.getDBOrders(tradePair.Key())) == 0 {
+               o.extendDBOrders(tradePair)
+       }
+
+       var nextOrder *common.Order
+       orders := o.getDBOrders(tradePair.Key())
+       if len(orders) != 0 {
+               nextOrder = orders[len(orders)-1]
+       }
+
+       if nextOrder != nil && o.getArrivalDelOrders(nextOrder.Key()) != nil {
+               o.dbOrders.Store(tradePair.Key(), orders[0:len(orders)-1])
+               return o.PeekOrder(tradePair)
+       }
+
+       arrivalOrder := o.peekArrivalOrder(tradePair)
+       if nextOrder == nil || (arrivalOrder != nil && arrivalOrder.Cmp(nextOrder) < 0) {
+               nextOrder = arrivalOrder
+       }
+       return nextOrder
+}
+
+// PeekOrders return the next lowest orders by given array of trade pairs
+func (o *OrderBook) PeekOrders(tradePairs []*common.TradePair) []*common.Order {
+       var orders []*common.Order
+       for _, tradePair := range tradePairs {
+               order := o.PeekOrder(tradePair)
+               if order == nil {
+                       return nil
+               }
+
+               orders = append(orders, order)
+       }
+       return orders
+}
+
+// PopOrder delete the next lowest order of given trade pair
+func (o *OrderBook) PopOrder(tradePair *common.TradePair) {
+       order := o.PeekOrder(tradePair)
+       if order == nil {
+               return
+       }
+
+       orders := o.getDBOrders(tradePair.Key())
+       if len(orders) != 0 && orders[len(orders)-1].Key() == order.Key() {
+               o.dbOrders.Store(tradePair.Key(), orders[0:len(orders)-1])
+               return
+       }
+
+       arrivalOrders := o.getArrivalAddOrders(tradePair.Key())
+       if len(arrivalOrders) != 0 && arrivalOrders[len(arrivalOrders)-1].Key() == order.Key() {
+               o.arrivalAddOrders.Store(tradePair.Key(), arrivalOrders[0:len(arrivalOrders)-1])
+       }
+}
+
+// PopOrders delete the next lowest orders by given trade pairs
+func (o *OrderBook) PopOrders(tradePairs []*common.TradePair) []*common.Order {
+       var orders []*common.Order
+       for _, tradePair := range tradePairs {
+               o.PopOrder(tradePair)
+       }
+       return orders
+}
+
+func (o *OrderBook) getDBOrders(tradePairKey string) []*common.Order {
+       if orders, ok := o.dbOrders.Load(tradePairKey); ok {
+               return orders.([]*common.Order)
+       }
+       return []*common.Order{}
+}
+
+func (o *OrderBook) getArrivalAddOrders(tradePairKey string) []*common.Order {
+       if orders, ok := o.arrivalAddOrders.Load(tradePairKey); ok {
+               return orders.([]*common.Order)
+       }
+       return []*common.Order{}
+}
+
+func (o *OrderBook) getArrivalDelOrders(orderKey string) *common.Order {
+       if order, ok := o.arrivalDelOrders.Load(orderKey); ok {
+               return order.(*common.Order)
+       }
+       return nil
+}
+
+func (o *OrderBook) extendDBOrders(tradePair *common.TradePair) {
+       iterator, ok := o.orderIterators.Load(tradePair.Key())
+       if !ok {
+               iterator = database.NewOrderIterator(o.movStore, tradePair)
+               o.orderIterators.Store(tradePair.Key(), iterator)
+       }
+
+       nextOrders := iterator.(*database.OrderIterator).NextBatch()
+       orders := o.getDBOrders(tradePair.Key())
+       for i := len(nextOrders) - 1; i >= 0; i-- {
+               orders = append(orders, nextOrders[i])
+       }
+       o.dbOrders.Store(tradePair.Key(), orders)
+}
+
+func (o *OrderBook) peekArrivalOrder(tradePair *common.TradePair) *common.Order {
+       orders := o.getArrivalAddOrders(tradePair.Key())
+       delPos := len(orders)
+       for i := delPos - 1; i >= 0; i-- {
+               if o.getArrivalDelOrders(orders[i].Key()) != nil {
+                       delPos = i
+               } else {
+                       break
+               }
+       }
+
+       if delPos < len(orders) {
+               orders = orders[:delPos]
+               o.arrivalAddOrders.Store(tradePair.Key(), orders)
+       }
+
+       if size := len(orders); size > 0 {
+               return orders[size-1]
+       }
+       return nil
+}
diff --git a/application/mov/match/order_book_test.go b/application/mov/match/order_book_test.go
new file mode 100644 (file)
index 0000000..daf330f
--- /dev/null
@@ -0,0 +1,376 @@
+package match
+
+import (
+       "testing"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/database"
+       "github.com/bytom/vapor/application/mov/mock"
+       "github.com/bytom/vapor/testutil"
+)
+
+var (
+       btc2eth = &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+)
+
+func TestOrderBook(t *testing.T) {
+       cases := []struct {
+               desc                 string
+               initMovStore         database.MovStore
+               initArrivalAddOrders []*common.Order
+               initArrivalDelOrders []*common.Order
+               addOrders            []*common.Order
+               popOrders            []*common.TradePair
+               wantPeekedOrders     map[common.TradePair]*common.Order
+       }{
+               {
+                       desc: "no arrival orders, no add order, no pop order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               },
+                       ),
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "no arrival orders, add lower price order, no pop order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       addOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[3],
+                       },
+               },
+               {
+                       desc: "no arrival orders, no add order, pop one order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       popOrders: []*common.TradePair{btc2eth},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[2],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, no add order, no pop order, the arrival add order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[3],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, no add order, no pop order, the db add order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[2]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, no add order, pop one order, after pop the arrival order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       popOrders:            []*common.TradePair{btc2eth},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "has arrival delete orders, no add order, no pop order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                               }),
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[2],
+                       },
+               },
+               {
+                       desc: "has arrival delete orders and arrival add orders, no add order, no pop order, the arrival order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[3], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "has arrival delete orders and arrival add orders, no add order, pop one order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[3], mock.Btc2EthOrders[1],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       popOrders:            []*common.TradePair{btc2eth},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[2],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, but db order is empty",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{}),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "no add orders, and db order is empty",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{}),
+                       initArrivalAddOrders: []*common.Order{},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: nil,
+                       },
+               },
+               {
+                       desc: "has arrival delete orders, no add order, no pop order, need recursive to peek one order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                               }),
+                       initArrivalAddOrders: []*common.Order{},
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3], mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[1],
+                       },
+               },
+       }
+
+       for i, c := range cases {
+               orderBook := NewOrderBook(c.initMovStore, c.initArrivalAddOrders, c.initArrivalDelOrders)
+               for _, order := range c.addOrders {
+                       orderBook.AddOrder(order)
+               }
+
+               for _, tradePair := range c.popOrders {
+                       orderBook.PopOrder(tradePair)
+               }
+
+               for tradePair, wantOrder := range c.wantPeekedOrders {
+                       gotOrder := orderBook.PeekOrder(&tradePair)
+                       if wantOrder == gotOrder && wantOrder == nil {
+                               continue
+                       }
+
+                       if gotOrder.Key() != wantOrder.Key() {
+                               t.Fatalf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, wantOrder)
+                       }
+               }
+       }
+}
+
+func TestPeekArrivalOrder(t *testing.T) {
+       cases := []struct {
+               desc                 string
+               initArrivalAddOrders []*common.Order
+               initArrivalDelOrders []*common.Order
+               peekTradePair        *common.TradePair
+               wantArrivalAddOrders []*common.Order
+               wantOrder            *common.Order
+       }{
+               {
+                       desc:                 "empty peek",
+                       initArrivalAddOrders: []*common.Order{},
+                       initArrivalDelOrders: []*common.Order{},
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{},
+                       wantOrder:            nil,
+               },
+               {
+                       desc:                 "1 element regular peek",
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       initArrivalDelOrders: []*common.Order{},
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       wantOrder:            mock.Btc2EthOrders[0],
+               },
+               {
+                       desc: "4 element regular peek with",
+                       initArrivalAddOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                       },
+                       initArrivalDelOrders: []*common.Order{},
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                       },
+                       wantOrder: mock.Btc2EthOrders[3],
+               },
+               {
+                       desc:                 "1 element peek with 1 unrelated deleted order",
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[1]},
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       wantOrder:            mock.Btc2EthOrders[0],
+               },
+               {
+                       desc:                 "1 element peek with 1 related deleted order",
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{},
+                       wantOrder:            nil,
+               },
+               {
+                       desc: "4 element peek with first 3 deleted order",
+                       initArrivalAddOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                       },
+                       initArrivalDelOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                       },
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[1]},
+                       wantOrder:            mock.Btc2EthOrders[1],
+               },
+               {
+                       desc: "4 element peek with first 1 deleted order",
+                       initArrivalAddOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                       },
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                       },
+                       wantOrder: mock.Btc2EthOrders[0],
+               },
+               {
+                       desc: "4 element peek with first 2th deleted order",
+                       initArrivalAddOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                       },
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       peekTradePair:        btc2eth,
+                       wantArrivalAddOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                       },
+                       wantOrder: mock.Btc2EthOrders[3],
+               },
+       }
+
+       for i, c := range cases {
+               orderBook := NewOrderBook(mock.NewMovStore(nil, nil), c.initArrivalAddOrders, c.initArrivalDelOrders)
+               gotOrder := orderBook.PeekOrder(c.peekTradePair)
+               if !testutil.DeepEqual(gotOrder, c.wantOrder) {
+                       t.Fatalf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, c.wantOrder)
+               }
+
+               wantAddOrders, _ := arrangeArrivalAddOrders(c.wantArrivalAddOrders).Load(c.peekTradePair.Key())
+               gotAddOrders := orderBook.getArrivalAddOrders(c.peekTradePair.Key())
+               if !testutil.DeepEqual(gotAddOrders, wantAddOrders) {
+                       t.Fatalf("#%d(%s): the got arrivalAddOrders(%v) is differnt than want arrivalAddOrders(%v)", i, c.desc, gotAddOrders, wantAddOrders)
+               }
+       }
+}
+
+func TestAddOrder(t *testing.T) {
+       cases := []struct {
+               initOrders []*common.Order
+               wantOrders []*common.Order
+               addOrder   *common.Order
+       }{
+               {
+                       initOrders: []*common.Order{},
+                       addOrder:   &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+                       wantOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+                       },
+               },
+               {
+                       initOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                       },
+                       addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                       wantOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                       },
+               },
+               {
+                       initOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+                       },
+                       addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+                       wantOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                       },
+               },
+               {
+                       initOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+                       },
+                       addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+                       wantOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+                       },
+               },
+               {
+                       initOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+                       },
+                       addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                       wantOrders: []*common.Order{
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+                               &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+                       },
+               },
+       }
+
+       for i, c := range cases {
+               orderBook := NewOrderBook(mock.NewMovStore(nil, nil), c.initOrders, nil)
+               orderBook.AddOrder(c.addOrder)
+               if gotOrders := orderBook.getArrivalAddOrders(btc2eth.Key()); !testutil.DeepEqual(gotOrders, c.wantOrders) {
+                       t.Fatalf("#%d: the gotOrders(%v) is differnt than wantOrders(%v)", i, gotOrders, c.wantOrders)
+               }
+       }
+}
diff --git a/application/mov/match_collector.go b/application/mov/match_collector.go
new file mode 100644 (file)
index 0000000..1081be3
--- /dev/null
@@ -0,0 +1,146 @@
+package mov
+
+import (
+       "runtime"
+       "sync"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/database"
+       "github.com/bytom/vapor/application/mov/match"
+       "github.com/bytom/vapor/protocol/bc/types"
+)
+
+type matchCollector struct {
+       engine            *match.Engine
+       tradePairIterator *database.TradePairIterator
+       gasLeft           int64
+       isTimeout         func() bool
+
+       workerNum     int
+       endWorkCh     chan int
+       tradePairCh   chan *common.TradePair
+       matchResultCh chan *matchTxResult
+       closeCh       chan struct{}
+}
+
+type matchTxResult struct {
+       matchedTx *types.Tx
+       err       error
+}
+
+func newMatchTxCollector(engine *match.Engine, iterator *database.TradePairIterator, gasLeft int64, isTimeout func() bool) *matchCollector {
+       workerNum := runtime.NumCPU()
+       return &matchCollector{
+               engine:            engine,
+               tradePairIterator: iterator,
+               gasLeft:           gasLeft,
+               isTimeout:         isTimeout,
+               workerNum:         workerNum,
+               endWorkCh:         make(chan int, workerNum),
+               tradePairCh:       make(chan *common.TradePair, workerNum),
+               matchResultCh:     make(chan *matchTxResult),
+               closeCh:           make(chan struct{}),
+       }
+}
+
+func (m *matchCollector) result() ([]*types.Tx, error) {
+       var wg sync.WaitGroup
+       for i := 0; i < int(m.workerNum); i++ {
+               wg.Add(1)
+               go m.matchTxWorker(&wg)
+       }
+
+       wg.Add(1)
+       go m.tradePairProducer(&wg)
+
+       matchedTxs, err := m.collect()
+       // wait for all goroutine release
+       wg.Wait()
+       return matchedTxs, err
+}
+
+func (m *matchCollector) collect() ([]*types.Tx, error) {
+       defer close(m.closeCh)
+
+       var matchedTxs []*types.Tx
+       for completed := 0; !m.isTimeout(); {
+               select {
+               case data := <-m.matchResultCh:
+                       if data.err != nil {
+                               return nil, data.err
+                       }
+
+                       gasUsed := calcMatchedTxGasUsed(data.matchedTx)
+                       if m.gasLeft -= gasUsed; m.gasLeft >= 0 {
+                               matchedTxs = append(matchedTxs, data.matchedTx)
+                       } else {
+                               return matchedTxs, nil
+                       }
+               case <-m.endWorkCh:
+                       if completed++; completed == m.workerNum {
+                               return matchedTxs, nil
+                       }
+               }
+       }
+       return matchedTxs, nil
+}
+
+func (m *matchCollector) tradePairProducer(wg *sync.WaitGroup) {
+       defer func() {
+               close(m.tradePairCh)
+               wg.Done()
+       }()
+
+       tradePairMap := make(map[string]bool)
+
+       for m.tradePairIterator.HasNext() {
+               tradePair := m.tradePairIterator.Next()
+               if tradePairMap[tradePair.Key()] {
+                       continue
+               }
+
+               tradePairMap[tradePair.Key()] = true
+               tradePairMap[tradePair.Reverse().Key()] = true
+
+               select {
+               case <-m.closeCh:
+                       return
+               case m.tradePairCh <- tradePair:
+               }
+       }
+}
+
+func (m *matchCollector) matchTxWorker(wg *sync.WaitGroup) {
+       defer func() {
+               m.endWorkCh <- 1
+               wg.Done()
+       }()
+
+       for {
+               select {
+               case <-m.closeCh:
+                       return
+               case tradePair := <-m.tradePairCh:
+                       // end worker due to all trade pair has been matched
+                       if tradePair == nil {
+                               return
+                       }
+
+                       for m.engine.HasMatchedTx(tradePair, tradePair.Reverse()) {
+                               matchedTx, err := m.engine.NextMatchedTx(tradePair, tradePair.Reverse())
+                               select {
+                               case <-m.closeCh:
+                                       return
+                               case m.matchResultCh <- &matchTxResult{matchedTx: matchedTx, err: err}:
+                                       if err != nil {
+                                               return
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+func calcMatchedTxGasUsed(tx *types.Tx) int64 {
+       return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
+}
diff --git a/application/mov/mock/mock.go b/application/mov/mock/mock.go
new file mode 100644 (file)
index 0000000..46f0bcc
--- /dev/null
@@ -0,0 +1,491 @@
+package mock
+
+import (
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/protocol/vm"
+       "github.com/bytom/vapor/protocol/vm/vmutil"
+       "github.com/bytom/vapor/testutil"
+)
+
+var (
+       BTC           = bc.NewAssetID([32]byte{1})
+       ETH           = bc.NewAssetID([32]byte{2})
+       EOS           = bc.NewAssetID([32]byte{3})
+       ETC           = bc.NewAssetID([32]byte{4})
+       RewardProgram = []byte{0x58}
+
+       Btc2EthOrders = []*common.Order{
+               {
+                       FromAssetID:      &BTC,
+                       ToAssetID:        &ETH,
+                       RatioNumerator:   50,
+                       RatioDenominator: 1,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("37b8edf656e45a7addf47f5626e114a8c394d918a36f61b5a2905675a09b40ae")),
+                               SourcePos:      0,
+                               Amount:         10,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 50, 1),
+                       },
+               },
+               {
+                       FromAssetID:      &BTC,
+                       ToAssetID:        &ETH,
+                       RatioNumerator:   53,
+                       RatioDenominator: 1,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("3ec2bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+                               SourcePos:      0,
+                               Amount:         20,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+                       },
+               },
+               {
+                       FromAssetID:      &BTC,
+                       ToAssetID:        &ETH,
+                       RatioNumerator:   52,
+                       RatioDenominator: 1,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("1232bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+                               SourcePos:      0,
+                               Amount:         15,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+                       },
+               },
+               {
+                       FromAssetID:      &BTC,
+                       ToAssetID:        &ETH,
+                       RatioNumerator:   49,
+                       RatioDenominator: 1,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("7872bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+                               SourcePos:      0,
+                               Amount:         17,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+                       },
+               },
+       }
+
+       Eth2BtcOrders = []*common.Order{
+               {
+                       FromAssetID:      &ETH,
+                       ToAssetID:        &BTC,
+                       RatioNumerator:   1,
+                       RatioDenominator: 51,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("fba43ff5155209cb1769e2ec0e1d4a33accf899c740865edfc6d1de39b873b29")),
+                               SourcePos:      0,
+                               Amount:         510,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253"), 1, 51.0),
+                       },
+               },
+               {
+                       FromAssetID:      &ETH,
+                       ToAssetID:        &BTC,
+                       RatioNumerator:   1,
+                       RatioDenominator: 52,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("05f24bb847db823075d81786aa270748e02602199cd009c0284f928503846a5a")),
+                               SourcePos:      0,
+                               Amount:         416,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254"), 1, 52.0),
+                       },
+               },
+               {
+                       FromAssetID:      &ETH,
+                       ToAssetID:        &BTC,
+                       RatioNumerator:   1,
+                       RatioDenominator: 54,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d201")),
+                               SourcePos:      0,
+                               Amount:         810,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
+                       },
+               },
+               {
+                       FromAssetID:      &ETH,
+                       ToAssetID:        &BTC,
+                       RatioNumerator:   1,
+                       RatioDenominator: 150,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("82752cda63c877a8529d7a7461da6096673e45b3e0b019ce44aa18687ad20445")),
+                               SourcePos:      0,
+                               Amount:         600,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"), 1, 150.0),
+                       },
+               },
+       }
+
+       Eos2EtcOrders = []*common.Order{
+               {
+                       FromAssetID:      &EOS,
+                       ToAssetID:        &ETC,
+                       RatioNumerator:   1,
+                       RatioDenominator: 2,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d202")),
+                               SourcePos:      0,
+                               Amount:         100,
+                               ControlProgram: MustCreateP2WMCProgram(ETC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 2.0),
+                       },
+               },
+       }
+
+       Etc2EosOrders = []*common.Order{
+               {
+                       FromAssetID:      &ETC,
+                       ToAssetID:        &EOS,
+                       RatioNumerator:   2,
+                       RatioDenominator: 1,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d203")),
+                               SourcePos:      0,
+                               Amount:         50,
+                               ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3"), 2, 1.0),
+                       },
+               },
+       }
+
+       Eth2EosOrders = []*common.Order{
+               {
+                       FromAssetID:      &ETH,
+                       ToAssetID:        &EOS,
+                       RatioNumerator:   2,
+                       RatioDenominator: 1,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("c1502d03946e4ea92abdb33f51638b181839bd0d8767acc2ee5c665b659c4b13")),
+                               SourcePos:      0,
+                               Amount:         500,
+                               ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86"), 2, 1.0),
+                       },
+               },
+       }
+
+       Eos2BtcOrders = []*common.Order{
+               {
+                       FromAssetID:      &EOS,
+                       ToAssetID:        &BTC,
+                       RatioNumerator:   1,
+                       RatioDenominator: 100,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("27cf8a0877dc858968cc06396fe6aa9e02d15f3e44c862fe29fa5fd50497cf20")),
+                               SourcePos:      0,
+                               Amount:         1000,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc"), 1, 100.0),
+                       },
+               },
+       }
+
+       Btc2EthCancelTxs = []*types.Tx{
+               // Btc2EthOrders[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram)},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"))},
+               }),
+
+               // output 2 of MatchedTxs[2]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *MustNewOrderFromOutput(MatchedTxs[2], 2).Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram)},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"))},
+               }),
+       }
+
+       Btc2EthMakerTxs = []*types.Tx{
+               // Btc2EthOrders[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.ControlProgram)},
+               }),
+               // Btc2EthOrders[1]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[1].Utxo.SourceID, *Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.ControlProgram)},
+               }),
+               // Btc2EthOrders[2]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[2].Utxo.SourceID, *Btc2EthOrders[2].FromAssetID, Btc2EthOrders[2].Utxo.Amount, Btc2EthOrders[2].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[2].FromAssetID, Btc2EthOrders[2].Utxo.Amount, Btc2EthOrders[2].Utxo.ControlProgram)},
+               }),
+               // Btc2EthOrders[3]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[3].Utxo.SourceID, *Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.ControlProgram)},
+               }),
+       }
+
+       Eth2BtcMakerTxs = []*types.Tx{
+               // Eth2Btc[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[0].Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.ControlProgram)},
+               }),
+               // Eth2Btc[1]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.ControlProgram)},
+               }),
+               // Eth2Btc[2]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.ControlProgram)},
+               }),
+       }
+
+       Eos2EtcMakerTxs = []*types.Tx{
+               // Eos2Etc[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eos2EtcOrders[0].Utxo.SourceID, *Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.ControlProgram)},
+               }),
+       }
+
+       Etc2EosMakerTxs = []*types.Tx{
+               // Etc2Eos[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Etc2EosOrders[0].Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.ControlProgram)},
+               }),
+       }
+
+       Eth2EosMakerTxs = []*types.Tx{
+               // Eth2Eos[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eth2EosOrders[0].Utxo.SourceID, *Eth2EosOrders[0].FromAssetID, Eth2EosOrders[0].Utxo.Amount, Eth2EosOrders[0].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2EosOrders[0].FromAssetID, Eth2EosOrders[0].Utxo.Amount, Eth2EosOrders[0].Utxo.ControlProgram)},
+               }),
+       }
+
+       MatchedTxs = []*types.Tx{
+               // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[1]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(416), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, Eth2BtcOrders[1].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               // re-order
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // full matched transaction from Btc2EthOrders[0], Eth2BtcOrders[0]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[0].Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.SourcePos, Eth2BtcOrders[0].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 11, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[2]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, Eth2BtcOrders[2].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                               // re-order
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
+                               // fee
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 1, RewardProgram),
+                               // refund
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                       },
+               }),
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[1].Utxo.SourceID, *Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.SourcePos, Btc2EthOrders[1].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("39bdb7058a0c31fb740af8e3c382bf608efff1b041cd4dd461332722ad24552a"), *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 269, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
+                               // re-order
+                               types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, 15, Btc2EthOrders[1].Utxo.ControlProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 4, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // partial matched transaction from Btc2EthMakerTxs[0], Eth2BtcMakerTxs[1]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Btc2EthMakerTxs[0], 0).Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, 0, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[1], 0).Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, 0, Eth2BtcOrders[1].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               // re-order
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // full matched transaction from Eos2EtcMakerTxs[0] Etc2EosMakerTxs[0]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eos2EtcMakerTxs[0], 0).Utxo.SourceID, *Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.SourcePos, Eos2EtcOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Etc2EosMakerTxs[0], 0).Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, Etc2EosOrders[0].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                               types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
+                               // fee
+                               types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // cycle matched from Btc2Eth Eth2Eos Eos2Btc
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2EosOrders[0].Utxo.SourceID, *Eth2EosOrders[0].FromAssetID, Eth2EosOrders[0].Utxo.Amount, Eth2EosOrders[0].Utxo.SourcePos, Eth2EosOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eos2BtcOrders[0].Utxo.SourceID, *Eos2BtcOrders[0].FromAssetID, Eos2BtcOrders[0].Utxo.Amount, Eos2BtcOrders[0].Utxo.SourcePos, Eos2BtcOrders[0].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(ETH, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(EOS, 999, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
+                               types.NewIntraChainOutput(BTC, 9, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
+                               // fee
+                               types.NewIntraChainOutput(ETH, 1, RewardProgram),
+                               types.NewIntraChainOutput(EOS, 1, RewardProgram),
+                               types.NewIntraChainOutput(BTC, 1, RewardProgram),
+                       },
+               }),
+
+               // partial matched transaction from MatchedTxs[4], Eth2BtcMakerTxs[0]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, testutil.MustDecodeHash("ed810e1672c3b9de27a1db23e017e6b9cc23334b6e3dbd25dfe8857e289b7f06"), *Btc2EthOrders[0].FromAssetID, 2, 1, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[0], 0).Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, 0, Eth2BtcOrders[0].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+                               // re-order
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, 404, Eth2BtcOrders[0].Utxo.ControlProgram),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // partial matched transaction from Btc2EthOrders[3], Eth2BtcOrders[2]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(810), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[3].Utxo.SourceID, *Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.SourcePos, Btc2EthOrders[3].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, Eth2BtcOrders[2].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[3].ToAssetID, 809, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
+                               // re-order
+                               types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 1, Btc2EthOrders[3].Utxo.ControlProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 14, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 2, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // full matched transaction from Eos2EtcOrders[0] Etc2EosOrders[0]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Eos2EtcOrders[0].Utxo.SourceID, *Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.SourcePos, Eos2EtcOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Etc2EosOrders[0].Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, Etc2EosOrders[0].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                               types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
+                               // fee
+                               types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // full matched transaction from Btc2EthOrders[0], Eth2BtcMakerTxs[0]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[0], 0).Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, 0, Eth2BtcOrders[0].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, RewardProgram),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+                       },
+               }),
+
+               // full matched transaction from Btc2EthOrders[0] Eth2BtcOrders[3]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[3].Utxo.SourceID, *Eth2BtcOrders[3].FromAssetID, Eth2BtcOrders[3].Utxo.Amount, Eth2BtcOrders[3].Utxo.SourcePos, Eth2BtcOrders[3].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+                               // fee
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 25, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 1, RewardProgram),
+                               // refund
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+                       },
+               }),
+       }
+)
+
+func MustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioNumerator, ratioDenominator int64) []byte {
+       contractArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   requestAsset,
+               RatioNumerator:   ratioNumerator,
+               RatioDenominator: ratioDenominator,
+               SellerProgram:    sellerProgram,
+               SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
+       }
+       program, err := vmutil.P2WMCProgram(contractArgs)
+       if err != nil {
+               panic(err)
+       }
+       return program
+}
+
+func MustNewOrderFromOutput(tx *types.Tx, outputIndex int) *common.Order {
+       order, err := common.NewOrderFromOutput(tx, outputIndex)
+       if err != nil {
+               panic(err)
+       }
+
+       return order
+}
+
+func hashPtr(hash bc.Hash) *bc.Hash {
+       return &hash
+}
diff --git a/application/mov/mock/mock_mov_store.go b/application/mov/mock/mock_mov_store.go
new file mode 100644 (file)
index 0000000..34a1bd0
--- /dev/null
@@ -0,0 +1,108 @@
+package mock
+
+import (
+       "sort"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+)
+
+type MovStore struct {
+       tradePairs []*common.TradePair
+       orderMap   map[string][]*common.Order
+       dbState    *common.MovDatabaseState
+}
+
+func NewMovStore(tradePairs []*common.TradePair, orders []*common.Order) *MovStore {
+       orderMap := make(map[string][]*common.Order)
+       for _, order := range orders {
+               orderMap[order.TradePair().Key()] = append(orderMap[order.TradePair().Key()], order)
+       }
+
+       for _, orders := range orderMap {
+               sort.Sort(common.OrderSlice(orders))
+       }
+       return &MovStore{
+               tradePairs: tradePairs,
+               orderMap:   orderMap,
+       }
+}
+
+func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
+       return m.dbState, nil
+}
+
+func (m *MovStore) InitDBState(height uint64, hash *bc.Hash) error {
+       return nil
+}
+
+func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
+       tradePair := &common.TradePair{FromAssetID: orderAfter.FromAssetID, ToAssetID: orderAfter.ToAssetID}
+       orders := m.orderMap[tradePair.Key()]
+       begin := len(orders)
+       if orderAfter.Rate() == 0 {
+               begin = 0
+       } else {
+               for i, order := range orders {
+                       if order.Rate() == orderAfter.Rate() {
+                               begin = i + 1
+                               break
+                       }
+               }
+       }
+       var result []*common.Order
+       for i := begin; i < len(orders) && len(result) < 3; i++ {
+               result = append(result, orders[i])
+       }
+       return result, nil
+}
+
+func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
+       begin := len(m.tradePairs)
+       if fromAssetIDAfter == nil || toAssetIDAfter == nil {
+               begin = 0
+       } else {
+               for i, tradePair := range m.tradePairs {
+                       if *tradePair.FromAssetID == *fromAssetIDAfter && *tradePair.ToAssetID == *toAssetIDAfter {
+                               begin = i + 1
+                               break
+                       }
+               }
+       }
+       var result []*common.TradePair
+       for i := begin; i < len(m.tradePairs) && len(result) < 3; i++ {
+               result = append(result, m.tradePairs[i])
+       }
+       return result, nil
+}
+
+func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
+       for _, order := range addOrders {
+               tradePair := &common.TradePair{FromAssetID: order.FromAssetID, ToAssetID: order.ToAssetID}
+               m.orderMap[tradePair.Key()] = append(m.orderMap[tradePair.Key()], order)
+       }
+       for _, delOrder := range delOrders {
+               tradePair := &common.TradePair{FromAssetID: delOrder.FromAssetID, ToAssetID: delOrder.ToAssetID}
+               orders := m.orderMap[tradePair.Key()]
+               for i, order := range orders {
+                       if delOrder.Key() == order.Key() {
+                               m.orderMap[tradePair.Key()] = append(orders[0:i], orders[i+1:]...)
+                       }
+               }
+       }
+       for _, orders := range m.orderMap {
+               sort.Sort(common.OrderSlice(orders))
+       }
+
+       if blockHeader.Height == m.dbState.Height {
+               m.dbState = &common.MovDatabaseState{Height: blockHeader.Height - 1, Hash: &blockHeader.PreviousBlockHash}
+       } else if blockHeader.Height == m.dbState.Height+1 {
+               blockHash := blockHeader.Hash()
+               m.dbState = &common.MovDatabaseState{Height: blockHeader.Height, Hash: &blockHash}
+       } else {
+               return errors.New("error block header")
+       }
+       return nil
+}
diff --git a/application/mov/mov_core.go b/application/mov/mov_core.go
new file mode 100644 (file)
index 0000000..a8386c2
--- /dev/null
@@ -0,0 +1,513 @@
+package mov
+
+import (
+       "encoding/hex"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/contract"
+       "github.com/bytom/vapor/application/mov/database"
+       "github.com/bytom/vapor/application/mov/match"
+       "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/consensus/segwit"
+       dbm "github.com/bytom/vapor/database/leveldb"
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+)
+
+var (
+       errInvalidTradePairs            = errors.New("The trade pairs in the tx input is invalid")
+       errStatusFailMustFalse          = errors.New("status fail of transaction does not allow to be true")
+       errInputProgramMustP2WMCScript  = errors.New("input program of trade tx must p2wmc script")
+       errExistCancelOrderInMatchedTx  = errors.New("can't exist cancel order in the matched transaction")
+       errExistTradeInCancelOrderTx    = errors.New("can't exist trade in the cancel order transaction")
+       errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
+       errRatioOfTradeLessThanZero     = errors.New("ratio arguments must greater than zero")
+       errSpendOutputIDIsIncorrect     = errors.New("spend output id of matched tx is not equals to actual matched tx")
+       errRequestAmountMath            = errors.New("request amount of order less than one or big than max of int64")
+       errNotMatchedOrder              = errors.New("order in matched tx is not matched")
+       errNotConfiguredRewardProgram   = errors.New("reward program is not configured properly")
+       errRewardProgramIsWrong         = errors.New("the reward program is not correct")
+)
+
+// Core represent the core logic of the match module, which include generate match transactions before packing the block,
+// verify the match transaction in block is correct, and update the order table according to the transaction.
+type Core struct {
+       movStore         database.MovStore
+       startBlockHeight uint64
+}
+
+// NewCore return a instance of Core by path of mov db
+func NewCore(dbBackend, dbDir string, startBlockHeight uint64) *Core {
+       movDB := dbm.NewDB("mov", dbBackend, dbDir)
+       return &Core{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
+}
+
+// NewCoreWithDB return a instance of Core by movStore
+func NewCoreWithDB(store *database.LevelDBMovStore, startBlockHeight uint64) *Core {
+       return &Core{movStore: store, startBlockHeight: startBlockHeight}
+}
+
+// ApplyBlock parse pending order and cancel from the the transactions of block
+// and add pending order to the dex db, remove cancel order from dex db.
+func (m *Core) ApplyBlock(block *types.Block) error {
+       if block.Height < m.startBlockHeight {
+               return nil
+       }
+
+       if block.Height == m.startBlockHeight {
+               blockHash := block.Hash()
+               return m.movStore.InitDBState(block.Height, &blockHash)
+       }
+
+       if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
+               return err
+       }
+
+       addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
+       if err != nil {
+               return err
+       }
+
+       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+// BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
+func (m *Core) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
+       if blockHeight <= m.startBlockHeight {
+               return nil, nil
+       }
+
+       orderBook, err := buildOrderBook(m.movStore, txs)
+       if err != nil {
+               return nil, err
+       }
+
+       program, _ := getRewardProgram(blockHeight)
+       rewardProgram, err := hex.DecodeString(program)
+       if err != nil {
+               return nil, errNotConfiguredRewardProgram
+       }
+
+       matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
+       tradePairIterator := database.NewTradePairIterator(m.movStore)
+       matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
+       return matchCollector.result()
+}
+
+// ChainStatus return the current block height and block hash in dex core
+func (m *Core) ChainStatus() (uint64, *bc.Hash, error) {
+       state, err := m.movStore.GetMovDatabaseState()
+       if err != nil {
+               return 0, nil, err
+       }
+
+       return state.Height, state.Hash, nil
+}
+
+// DetachBlock parse pending order and cancel from the the transactions of block
+// and add cancel order to the dex db, remove pending order from dex db.
+func (m *Core) DetachBlock(block *types.Block) error {
+       if block.Height < m.startBlockHeight {
+               return nil
+       }
+
+       deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
+       if err != nil {
+               return err
+       }
+
+       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+// IsDust block the transaction that are not generated by the match engine
+func (m *Core) IsDust(tx *types.Tx) bool {
+       for _, input := range tx.Inputs {
+               if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
+                       return true
+               }
+       }
+       return false
+}
+
+// Name return the name of current module
+func (m *Core) Name() string {
+       return "MOV"
+}
+
+// StartHeight return the start block height of current module
+func (m *Core) StartHeight() uint64 {
+       return m.startBlockHeight
+}
+
+// ValidateBlock no need to verify the block header, because the first module has been verified.
+// just need to verify the transactions in the block.
+func (m *Core) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
+       for i, tx := range block.Transactions {
+               if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// ValidateTx validate one transaction.
+func (m *Core) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
+       if blockHeight <= m.startBlockHeight {
+               return nil
+       }
+
+       if verifyResult.StatusFail {
+               return errStatusFailMustFalse
+       }
+
+       if common.IsMatchedTx(tx) {
+               if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil {
+                       return err
+               }
+       } else if common.IsCancelOrderTx(tx) {
+               if err := validateCancelOrderTx(tx, verifyResult); err != nil {
+                       return err
+               }
+       }
+
+       for _, output := range tx.Outputs {
+               if !segwit.IsP2WMCScript(output.ControlProgram()) {
+                       continue
+               }
+
+               if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// matchedTxFee is object to record the mov tx's fee information
+type matchedTxFee struct {
+       rewardProgram []byte
+       amount        uint64
+}
+
+// calcFeeAmount return the amount of fee in the matching transaction
+func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
+       assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
+       dealProgMaps := make(map[string]bool)
+
+       for _, input := range matchedTx.Inputs {
+               assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
+               contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
+               if err != nil {
+                       return nil, err
+               }
+
+               dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
+       }
+
+       for _, output := range matchedTx.Outputs {
+               assetAmount := output.AssetAmount()
+               if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
+                       assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
+                       if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
+                               delete(assetFeeMap, *assetAmount.AssetId)
+                       }
+               } else if assetFeeMap[*assetAmount.AssetId].rewardProgram == nil {
+                       assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
+               } else {
+                       return nil, errors.Wrap(errRewardProgramIsWrong, "double reward program")
+               }
+       }
+       return assetFeeMap, nil
+}
+
+func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+       for _, input := range tx.Inputs {
+               if !segwit.IsP2WMCScript(input.ControlProgram()) {
+                       return errInputProgramMustP2WMCScript
+               }
+
+               if contract.IsTradeClauseSelector(input) {
+                       return errExistTradeInCancelOrderTx
+               }
+       }
+       return nil
+}
+
+func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
+       contractArgs, err := segwit.DecodeP2WMCProgram(program)
+       if err != nil {
+               return err
+       }
+
+       if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
+               return errInvalidTradePairs
+       }
+
+       if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
+               return errRatioOfTradeLessThanZero
+       }
+
+       if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
+               return errRequestAmountMath
+       }
+       return nil
+}
+
+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
+       fromAssetIDMap := make(map[string]bool)
+       toAssetIDMap := make(map[string]bool)
+       for i, input := range tx.Inputs {
+               if !segwit.IsP2WMCScript(input.ControlProgram()) {
+                       return errInputProgramMustP2WMCScript
+               }
+
+               if contract.IsCancelClauseSelector(input) {
+                       return errExistCancelOrderInMatchedTx
+               }
+
+               order, err := common.NewOrderFromInput(tx, i)
+               if err != nil {
+                       return err
+               }
+
+               fromAssetIDMap[order.FromAssetID.String()] = true
+               toAssetIDMap[order.ToAssetID.String()] = true
+       }
+
+       if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+               return errAssetIDMustUniqueInMatchedTx
+       }
+
+       return validateMatchedTxFee(tx, blockHeight)
+}
+
+func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
+       matchedTxFees, err := calcFeeAmount(tx)
+       if err != nil {
+               return err
+       }
+
+       for _, fee := range matchedTxFees {
+               if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
+                       return err
+               }
+       }
+
+       orders, err := getDeleteOrdersFromTx(tx)
+       if err != nil {
+               return err
+       }
+
+       receivedAmount, _ := match.CalcReceivedAmount(orders)
+       feeAmounts := make(map[bc.AssetID]uint64)
+       for assetID, fee := range matchedTxFees {
+               feeAmounts[assetID] = fee.amount
+       }
+
+       feeStrategy := match.NewDefaultFeeStrategy()
+       return feeStrategy.Validate(receivedAmount, feeAmounts)
+}
+
+func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
+       orderBook := match.NewOrderBook(m.movStore, nil, nil)
+       for _, tx := range txs {
+               if common.IsMatchedTx(tx) {
+                       tradePairs, err := getTradePairsFromMatchedTx(tx)
+                       if err != nil {
+                               return err
+                       }
+
+                       orders := orderBook.PeekOrders(tradePairs)
+                       if err := validateSpendOrders(tx, orders); err != nil {
+                               return err
+                       }
+
+                       orderBook.PopOrders(tradePairs)
+               } else if common.IsCancelOrderTx(tx) {
+                       orders, err := getDeleteOrdersFromTx(tx)
+                       if err != nil {
+                               return err
+                       }
+
+                       for _, order := range orders {
+                               orderBook.DelOrder(order)
+                       }
+               }
+
+               addOrders, err := getAddOrdersFromTx(tx)
+               if err != nil {
+                       return err
+               }
+
+               for _, order := range addOrders {
+                       orderBook.AddOrder(order)
+               }
+       }
+       return nil
+}
+
+func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
+       if len(tx.Inputs) != len(orders) {
+               return errNotMatchedOrder
+       }
+
+       spendOutputIDs := make(map[string]bool)
+       for _, input := range tx.Inputs {
+               spendOutputID, err := input.SpentOutputID()
+               if err != nil {
+                       return err
+               }
+
+               spendOutputIDs[spendOutputID.String()] = true
+       }
+
+       for _, order := range orders {
+               outputID := order.UTXOHash().String()
+               if _, ok := spendOutputIDs[outputID]; !ok {
+                       return errSpendOutputIDIsIncorrect
+               }
+       }
+       return nil
+}
+
+func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
+       deleteOrderMap := make(map[string]*common.Order)
+       addOrderMap := make(map[string]*common.Order)
+       for _, tx := range txs {
+               addOrders, err := getAddOrdersFromTx(tx)
+               if err != nil {
+                       return nil, nil, err
+               }
+
+               for _, order := range addOrders {
+                       addOrderMap[order.Key()] = order
+               }
+
+               deleteOrders, err := getDeleteOrdersFromTx(tx)
+               if err != nil {
+                       return nil, nil, err
+               }
+
+               for _, order := range deleteOrders {
+                       deleteOrderMap[order.Key()] = order
+               }
+       }
+
+       addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
+       return addOrders, deleteOrders, nil
+}
+
+func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
+       var arrivalAddOrders, arrivalDelOrders []*common.Order
+       for _, tx := range txs {
+               addOrders, err := getAddOrdersFromTx(tx)
+               if err != nil {
+                       return nil, err
+               }
+
+               delOrders, err := getDeleteOrdersFromTx(tx)
+               if err != nil {
+                       return nil, err
+               }
+
+               arrivalAddOrders = append(arrivalAddOrders, addOrders...)
+               arrivalDelOrders = append(arrivalDelOrders, delOrders...)
+       }
+
+       return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
+}
+
+func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+       var orders []*common.Order
+       for i, output := range tx.Outputs {
+               if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
+                       continue
+               }
+
+               if output.AssetAmount().Amount == 0 {
+                       continue
+               }
+
+               order, err := common.NewOrderFromOutput(tx, i)
+               if err != nil {
+                       return nil, err
+               }
+
+               orders = append(orders, order)
+       }
+       return orders, nil
+}
+
+func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+       var orders []*common.Order
+       for i, input := range tx.Inputs {
+               if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
+                       continue
+               }
+
+               order, err := common.NewOrderFromInput(tx, i)
+               if err != nil {
+                       return nil, err
+               }
+
+               orders = append(orders, order)
+       }
+       return orders, nil
+}
+
+func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+       var tradePairs []*common.TradePair
+       for _, tx := range tx.Inputs {
+               contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
+               if err != nil {
+                       return nil, err
+               }
+
+               tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
+       }
+       return tradePairs, nil
+}
+
+func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
+       var deleteOrders, addOrders []*common.Order
+       for orderID, order := range addOrderMap {
+               if _, ok := deleteOrderMap[orderID]; ok {
+                       delete(deleteOrderMap, orderID)
+                       continue
+               }
+               addOrders = append(addOrders, order)
+       }
+
+       for _, order := range deleteOrderMap {
+               deleteOrders = append(deleteOrders, order)
+       }
+       return addOrders, deleteOrders
+}
+
+// getRewardProgram return the reward program by specified block height
+// if no reward program configured, then will return empty string
+// if reward program of 0-100 height is configured, but the specified height is 200, then will return  0-100's reward program
+// the second return value represent whether to find exactly
+func getRewardProgram(height uint64) (string, bool) {
+       rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
+       if len(rewardPrograms) == 0 {
+               return "", false
+       }
+
+       var program string
+       for _, rewardProgram := range rewardPrograms {
+               program = rewardProgram.Program
+               if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
+                       return program, true
+               }
+       }
+       return program, false
+}
+
+func validateRewardProgram(height uint64, program string) error {
+       rewardProgram, exact := getRewardProgram(height)
+       if exact && rewardProgram != program {
+               return errRewardProgramIsWrong
+       }
+       return nil
+}
diff --git a/application/mov/mov_core_test.go b/application/mov/mov_core_test.go
new file mode 100644 (file)
index 0000000..9b4e037
--- /dev/null
@@ -0,0 +1,846 @@
+package mov
+
+import (
+       "encoding/hex"
+       "math"
+       "os"
+       "testing"
+
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/database"
+       "github.com/bytom/vapor/application/mov/match"
+       "github.com/bytom/vapor/application/mov/mock"
+       "github.com/bytom/vapor/consensus"
+       dbm "github.com/bytom/vapor/database/leveldb"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/protocol/vm"
+       "github.com/bytom/vapor/testutil"
+)
+
+var initBlockHeader = &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
+
+func TestApplyBlock(t *testing.T) {
+       cases := []struct {
+               desc        string
+               block       *types.Block
+               blockFunc   testFun
+               initOrders  []*common.Order
+               wantOrders  []*common.Order
+               wantDBState *common.MovDatabaseState
+               wantError   error
+       }{
+               {
+                       desc: "apply block has pending order transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has two different trade pairs & different trade pair won't affect each order",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Eos2EtcMakerTxs[0],
+                                       mock.Eth2EosMakerTxs[0],
+                               },
+                       },
+                       blockFunc: applyBlock,
+                       wantOrders: []*common.Order{
+                               mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+                       },
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has full matched transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[1],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[1]},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[0],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has two partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[2], mock.MatchedTxs[3],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has partial matched transaction by pending orders from tx pool",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.Eth2BtcMakerTxs[1],
+                                       mock.MatchedTxs[4],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block which node packed maker tx and match transaction in random orde",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.Eos2EtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.Eth2BtcMakerTxs[1],
+                                       mock.MatchedTxs[4],
+                                       mock.Eth2EosMakerTxs[0],
+                                       mock.Etc2EosMakerTxs[0],
+                                       mock.MatchedTxs[5],
+                               },
+                       },
+                       blockFunc:  applyBlock,
+                       initOrders: []*common.Order{},
+                       wantOrders: []*common.Order{
+                               mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
+                               mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+                       },
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has partial matched transaction chain",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.Eth2BtcMakerTxs[1],
+                                       mock.MatchedTxs[4],
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.MatchedTxs[7],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[7], 2)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "detach block has pending order transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
+                       wantOrders:  []*common.Order{},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block has two different trade pairs & different trade pair won't affect each order",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Eos2EtcMakerTxs[0],
+                                       mock.Eth2EosMakerTxs[0],
+                               },
+                       },
+                       blockFunc: detachBlock,
+                       initOrders: []*common.Order{
+                               mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+                       },
+                       wantOrders:  []*common.Order{},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block has full matched transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[1],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[1]},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block has partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[0],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block has two partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[2], mock.MatchedTxs[3],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block which node packed maker tx and match transaction in random orde",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.Eos2EtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.MatchedTxs[4],
+                                       mock.Eth2EosMakerTxs[0],
+                                       mock.Eth2BtcMakerTxs[1],
+                                       mock.MatchedTxs[5],
+                                       mock.Etc2EosMakerTxs[0],
+                               },
+                       },
+                       blockFunc: detachBlock,
+                       initOrders: []*common.Order{
+                               mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
+                               mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+                       },
+                       wantOrders:  []*common.Order{},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+       }
+
+       defer os.RemoveAll("temp")
+       for i, c := range cases {
+               testDB := dbm.NewDB("testdb", "leveldb", "temp")
+               store := database.NewLevelDBMovStore(testDB)
+               if err := store.InitDBState(0, &bc.Hash{}); err != nil {
+                       t.Fatal(err)
+               }
+
+               if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
+                       t.Fatal(err)
+               }
+
+               movCore := &Core{movStore: store}
+               if err := c.blockFunc(movCore, c.block); err != c.wantError {
+                       t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
+               }
+
+               gotOrders := queryAllOrders(store)
+               if !ordersEquals(c.wantOrders, gotOrders) {
+                       t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
+               }
+
+               dbState, err := store.GetMovDatabaseState()
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               if !testutil.DeepEqual(c.wantDBState, dbState) {
+                       t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
+               }
+
+               testDB.Close()
+               os.RemoveAll("temp")
+       }
+}
+
+func TestValidateBlock(t *testing.T) {
+       consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
+               {
+                       BeginBlock: 0,
+                       EndBlock:   100,
+                       Program:    hex.EncodeToString(mock.RewardProgram),
+               },
+       }
+
+       cases := []struct {
+               desc          string
+               block         *types.Block
+               verifyResults []*bc.TxVerifyResult
+               wantError     error
+       }{
+               {
+                       desc: "block only has maker tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
+                       wantError:     nil,
+               },
+               {
+                       desc: "block only has matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[0],
+                                       mock.MatchedTxs[1],
+                                       mock.MatchedTxs[2],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
+                       wantError:     nil,
+               },
+               {
+                       desc: "block has maker tx and matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.MatchedTxs[0],
+                                       mock.MatchedTxs[1],
+                                       mock.MatchedTxs[2],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
+                       wantError:     nil,
+               },
+               {
+                       desc: "status fail of maker tx is true",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+                       wantError:     errStatusFailMustFalse,
+               },
+               {
+                       desc: "status fail of matched tx is true",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[1],
+                                       mock.MatchedTxs[2],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+                       wantError:     errStatusFailMustFalse,
+               },
+               {
+                       desc: "asset id in matched tx is not unique",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
+                                                       // fee
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+                       wantError:     errAssetIDMustUniqueInMatchedTx,
+               },
+               {
+                       desc: "common input in the matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
+                                                       types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
+                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError:     errInputProgramMustP2WMCScript,
+               },
+               {
+                       desc: "cancel order in the matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
+                                                       types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError:     errExistCancelOrderInMatchedTx,
+               },
+               {
+                       desc: "common input in the cancel order tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+                                                       types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError:     errInputProgramMustP2WMCScript,
+               },
+               {
+                       desc: "amount of fee greater than max fee amount",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *mock.Eth2BtcOrders[2].Utxo.SourceID, *mock.Eth2BtcOrders[2].FromAssetID, mock.Eth2BtcOrders[2].Utxo.Amount, mock.Eth2BtcOrders[2].Utxo.SourcePos, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                                                       // re-order
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
+                                                       // fee
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[2].ToAssetID, 41, mock.RewardProgram),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 1, mock.RewardProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError:     match.ErrAmountOfFeeOutOfRange,
+               },
+               {
+                       desc: "ratio numerator is zero",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs:  []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 0, 1))},
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError:     errRatioOfTradeLessThanZero,
+               },
+               {
+                       desc: "ratio denominator is zero",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs:  []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 1, 0))},
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError:     errRatioOfTradeLessThanZero,
+               },
+               {
+                       desc: "want amount is overflow",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs:  []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), math.MaxInt64, 1))},
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError:     errRequestAmountMath,
+               },
+       }
+
+       for i, c := range cases {
+               movCore := &Core{}
+               c.block.Height = 3456786543
+               if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
+                       t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
+               }
+       }
+}
+
+func TestCalcMatchedTxFee(t *testing.T) {
+       cases := []struct {
+               desc             string
+               tx               types.TxData
+               maxFeeRate       float64
+               wantMatchedTxFee map[bc.AssetID]*matchedTxFee
+       }{
+               {
+                       desc:       "fee less than max fee",
+                       maxFeeRate: 0.05,
+                       wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
+                               mock.ETH: {amount: 11, rewardProgram: mock.RewardProgram},
+                               mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+                       },
+                       tx: mock.MatchedTxs[1].TxData,
+               },
+               {
+                       desc:       "fee refund in tx",
+                       maxFeeRate: 0.05,
+                       wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
+                               mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram},
+                               mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+                       },
+                       tx: mock.MatchedTxs[2].TxData,
+               },
+               {
+                       desc:       "no price diff",
+                       maxFeeRate: 0.05,
+                       wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
+                               mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
+                               mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+                       },
+                       tx: mock.MatchedTxs[0].TxData,
+               },
+       }
+
+       for i, c := range cases {
+               gotMatchedTxFee, err := calcFeeAmount(types.NewTx(c.tx))
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
+                       t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
+               }
+       }
+}
+
+func TestBeforeProposalBlock(t *testing.T) {
+       consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
+               {
+                       BeginBlock: 0,
+                       EndBlock:   100,
+                       Program:    hex.EncodeToString(mock.RewardProgram),
+               },
+       }
+
+       cases := []struct {
+               desc           string
+               initOrders     []*common.Order
+               gasLeft        int64
+               wantMatchedTxs []*types.Tx
+       }{
+               {
+                       desc:           "has matched tx, but gas left is zero",
+                       initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+                       gasLeft:        0,
+                       wantMatchedTxs: []*types.Tx{},
+               },
+               {
+                       desc:           "has one matched tx, and gas is sufficient",
+                       initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+                       gasLeft:        2000,
+                       wantMatchedTxs: []*types.Tx{mock.MatchedTxs[1]},
+               },
+               {
+                       desc: "has two matched tx, but gas is only enough to pack a matched tx",
+                       initOrders: []*common.Order{
+                               mock.Btc2EthOrders[0],
+                               mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[2],
+                       },
+                       gasLeft:        2000,
+                       wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2]},
+               },
+               {
+                       desc: "has two matched tx, and gas left is sufficient",
+                       initOrders: []*common.Order{
+                               mock.Btc2EthOrders[0],
+                               mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[2],
+                       },
+                       gasLeft:        4000,
+                       wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
+               },
+               {
+                       desc: "has multiple trade pairs, and gas left is sufficient",
+                       initOrders: []*common.Order{
+                               mock.Btc2EthOrders[0],
+                               mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[2],
+                               mock.Eos2EtcOrders[0],
+                               mock.Etc2EosOrders[0],
+                       },
+                       gasLeft:        6000,
+                       wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3], mock.MatchedTxs[5]},
+               },
+       }
+
+       for i, c := range cases {
+               testDB := dbm.NewDB("testdb", "leveldb", "temp")
+               store := database.NewLevelDBMovStore(testDB)
+               if err := store.InitDBState(0, &bc.Hash{}); err != nil {
+                       t.Fatal(err)
+               }
+
+               if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
+                       t.Fatal(err)
+               }
+
+               movCore := &Core{movStore: store}
+               gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, 2, c.gasLeft, func() bool { return false })
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               gotMatchedTxMap := make(map[string]interface{})
+               for _, matchedTx := range gotMatchedTxs {
+                       gotMatchedTxMap[matchedTx.ID.String()] = nil
+               }
+
+               wantMatchedTxMap := make(map[string]interface{})
+               for _, matchedTx := range c.wantMatchedTxs {
+                       wantMatchedTxMap[matchedTx.ID.String()] = nil
+               }
+
+               if !testutil.DeepEqual(gotMatchedTxMap, wantMatchedTxMap) {
+                       t.Errorf("#%d(%s):want matched tx(%v) is not equals got matched tx(%v)", i, c.desc, c.wantMatchedTxs, gotMatchedTxs)
+               }
+
+               testDB.Close()
+               os.RemoveAll("temp")
+       }
+}
+
+func TestValidateMatchedTxSequence(t *testing.T) {
+       cases := []struct {
+               desc         string
+               initOrders   []*common.Order
+               transactions []*types.Tx
+               wantError    error
+       }{
+               {
+                       desc:         "both db orders and transactions is empty",
+                       initOrders:   []*common.Order{},
+                       transactions: []*types.Tx{},
+                       wantError:    nil,
+               },
+               {
+                       desc:         "existing matched orders in db, and transactions is empty",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+                       transactions: []*types.Tx{},
+                       wantError:    nil,
+               },
+               {
+                       desc:         "db orders is empty, but transactions has matched tx",
+                       initOrders:   []*common.Order{},
+                       transactions: []*types.Tx{mock.MatchedTxs[1]},
+                       wantError:    errNotMatchedOrder,
+               },
+               {
+                       desc:         "existing matched orders in db, and corresponding matched tx in transactions",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+                       transactions: []*types.Tx{mock.MatchedTxs[1]},
+                       wantError:    nil,
+               },
+               {
+                       desc:         "package matched tx, one order from db, and the another order from transactions",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0]},
+                       transactions: []*types.Tx{mock.Eth2BtcMakerTxs[0], mock.MatchedTxs[10]},
+                       wantError:    nil,
+               },
+               {
+                       desc:         "two matched txs use the same orders",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[1]},
+                       wantError:    errNotMatchedOrder,
+               },
+               {
+                       desc: "existing two matched orders in db, and only one corresponding matched tx in transactions",
+                       initOrders: []*common.Order{
+                               mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
+                               mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+                       },
+                       transactions: []*types.Tx{mock.MatchedTxs[8]},
+                       wantError:    nil,
+               },
+               {
+                       desc: "existing two matched orders in db, and the sequence of match txs in incorrect",
+                       initOrders: []*common.Order{
+                               mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
+                               mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+                       },
+                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[8]},
+                       wantError:    errSpendOutputIDIsIncorrect,
+               },
+               {
+                       desc:         "matched tx and orders from packaged transactions",
+                       initOrders:   []*common.Order{},
+                       transactions: []*types.Tx{mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1], mock.MatchedTxs[4]},
+                       wantError:    nil,
+               },
+               {
+                       desc:         "package the matched tx first, then package match orders",
+                       initOrders:   []*common.Order{},
+                       transactions: []*types.Tx{mock.MatchedTxs[4], mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1]},
+                       wantError:    errNotMatchedOrder,
+               },
+               {
+                       desc:         "cancel order in transactions",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+                       transactions: []*types.Tx{mock.Btc2EthCancelTxs[0], mock.MatchedTxs[1]},
+                       wantError:    errNotMatchedOrder,
+               },
+               {
+                       desc:         "package cancel order after match tx",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.Btc2EthCancelTxs[0]},
+                       wantError:    nil,
+               },
+               {
+                       desc: "package matched txs of different trade pairs",
+                       initOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+                               mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
+                       },
+                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[9]},
+                       wantError:    nil,
+               },
+               {
+                       desc: "package matched txs of different trade pairs in different sequence",
+                       initOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+                               mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
+                       },
+                       transactions: []*types.Tx{mock.MatchedTxs[9], mock.MatchedTxs[1]},
+                       wantError:    nil,
+               },
+               {
+                       desc:         "package partial matched tx from db orders, and the re-pending order continue to match",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+                       transactions: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
+                       wantError:    nil,
+               },
+               {
+                       desc:         "cancel the re-pending order",
+                       initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+                       transactions: []*types.Tx{mock.MatchedTxs[2], mock.Btc2EthCancelTxs[1], mock.MatchedTxs[3]},
+                       wantError:    errNotMatchedOrder,
+               },
+       }
+
+       for i, c := range cases {
+               testDB := dbm.NewDB("testdb", "leveldb", "temp")
+               store := database.NewLevelDBMovStore(testDB)
+               if err := store.InitDBState(0, &bc.Hash{}); err != nil {
+                       t.Fatal(err)
+               }
+
+               if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
+                       t.Fatal(err)
+               }
+
+               movCore := &Core{movStore: store}
+               if err := movCore.validateMatchedTxSequence(c.transactions); err != c.wantError {
+                       t.Errorf("#%d(%s):wanet error(%v), got error(%v)", i, c.desc, c.wantError, err)
+               }
+
+               testDB.Close()
+               os.RemoveAll("temp")
+       }
+}
+
+type testFun func(movCore *Core, block *types.Block) error
+
+func applyBlock(movCore *Core, block *types.Block) error {
+       return movCore.ApplyBlock(block)
+}
+
+func detachBlock(movCore *Core, block *types.Block) error {
+       return movCore.DetachBlock(block)
+}
+
+func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
+       var orders []*common.Order
+       tradePairIterator := database.NewTradePairIterator(store)
+       for tradePairIterator.HasNext() {
+               orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
+               for orderIterator.HasNext() {
+                       orders = append(orders, orderIterator.NextBatch()...)
+               }
+       }
+       return orders
+}
+
+func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
+       orderMap1 := make(map[string]*common.Order)
+       for _, order := range orders1 {
+               orderMap1[order.Key()] = order
+       }
+
+       orderMap2 := make(map[string]*common.Order)
+       for _, order := range orders2 {
+               orderMap2[order.Key()] = order
+       }
+       return testutil.DeepEqual(orderMap1, orderMap2)
+}
+
+func hashPtr(hash bc.Hash) *bc.Hash {
+       return &hash
+}
index 907d1af..c41d7dc 100644 (file)
@@ -150,13 +150,14 @@ func DecodeCrossOutAction(data []byte) (Action, error) {
 
 type crossOutAction struct {
        bc.AssetAmount
-       Address string `json:"address"`
+       Address string        `json:"address"`
+       Program json.HexBytes `json:"control_program"`
 }
 
 func (a *crossOutAction) Build(ctx context.Context, b *TemplateBuilder) error {
        var missing []string
-       if a.Address == "" {
-               missing = append(missing, "address")
+       if a.Address == "" && len(a.Program) == 0 {
+               missing = append(missing, "address or program")
        }
        if a.AssetId.IsZero() {
                missing = append(missing, "asset_id")
@@ -168,23 +169,25 @@ func (a *crossOutAction) Build(ctx context.Context, b *TemplateBuilder) error {
                return MissingFieldsError(missing...)
        }
 
-       address, err := common.DecodeAddress(a.Address, consensus.BytomMainNetParams(&consensus.ActiveNetParams))
-       if err != nil {
-               return err
-       }
+       program := a.Program
+       if a.Address != "" {
+               address, err := common.DecodeAddress(a.Address, consensus.BytomMainNetParams(&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
+               redeemContract := address.ScriptAddress()
+               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.NewCrossChainOutput(*a.AssetId, a.Amount, program)
@@ -269,15 +272,15 @@ type crossInAction struct {
        IssuanceProgram   json.HexBytes `json:"issuance_program"`
 }
 
-func (a *crossInAction) Build(ctx context.Context, builder *TemplateBuilder) error {
+func (c *crossInAction) Build(ctx context.Context, builder *TemplateBuilder) error {
        var missing []string
-       if a.SourceID.IsZero() {
+       if c.SourceID.IsZero() {
                missing = append(missing, "source_id")
        }
-       if a.AssetId.IsZero() {
+       if c.AssetId.IsZero() {
                missing = append(missing, "asset_id")
        }
-       if a.Amount == 0 {
+       if c.Amount == 0 {
                missing = append(missing, "amount")
        }
 
@@ -285,20 +288,24 @@ func (a *crossInAction) Build(ctx context.Context, builder *TemplateBuilder) err
                return MissingFieldsError(missing...)
        }
 
-       if err := a.checkAssetID(); err != nil {
+       if err := c.checkAssetID(); err != nil {
                return err
        }
 
        // arguments will be set when materializeWitnesses
-       txin := types.NewCrossChainInput(nil, a.SourceID, *a.AssetId, a.Amount, a.SourcePos, a.VMVersion, a.RawDefinitionByte, a.IssuanceProgram)
+       txin := types.NewCrossChainInput(nil, c.SourceID, *c.AssetId, c.Amount, c.SourcePos, c.VMVersion, c.RawDefinitionByte, c.IssuanceProgram)
        tplIn := &SigningInstruction{}
        fed := cfg.CommonConfig.Federation
-       tplIn.AddRawWitnessKeys(fed.Xpubs, cfg.FedAddressPath, fed.Quorum)
-       tplIn.AddDataWitness(cfg.FederationPMultiSigScript(cfg.CommonConfig))
+
+       if !common.IsOpenFederationIssueAsset(c.RawDefinitionByte) {
+               tplIn.AddRawWitnessKeys(fed.Xpubs, cfg.FedAddressPath, fed.Quorum)
+               tplIn.AddDataWitness(cfg.FederationPMultiSigScript(cfg.CommonConfig))
+       }
+
        return builder.AddInput(txin, tplIn)
 }
 
-func (a *crossInAction) ActionType() string {
+func (c *crossInAction) ActionType() string {
        return "cross_chain_in"
 }
 
index 6a11198..ba448d6 100644 (file)
@@ -8,6 +8,7 @@ import (
        "github.com/bytom/vapor/errors"
 )
 
+// AddDataWitness append data to the witness array
 func (si *SigningInstruction) AddDataWitness(data chainjson.HexBytes) {
        dw := DataWitness(data)
        si.WitnessComponents = append(si.WitnessComponents, &dw)
diff --git a/cmd/vapord/commands/rollback_node.go b/cmd/vapord/commands/rollback_node.go
new file mode 100644 (file)
index 0000000..ff1f878
--- /dev/null
@@ -0,0 +1,38 @@
+package commands
+
+import (
+       "strconv"
+
+       log "github.com/sirupsen/logrus"
+       "github.com/spf13/cobra"
+
+       "github.com/bytom/vapor/node"
+)
+
+var rollbackCmd = &cobra.Command{
+       Use:   "rollback",
+       Short: "Rollback chain to target height!",
+       Args:  cobra.ExactArgs(1),
+       Run: func(cmd *cobra.Command, args []string) {
+               setLogLevel(config.LogLevel)
+
+               height, err := strconv.ParseInt(args[0], 10, 64)
+               if err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "err": err}).Fatal("failed to parse int")
+               }
+
+               if height < 0 {
+                       log.WithFields(log.Fields{"module": logModule}).Fatal("height should >= 0")
+               }
+
+               if err = node.Rollback(config, uint64(height)); err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "err": err}).Fatal("failed to rollback")
+               }
+
+               log.WithFields(log.Fields{"module": logModule}).Infof("success to rollback height of %d", height)
+       },
+}
+
+func init() {
+       RootCmd.AddCommand(rollbackCmd)
+}
index ed4060d..d534b2d 100644 (file)
@@ -15,6 +15,7 @@ var (
        config = cfg.DefaultConfig()
 )
 
+// RootCmd is the command for run node
 var RootCmd = &cobra.Command{
        Use:   "vapord",
        Short: "Multiple asset management.",
index 2cc8bc7..af75e32 100644 (file)
@@ -21,6 +21,7 @@ var runNodeCmd = &cobra.Command{
 func init() {
        runNodeCmd.Flags().String("prof_laddr", config.ProfListenAddress, "Use http to profile vapord programs")
        runNodeCmd.Flags().Bool("mining", config.Mining, "Enable mining")
+       runNodeCmd.Flags().String("cross_chain.asset_whitelist", config.CrossChain.AssetWhitelist, "Cross-chain-allowed asset whitelist")
 
        runNodeCmd.Flags().Bool("auth.disable", config.Auth.Disable, "Disable rpc access authenticate")
 
index db0cdc9..e899a1e 100644 (file)
@@ -6,6 +6,7 @@ import (
        "encoding/hex"
 )
 
+// FromHex convert hex byte string to []byte
 func FromHex(s string) []byte {
        if len(s) > 1 {
                if s[0:2] == "0x" {
@@ -19,21 +20,25 @@ func FromHex(s string) []byte {
        return nil
 }
 
+// Bytes2Hex convert byte array to string
 func Bytes2Hex(d []byte) string {
        return hex.EncodeToString(d)
 }
 
+// Hex2Bytes convert hex string to byte array
 func Hex2Bytes(str string) []byte {
        h, _ := hex.DecodeString(str)
        return h
 }
 
+// Unit64ToBytes convert uint64 to bytes
 func Unit64ToBytes(n uint64) []byte {
        buf := make([]byte, 8)
        binary.LittleEndian.PutUint64(buf, n)
        return buf
 }
 
+// BytesToUnit64 convert bytes to uint64
 func BytesToUnit64(b []byte) uint64 {
        return binary.LittleEndian.Uint64(b)
 }
diff --git a/common/crossin_asset.go b/common/crossin_asset.go
new file mode 100644 (file)
index 0000000..9dbd729
--- /dev/null
@@ -0,0 +1,25 @@
+package common
+
+import (
+       "encoding/json"
+)
+
+// IsOpenFederationIssueAsset check if the asset definition satisfy ofmf asset
+func IsOpenFederationIssueAsset(rawDefinitionByte []byte) bool {
+       var defMap map[string]interface{}
+       if err := json.Unmarshal(rawDefinitionByte, &defMap); err != nil {
+               return false
+       }
+
+       description, ok := defMap["description"].(map[string]interface{})
+       if !ok {
+               return false
+       }
+
+       issueAssetAction, ok := description["issue_asset_action"].(string)
+       if !ok {
+               return false
+       }
+
+       return issueAssetAction == "open_federation_cross_chain"
+}
diff --git a/common/sort.go b/common/sort.go
deleted file mode 100644 (file)
index a476c92..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package common
-
-// timeSorter implements sort.Interface to allow a slice of timestamps to
-// be sorted.
-type TimeSorter []uint64
-
-// Len returns the number of timestamps in the slice.  It is part of the
-// sort.Interface implementation.
-func (s TimeSorter) Len() int {
-       return len(s)
-}
-
-// Swap swaps the timestamps at the passed indices.  It is part of the
-// sort.Interface implementation.
-func (s TimeSorter) Swap(i, j int) {
-       s[i], s[j] = s[j], s[i]
-}
-
-// Less returns whether the timstamp with index i should sort before the
-// timestamp with index j.  It is part of the sort.Interface implementation.
-func (s TimeSorter) Less(i, j int) bool {
-       return s[i] < s[j]
-}
index fa19b30..e9490f9 100644 (file)
@@ -28,6 +28,7 @@ type Config struct {
        Web        *WebConfig        `mapstructure:"web"`
        Websocket  *WebsocketConfig  `mapstructure:"ws"`
        Federation *FederationConfig `mapstructure:"federation"`
+       CrossChain *CrossChainConfig `mapstructure:"cross_chain"`
 }
 
 // Default configurable parameters.
@@ -40,6 +41,7 @@ func DefaultConfig() *Config {
                Web:        DefaultWebConfig(),
                Websocket:  DefaultWebsocketConfig(),
                Federation: DefaultFederationConfig(),
+               CrossChain: DefaultCrossChainConfig(),
        }
 }
 
@@ -214,6 +216,10 @@ type FederationConfig struct {
        Quorum int            `json:"quorum"`
 }
 
+type CrossChainConfig struct {
+       AssetWhitelist string `mapstructure:"asset_whitelist"`
+}
+
 // Default configurable rpc's auth parameters.
 func DefaultRPCAuthConfig() *RPCAuthConfig {
        return &RPCAuthConfig{
@@ -238,6 +244,7 @@ func DefaultWalletConfig() *WalletConfig {
        }
 }
 
+// Default configurable websocket parameters.
 func DefaultWebsocketConfig() *WebsocketConfig {
        return &WebsocketConfig{
                MaxNumWebsockets:     25,
@@ -245,6 +252,7 @@ func DefaultWebsocketConfig() *WebsocketConfig {
        }
 }
 
+// Default configurable federation parameters.
 func DefaultFederationConfig() *FederationConfig {
        return &FederationConfig{
                Xpubs: []chainkd.XPub{
@@ -257,6 +265,11 @@ func DefaultFederationConfig() *FederationConfig {
        }
 }
 
+// Default configurable crosschain parameters.
+func DefaultCrossChainConfig() *CrossChainConfig {
+       return &CrossChainConfig{}
+}
+
 func xpub(str string) (xpub chainkd.XPub) {
        if err := xpub.UnmarshalText([]byte(str)); err != nil {
                log.Panicf("Fail converts a string to xpub")
index 1c36d86..34e2897 100644 (file)
@@ -9,7 +9,6 @@ import (
 )
 
 func TestFederation(t *testing.T) {
-
        tmpDir, err := ioutil.TempDir(".", "")
        if err != nil {
                t.Fatalf("failed to create temporary data folder: %v", err)
index 8c1d570..83091b5 100644 (file)
@@ -31,18 +31,24 @@ var mainNetConfigTmpl = `chain_id = "mainnet"
 [p2p]
 laddr = "tcp://0.0.0.0:56656"
 seeds = "47.103.79.68:56656,47.103.13.86:56656,47.102.193.119:56656,47.103.17.22:56656"
+[cross_chain]
+asset_whitelist = ""
 `
 
 var testNetConfigTmpl = `chain_id = "testnet"
 [p2p]
 laddr = "tcp://0.0.0.0:56657"
-seeds = "52.82.28.25:56657,52.82.31.195:56657,52.82.31.247:56657"
+seeds = "52.82.7.233:56657,52.82.109.252:56657,52.82.29.30:56657"
+[cross_chain]
+asset_whitelist = ""
 `
 
 var soloNetConfigTmpl = `chain_id = "solonet"
 [p2p]
 laddr = "tcp://0.0.0.0:56658"
 seeds = ""
+[cross_chain]
+asset_whitelist = ""
 `
 
 // Select network seeds to merge a new string.
index 07c32e8..799185e 100644 (file)
@@ -75,6 +75,13 @@ type ProducerSubsidy struct {
        Subsidy    uint64
 }
 
+// MovRewardProgram is a reward address corresponding to the range of the specified block height when matching transactions
+type MovRewardProgram struct {
+       BeginBlock uint64
+       EndBlock   uint64
+       Program    string
+}
+
 // Params store the config for different network
 type Params struct {
        // Name defines a human-readable identifier for the network.
@@ -103,6 +110,12 @@ type Params struct {
        ProducerSubsidys []ProducerSubsidy
 
        SoftForkPoint map[uint64]uint64
+
+       // Mov will only start when the block height is greater than this value
+       MovStartHeight uint64
+
+       // Used to receive rewards for matching transactions
+       MovRewardPrograms []MovRewardProgram
 }
 
 // ActiveNetParams is the active NetParams
@@ -144,7 +157,15 @@ var MainNetParams = Params{
        ProducerSubsidys: []ProducerSubsidy{
                {BeginBlock: 1, EndBlock: 63072000, Subsidy: 9512938},
        },
-       SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600},
+       SoftForkPoint:  map[uint64]uint64{SoftFork001: 10461600},
+       MovStartHeight: 43354800,
+       MovRewardPrograms: []MovRewardProgram{
+               {
+                       BeginBlock: 1,
+                       EndBlock:   126144000,
+                       Program:    "00141d00f85e220e35a23282cfc7f91fe7b34bf6dc18",
+               },
+       },
 }
 
 // TestNetParams is the config for vapor-testnet
@@ -155,12 +176,12 @@ var TestNetParams = Params{
        DNSSeeds:        []string{"www.testnetseed.vapor.io"},
        BasicConfig: BasicConfig{
                MaxBlockGas:                uint64(10000000),
-               MaxGasAmount:               int64(200000),
+               MaxGasAmount:               int64(640000),
                DefaultGasCredit:           int64(160000),
                StorageGasRate:             int64(1),
                VMGasRate:                  int64(200),
-               VotePendingBlockNumber:     uint64(10000),
-               CoinbasePendingBlockNumber: uint64(1200),
+               VotePendingBlockNumber:     uint64(3456000),
+               CoinbasePendingBlockNumber: uint64(7200),
                CoinbaseArbitrarySizeLimit: 128,
        },
        DPOSConfig: DPOSConfig{
@@ -233,6 +254,7 @@ func BytomMainNetParams(vaporParam *Params) *Params {
        return &Params{Bech32HRPSegwit: bech32HRPSegwit}
 }
 
+// InitActiveNetParams load the config by chain ID
 func InitActiveNetParams(chainID string) error {
        var exist bool
        if ActiveNetParams, exist = NetParams[chainID]; !exist {
index 7515141..8f30320 100644 (file)
@@ -4,14 +4,17 @@ import (
        "errors"
 
        "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/vm"
        "github.com/bytom/vapor/protocol/vm/vmutil"
 )
 
+// IsP2WScript is used to determine whether it is a P2WScript or not
 func IsP2WScript(prog []byte) bool {
        return IsP2WPKHScript(prog) || IsP2WSHScript(prog) || IsStraightforward(prog)
 }
 
+// IsStraightforward is used to determine whether it is a Straightforward script or not
 func IsStraightforward(prog []byte) bool {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -23,6 +26,7 @@ func IsStraightforward(prog []byte) bool {
        return insts[0].Op == vm.OP_TRUE || insts[0].Op == vm.OP_FAIL
 }
 
+// IsP2WPKHScript is used to determine whether it is a P2WPKH script or not
 func IsP2WPKHScript(prog []byte) bool {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -37,6 +41,7 @@ func IsP2WPKHScript(prog []byte) bool {
        return insts[1].Op == vm.OP_DATA_20 && len(insts[1].Data) == consensus.PayToWitnessPubKeyHashDataSize
 }
 
+// IsP2WSHScript is used to determine whether it is a P2WSH script or not
 func IsP2WSHScript(prog []byte) bool {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -51,6 +56,45 @@ func IsP2WSHScript(prog []byte) bool {
        return insts[1].Op == vm.OP_DATA_32 && len(insts[1].Data) == consensus.PayToWitnessScriptHashDataSize
 }
 
+// IsP2WMCScript is used to determine whether it is a P2WMC script or not
+func IsP2WMCScript(prog []byte) bool {
+       insts, err := vm.ParseProgram(prog)
+       if err != nil {
+               return false
+       }
+
+       if len(insts) != 6 {
+               return false
+       }
+
+       if insts[0].Op > vm.OP_16 {
+               return false
+       }
+
+       if insts[1].Op != vm.OP_DATA_32 || len(insts[1].Data) != 32 {
+               return false
+       }
+
+       if !(insts[2].IsPushdata() && insts[3].IsPushdata() && insts[4].IsPushdata()) {
+               return false
+       }
+
+       if _, err = vm.AsInt64(insts[2].Data); err != nil {
+               return false
+       }
+
+       if _, err = vm.AsInt64(insts[3].Data); err != nil {
+               return false
+       }
+
+       if !IsP2WScript(insts[4].Data) {
+               return false
+       }
+
+       return insts[5].Op == vm.OP_DATA_32 && len(insts[5].Data) == 32
+}
+
+// ConvertP2PKHSigProgram convert standard P2WPKH program into P2PKH program
 func ConvertP2PKHSigProgram(prog []byte) ([]byte, error) {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -62,6 +106,7 @@ func ConvertP2PKHSigProgram(prog []byte) ([]byte, error) {
        return nil, errors.New("unknow P2PKH version number")
 }
 
+// ConvertP2SHProgram convert standard P2WSH program into P2SH program
 func ConvertP2SHProgram(prog []byte) ([]byte, error) {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -73,6 +118,46 @@ func ConvertP2SHProgram(prog []byte) ([]byte, error) {
        return nil, errors.New("unknow P2SHP version number")
 }
 
+// ConvertP2MCProgram convert standard P2WMC program into P2MC program
+func ConvertP2MCProgram(prog []byte) ([]byte, error) {
+       magneticContractArgs, err := DecodeP2WMCProgram(prog)
+       if err != nil {
+               return nil, err
+       }
+       return vmutil.P2MCProgram(*magneticContractArgs)
+}
+
+// DecodeP2WMCProgram parse standard P2WMC arguments to magneticContractArgs
+func DecodeP2WMCProgram(prog []byte) (*vmutil.MagneticContractArgs, error) {
+       if !IsP2WMCScript(prog) {
+               return nil, errors.New("invalid P2MC program")
+       }
+
+       insts, err := vm.ParseProgram(prog)
+       if err != nil {
+               return nil, err
+       }
+
+       magneticContractArgs := &vmutil.MagneticContractArgs{
+               SellerProgram: insts[4].Data,
+               SellerKey:     insts[5].Data,
+       }
+       requestedAsset := [32]byte{}
+       copy(requestedAsset[:], insts[1].Data)
+       magneticContractArgs.RequestedAsset = bc.NewAssetID(requestedAsset)
+
+       if magneticContractArgs.RatioNumerator, err = vm.AsInt64(insts[2].Data); err != nil {
+               return nil, err
+       }
+
+       if magneticContractArgs.RatioDenominator, err = vm.AsInt64(insts[3].Data); err != nil {
+               return nil, err
+       }
+
+       return magneticContractArgs, nil
+}
+
+// GetHashFromStandardProg get hash from standard program
 func GetHashFromStandardProg(prog []byte) ([]byte, error) {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
index 8d15bcf..f392f1d 100644 (file)
@@ -45,7 +45,7 @@ func accountIndexKey(xpubs []chainkd.XPub) []byte {
        return append(AccountIndexPrefix, hash[:]...)
 }
 
-func Bip44ContractIndexKey(accountID string, change bool) []byte {
+func bip44ContractIndexKey(accountID string, change bool) []byte {
        key := append(ContractIndexPrefix, []byte(accountID)...)
        if change {
                return append(key, 0x01)
@@ -96,7 +96,7 @@ func (store *AccountStore) InitBatch() acc.AccountStore {
 // CommitBatch commit batch
 func (store *AccountStore) CommitBatch() error {
        if store.batch == nil {
-               return errors.New("AccountStore commit fail, store batch is nil.")
+               return errors.New("accountStore commit fail, store batch is nil")
        }
        store.batch.Write()
        store.batch = nil
@@ -119,8 +119,8 @@ func (store *AccountStore) DeleteAccount(account *acc.Account) error {
        }
 
        // delete bip44 contract index
-       batch.Delete(Bip44ContractIndexKey(account.ID, false))
-       batch.Delete(Bip44ContractIndexKey(account.ID, true))
+       batch.Delete(bip44ContractIndexKey(account.ID, false))
+       batch.Delete(bip44ContractIndexKey(account.ID, true))
 
        // delete contract index
        batch.Delete(contractIndexKey(account.ID))
@@ -216,7 +216,7 @@ func (store *AccountStore) GetAccountIndex(xpubs []chainkd.XPub) uint64 {
 // GetBip44ContractIndex get bip44 contract index
 func (store *AccountStore) GetBip44ContractIndex(accountID string, change bool) uint64 {
        index := uint64(0)
-       if rawIndexBytes := store.db.Get(Bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
+       if rawIndexBytes := store.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
                index = common.BytesToUnit64(rawIndexBytes)
        }
        return index
@@ -367,9 +367,9 @@ func (store *AccountStore) SetAccountIndex(account *acc.Account) {
 // SetBip44ContractIndex set contract index
 func (store *AccountStore) SetBip44ContractIndex(accountID string, change bool, index uint64) {
        if store.batch == nil {
-               store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+               store.db.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
        } else {
-               store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+               store.batch.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
        }
 }
 
index 1970543..4ffc65d 100644 (file)
@@ -156,6 +156,56 @@ func GetConsensusResult(db dbm.DB, seq uint64) (*state.ConsensusResult, error) {
        return consensusResult, nil
 }
 
+// DeleteConsensusResult delete a consensusResult from cache and database
+func (s *Store) DeleteConsensusResult(seq uint64) error {
+       consensusResult, err := GetConsensusResult(s.db, seq)
+       if err != nil {
+               return err
+       }
+
+       s.db.Delete(calcConsensusResultKey(seq))
+       s.cache.removeConsensusResult(consensusResult)
+       return nil
+}
+
+// DeleteBlock delete a new block in the protocol.
+func (s *Store) DeleteBlock(block *types.Block) error {
+       blockHash := block.Hash()
+       blockHashes, err := s.GetBlockHashesByHeight(block.Height)
+       if err != nil {
+               return err
+       }
+
+       for i := 0; i < len(blockHashes); i++ {
+               if blockHashes[i].String() == blockHash.String() {
+                       blockHashes = append(blockHashes[0:i], blockHashes[i+1:len(blockHashes)]...)
+                       break
+               }
+       }
+
+       batch := s.db.NewBatch()
+       if len(blockHashes) == 0 {
+               batch.Delete(calcBlockHashesPrefix(block.Height))
+       } else {
+               binaryBlockHashes, err := json.Marshal(blockHashes)
+               if err != nil {
+                       return errors.Wrap(err, "Marshal block hashes")
+               }
+
+               batch.Set(calcBlockHashesPrefix(block.Height), binaryBlockHashes)
+       }
+
+       batch.Delete(calcBlockHeaderKey(&blockHash))
+       batch.Delete(calcBlockTransactionsKey(&blockHash))
+       batch.Delete(calcTxStatusKey(&blockHash))
+       batch.Write()
+
+       s.cache.removeBlockHashes(block.Height)
+       s.cache.removeBlockHeader(&block.BlockHeader)
+
+       return nil
+}
+
 // NewStore creates and returns a new Store object.
 func NewStore(db dbm.DB) *Store {
        fillBlockHeaderFn := func(hash *bc.Hash) (*types.BlockHeader, error) {
index 548f6b2..cf8e86a 100644 (file)
@@ -4,6 +4,8 @@ import (
        "os"
        "testing"
 
+       "github.com/stretchr/testify/require"
+
        "github.com/bytom/vapor/consensus"
        dbm "github.com/bytom/vapor/database/leveldb"
        "github.com/bytom/vapor/database/storage"
@@ -290,3 +292,227 @@ func TestSaveBlockHeader(t *testing.T) {
                }
        }
 }
+
+func TestDeleteBlock(t *testing.T) {
+       cases := []struct {
+               initBlocks  []*types.BlockHeader
+               deleteBlock *types.BlockHeader
+               wantBlocks  []*types.BlockHeader
+       }{
+               {
+                       initBlocks: []*types.BlockHeader{},
+                       deleteBlock: &types.BlockHeader{
+                               Version:   uint64(1),
+                               Height:    uint64(1),
+                               Timestamp: uint64(1528945000),
+                       },
+                       wantBlocks: []*types.BlockHeader{},
+               },
+               {
+                       initBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945000),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945005),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945010),
+                               },
+                       },
+                       deleteBlock: &types.BlockHeader{
+                               Version:   uint64(1),
+                               Height:    uint64(1),
+                               Timestamp: uint64(1528945000),
+                       },
+                       wantBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945005),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945010),
+                               },
+                       },
+               },
+               {
+                       initBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945000),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945005),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945010),
+                               },
+                       },
+                       deleteBlock: &types.BlockHeader{
+                               Version:   uint64(1),
+                               Height:    uint64(1),
+                               Timestamp: uint64(1528945005),
+                       },
+                       wantBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945000),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945010),
+                               },
+                       },
+               },
+               {
+                       initBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945000),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945005),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945010),
+                               },
+                       },
+                       deleteBlock: &types.BlockHeader{
+                               Version:   uint64(1),
+                               Height:    uint64(1),
+                               Timestamp: uint64(1528945010),
+                       },
+                       wantBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945000),
+                               },
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945005),
+                               },
+                       },
+               },
+               {
+                       initBlocks: []*types.BlockHeader{},
+                       deleteBlock: &types.BlockHeader{
+                               Version:   uint64(1),
+                               Height:    uint64(1),
+                               Timestamp: uint64(1528945030),
+                       },
+                       wantBlocks: []*types.BlockHeader{},
+               },
+               {
+                       initBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945000),
+                               },
+                       },
+                       deleteBlock: &types.BlockHeader{
+                               Version:   uint64(1),
+                               Height:    uint64(1),
+                               Timestamp: uint64(1528945030),
+                       },
+                       wantBlocks: []*types.BlockHeader{
+                               {
+                                       Version:   uint64(1),
+                                       Height:    uint64(1),
+                                       Timestamp: uint64(1528945000),
+                               },
+                       },
+               },
+       }
+
+       for _, c := range cases {
+               verifyStatus := &bc.TransactionStatus{
+                       VerifyStatus: []*bc.TxVerifyResult{
+                               {StatusFail: false},
+                       },
+               }
+               deleteBlock := &types.Block{
+                       BlockHeader: types.BlockHeader{
+                               Version:   c.deleteBlock.Version,
+                               Height:    c.deleteBlock.Height,
+                               Timestamp: c.deleteBlock.Timestamp,
+                       },
+               }
+
+               dbA := dbm.NewDB("dbu", "leveldb", "tempA")
+               dbB := dbm.NewDB("dbc", "leveldb", "tempB")
+
+               storeA := NewStore(dbA)
+               storeB := NewStore(dbB)
+
+               for i := 0; i < len(c.initBlocks); i++ {
+                       block := &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Version:   c.initBlocks[i].Version,
+                                       Height:    c.initBlocks[i].Height,
+                                       Timestamp: c.initBlocks[i].Timestamp,
+                               },
+                       }
+                       if err := storeA.SaveBlock(block, verifyStatus); err != nil {
+                               t.Fatal(err)
+                       }
+               }
+
+               if err := storeA.DeleteBlock(deleteBlock); err != nil {
+                       t.Fatal(err)
+               }
+
+               for i := 0; i < len(c.wantBlocks); i++ {
+                       block := &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Version:   c.wantBlocks[i].Version,
+                                       Height:    c.wantBlocks[i].Height,
+                                       Timestamp: c.wantBlocks[i].Timestamp,
+                               },
+                       }
+                       if err := storeB.SaveBlock(block, verifyStatus); err != nil {
+                               t.Fatal(err)
+                       }
+               }
+
+               iterA := dbA.Iterator()
+               iterB := dbB.Iterator()
+
+               for iterA.Next() && iterB.Next() {
+                       require.Equal(t, iterA.Key(), iterB.Key())
+                       require.Equal(t, iterA.Value(), iterB.Value())
+               }
+
+               if iterA.Next() || iterB.Next() {
+                       t.Fatalf("why iterator is not finished")
+               }
+
+               dbA.Close()
+               os.RemoveAll("tempA")
+               dbB.Close()
+               os.RemoveAll("tempB")
+       }
+}
index 81a7f28..5f4de1f 100644 (file)
@@ -1,12 +1,12 @@
 package database
 
 import (
-       "github.com/golang/protobuf/proto"
        dbm "github.com/bytom/vapor/database/leveldb"
        "github.com/bytom/vapor/database/storage"
        "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/state"
+       "github.com/golang/protobuf/proto"
 )
 
 const utxoPreFix = "UT:"
@@ -91,6 +91,7 @@ func saveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
        return nil
 }
 
+// SaveUtxoView is export for intergation test
 func SaveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
        return saveUtxoView(batch, view)
 }
index d0bfa5a..d50464b 100644 (file)
@@ -31,6 +31,7 @@ const (
        recoveryKey //recoveryKey key for db store recovery info.
 )
 
+// pre-define variables
 var (
        walletStore         = []byte("WS:")
        SUTXOPrefix         = append(walletStore, sutxoPrefix, colon)
@@ -66,10 +67,12 @@ func calcUnconfirmedTxKey(formatKey string) []byte {
        return append(UnconfirmedTxPrefix, []byte(formatKey)...)
 }
 
+// CalcGlobalTxIndexKey calculate tx hash index key
 func CalcGlobalTxIndexKey(txID string) []byte {
        return append(GlobalTxIndexPrefix, []byte(txID)...)
 }
 
+// CalcGlobalTxIndex calcuate the block index + position index key
 func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
        txIdx := make([]byte, 40)
        copy(txIdx[:32], blockHash.Bytes())
@@ -109,7 +112,7 @@ func (store *WalletStore) InitBatch() wallet.WalletStore {
 // CommitBatch commit batch
 func (store *WalletStore) CommitBatch() error {
        if store.batch == nil {
-               return errors.New("WalletStore commit fail, store batch is nil.")
+               return errors.New("walletStore commit fail, store batch is nil")
        }
 
        store.batch.Write()
@@ -347,6 +350,7 @@ func (store *WalletStore) ListAccountUTXOs(id string, isSmartContract bool) ([]*
        return confirmedUTXOs, nil
 }
 
+// ListTransactions list tx by filter args
 func (store *WalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
        annotatedTxs := []*query.AnnotatedTx{}
        var startKey []byte
index 36e0061..97b48c0 100644 (file)
@@ -96,6 +96,7 @@ CREATE TABLE `assets` (
   `issuance_program` mediumtext NOT NULL,
   `vm_version` int(11) NOT NULL DEFAULT '1',
   `definition` text,
+  `is_open_federation_issue` tinyint(1) DEFAULT '0',
   `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`),
diff --git a/math/algorithm.go b/math/algorithm.go
new file mode 100644 (file)
index 0000000..791a70c
--- /dev/null
@@ -0,0 +1,9 @@
+package math
+
+// MinUint64 return the min of x and y
+func MinUint64(x, y uint64) uint64 {
+       if x < y {
+               return x
+       }
+       return y
+}
index 5d0522d..9cdfe1b 100644 (file)
@@ -27,11 +27,7 @@ var (
        maxNumOfBlocksRegularSync = uint64(128)
 )
 
-type FastSync interface {
-       process() error
-       setSyncPeer(peer *peers.Peer)
-}
-
+// Fetcher is the interface for fetch struct
 type Fetcher interface {
        processBlock(peerID string, block *types.Block)
        processBlocks(peerID string, blocks []*types.Block)
@@ -56,7 +52,7 @@ type headersMsg struct {
 
 type blockKeeper struct {
        chain      Chain
-       fastSync   FastSync
+       fastSync   *fastSync
        msgFetcher Fetcher
        peers      *peers.PeerSet
        syncPeer   *peers.Peer
@@ -76,7 +72,7 @@ func newBlockKeeper(chain Chain, peers *peers.PeerSet, fastSyncDB dbm.DB) *block
        }
 }
 
-func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash) ([]*types.Block, error) {
+func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash, isTimeout func() bool) ([]*types.Block, error) {
        headers, err := bk.locateHeaders(locator, stopHash, 0, maxNumOfBlocksPerMsg)
        if err != nil {
                return nil, err
@@ -91,6 +87,9 @@ func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash) ([]*t
                }
 
                blocks = append(blocks, block)
+               if isTimeout() {
+                       break
+               }
        }
        return blocks, nil
 }
index e34e48b..52b206c 100644 (file)
@@ -448,7 +448,8 @@ func TestLocateBlocks(t *testing.T) {
                        want = append(want, blocks[i])
                }
 
-               got, err := bk.locateBlocks(locator, &c.stopHash)
+               mockTimeout := func() bool { return false }
+               got, err := bk.locateBlocks(locator, &c.stopHash, mockTimeout)
                if err != c.wantErr {
                        t.Errorf("case %d: got %v want err = %v", i, err, c.wantErr)
                }
index d1e45a6..50a110b 100644 (file)
@@ -12,17 +12,13 @@ import (
 
 var errOrphanBlock = errors.New("fast sync inserting orphan block")
 
-type BlockProcessor interface {
-       process(chan struct{}, chan struct{}, uint64, *sync.WaitGroup)
-}
-
 type blockProcessor struct {
        chain   Chain
-       storage Storage
+       storage *storage
        peers   *peers.PeerSet
 }
 
-func newBlockProcessor(chain Chain, storage Storage, peers *peers.PeerSet) *blockProcessor {
+func newBlockProcessor(chain Chain, storage *storage, peers *peers.PeerSet) *blockProcessor {
        return &blockProcessor{
                chain:   chain,
                peers:   peers,
index 6b8ea72..a802227 100644 (file)
@@ -29,12 +29,12 @@ var (
 type fastSync struct {
        chain          Chain
        msgFetcher     MsgFetcher
-       blockProcessor BlockProcessor
+       blockProcessor *blockProcessor
        peers          *peers.PeerSet
        mainSyncPeer   *peers.Peer
 }
 
-func newFastSync(chain Chain, msgFetcher MsgFetcher, storage Storage, peers *peers.PeerSet) *fastSync {
+func newFastSync(chain Chain, msgFetcher MsgFetcher, storage *storage, peers *peers.PeerSet) *fastSync {
        return &fastSync{
                chain:          chain,
                msgFetcher:     msgFetcher,
@@ -152,7 +152,7 @@ func (fs *fastSync) process() error {
 // sync length cannot be greater than maxFastSyncBlocksNum.
 func (fs *fastSync) findSyncRange() (*types.Block, error) {
        bestHeight := fs.chain.BestBlockHeight()
-       length := fs.mainSyncPeer.IrreversibleHeight() - fastSyncPivotGap - bestHeight
+       length := fs.mainSyncPeer.Height() - fastSyncPivotGap - bestHeight
        if length > maxNumOfBlocksPerSync {
                length = maxNumOfBlocksPerSync
        }
index e57e60a..35d1f9a 100644 (file)
@@ -3,6 +3,7 @@ package chainmgr
 import (
        "errors"
        "reflect"
+       "time"
 
        log "github.com/sirupsen/logrus"
 
@@ -38,6 +39,7 @@ type Chain interface {
        ValidateTx(*types.Tx) (bool, error)
 }
 
+// Switch is the interface for network layer
 type Switch interface {
        AddReactor(name string, reactor p2p.Reactor) p2p.Reactor
        Start() (bool, error)
@@ -50,6 +52,7 @@ type Switch interface {
 // Mempool is the interface for Bytom mempool
 type Mempool interface {
        GetTransactions() []*core.TxDesc
+       IsDust(tx *types.Tx) bool
 }
 
 //Manager is responsible for the business layer information synchronization
@@ -89,6 +92,7 @@ func NewManager(config *cfg.Config, sw Switch, chain Chain, mempool Mempool, dis
        return manager, nil
 }
 
+// AddPeer add the network layer peer to logic layer
 func (m *Manager) AddPeer(peer peers.BasePeer) {
        m.peers.AddPeer(peer)
 }
@@ -153,7 +157,12 @@ func (m *Manager) handleGetBlockMsg(peer *peers.Peer, msg *msgs.GetBlockMessage)
 }
 
 func (m *Manager) handleGetBlocksMsg(peer *peers.Peer, msg *msgs.GetBlocksMessage) {
-       blocks, err := m.blockKeeper.locateBlocks(msg.GetBlockLocator(), msg.GetStopHash())
+       endTime := time.Now().Add(requireBlocksTimeout / 2)
+       isTimeout := func() bool {
+               return time.Now().After(endTime)
+       }
+
+       blocks, err := m.blockKeeper.locateBlocks(msg.GetBlockLocator(), msg.GetStopHash(), isTimeout)
        if err != nil || len(blocks) == 0 {
                return
        }
@@ -254,6 +263,11 @@ func (m *Manager) handleTransactionMsg(peer *peers.Peer, msg *msgs.TransactionMe
                return
        }
 
+       if m.mempool.IsDust(tx) {
+               m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "receive dust tx msg")
+               return
+       }
+
        m.peers.MarkTx(peer.ID(), tx.ID)
        if isOrphan, err := m.chain.ValidateTx(tx); err != nil && err != core.ErrDustTx && !isOrphan {
                m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "fail on validate tx transaction")
@@ -273,6 +287,11 @@ func (m *Manager) handleTransactionsMsg(peer *peers.Peer, msg *msgs.Transactions
        }
 
        for _, tx := range txs {
+               if m.mempool.IsDust(tx) {
+                       m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "receive dust txs msg")
+                       continue
+               }
+
                m.peers.MarkTx(peer.ID(), tx.ID)
                if isOrphan, err := m.chain.ValidateTx(tx); err != nil && !isOrphan {
                        m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "fail on validate tx transaction")
@@ -343,10 +362,12 @@ func (m *Manager) processMsg(basePeer peers.BasePeer, msgType byte, msg msgs.Blo
        }
 }
 
+// RemovePeer delete peer for peer set
 func (m *Manager) RemovePeer(peerID string) {
        m.peers.RemovePeer(peerID)
 }
 
+// SendStatus sent the current self status to remote peer
 func (m *Manager) SendStatus(peer peers.BasePeer) error {
        p := m.peers.GetPeer(peer.ID())
        if p == nil {
@@ -360,6 +381,7 @@ func (m *Manager) SendStatus(peer peers.BasePeer) error {
        return nil
 }
 
+// Start the network logic layer
 func (m *Manager) Start() error {
        var err error
        m.txMsgSub, err = m.eventDispatcher.Subscribe(core.TxMsgEvent{})
index 457574b..aa8d657 100644 (file)
@@ -24,7 +24,7 @@ const (
 var (
        requireBlockTimeout      = 20 * time.Second
        requireHeadersTimeout    = 30 * time.Second
-       requireBlocksTimeout     = 50 * time.Second
+       requireBlocksTimeout     = 90 * time.Second
        checkSyncPeerNumInterval = 5 * time.Second
 
        errRequestBlocksTimeout = errors.New("request blocks timeout")
@@ -33,6 +33,7 @@ var (
        errSendMsg              = errors.New("send message error")
 )
 
+// MsgFetcher is the interface for msg fetch struct
 type MsgFetcher interface {
        resetParameter()
        addSyncPeer(peerID string)
@@ -51,7 +52,7 @@ type fetchBlocksResult struct {
 }
 
 type msgFetcher struct {
-       storage          Storage
+       storage          *storage
        syncPeers        *fastSyncPeers
        peers            *peers.PeerSet
        blockProcessCh   chan *blockMsg
@@ -61,7 +62,7 @@ type msgFetcher struct {
        mux              sync.RWMutex
 }
 
-func newMsgFetcher(storage Storage, peers *peers.PeerSet) *msgFetcher {
+func newMsgFetcher(storage *storage, peers *peers.PeerSet) *msgFetcher {
        return &msgFetcher{
                storage:          storage,
                syncPeers:        newFastSyncPeers(),
index ff6b758..8cc8b12 100644 (file)
@@ -15,13 +15,7 @@ var (
        errDBFindBlock      = errors.New("can't find block from DB")
 )
 
-type Storage interface {
-       resetParameter()
-       writeBlocks(peerID string, blocks []*types.Block) error
-       readBlock(height uint64) (*blockStorage, error)
-       deleteBlock(height uint64)
-}
-
+// LocalStore is the interface for persistent storage
 type LocalStore interface {
        writeBlock(block *types.Block) error
        readBlock(height uint64) (*types.Block, error)
index cfe8560..00e59e1 100644 (file)
@@ -51,6 +51,10 @@ func (m *mempool) GetTransactions() []*core.TxDesc {
        return txs
 }
 
+func (m *mempool) IsDust(tx *types.Tx) bool {
+       return false
+}
+
 func TestSyncMempool(t *testing.T) {
        tmpDir, err := ioutil.TempDir(".", "")
        if err != nil {
index 1b00877..313f55b 100644 (file)
@@ -5,6 +5,7 @@ import (
        "errors"
        "net"
        "net/http"
+       // debug tool
        _ "net/http/pprof"
        "path/filepath"
        "reflect"
@@ -17,6 +18,7 @@ import (
        "github.com/bytom/vapor/accesstoken"
        "github.com/bytom/vapor/account"
        "github.com/bytom/vapor/api"
+       "github.com/bytom/vapor/application/mov"
        "github.com/bytom/vapor/asset"
        "github.com/bytom/vapor/blockchain/pseudohsm"
        cfg "github.com/bytom/vapor/config"
@@ -58,16 +60,10 @@ type Node struct {
 
 // NewNode create bytom node
 func NewNode(config *cfg.Config) *Node {
-       if err := lockDataDirectory(config); err != nil {
-               cmn.Exit("Error: " + err.Error())
-       }
+       initNodeConfig(config)
 
-       if err := cfg.LoadFederationFile(config.FederationFile(), config); err != nil {
-               cmn.Exit(cmn.Fmt("Failed to load federated information:[%s]", err.Error()))
-       }
-
-       if err:=vaporLog.InitLogFile(config);err!=nil{
-               log.WithField("err",err).Fatalln("InitLogFile failed")
+       if err := vaporLog.InitLogFile(config); err != nil {
+               log.WithField("err", err).Fatalln("InitLogFile failed")
        }
 
        log.WithFields(log.Fields{
@@ -78,12 +74,6 @@ func NewNode(config *cfg.Config) *Node {
                "fed_controlprogram": hex.EncodeToString(cfg.FederationWScript(config)),
        }).Info()
 
-       if err := consensus.InitActiveNetParams(config.ChainID); err != nil {
-               log.Fatalf("Failed to init ActiveNetParams:[%s]", err.Error())
-       }
-
-       initCommonConfig(config)
-
        // Get store
        if config.DBBackend != "memdb" && config.DBBackend != "leveldb" {
                cmn.Exit(cmn.Fmt("Param db_backend [%v] is invalid, use leveldb or memdb", config.DBBackend))
@@ -95,8 +85,10 @@ func NewNode(config *cfg.Config) *Node {
        accessTokens := accesstoken.NewStore(tokenDB)
 
        dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       movCore := mov.NewCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
+       assetFilter := protocol.NewAssetFilter(config.CrossChain.AssetWhitelist)
+       txPool := protocol.NewTxPool(store, []protocol.DustFilterer{movCore, assetFilter}, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, []protocol.Protocoler{movCore}, dispatcher)
        if err != nil {
                cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))
        }
@@ -125,6 +117,10 @@ func NewNode(config *cfg.Config) *Node {
                        log.WithFields(log.Fields{"module": logModule, "error": err}).Error("init NewWallet")
                }
 
+               if err = wallet.Run(); err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "error": err}).Error("init NewWallet work running thread")
+               }
+
                // trigger rescan wallet
                if config.Wallet.Rescan {
                        wallet.RescanBlocks()
@@ -167,6 +163,69 @@ func NewNode(config *cfg.Config) *Node {
        return node
 }
 
+// Rollback rollback chain from one height to targetHeight
+func Rollback(config *cfg.Config, targetHeight uint64) error {
+       if err := initNodeConfig(config); err != nil {
+               return err
+       }
+
+       // Get store
+       if config.DBBackend != "leveldb" {
+               return errors.New("Param db_backend is invalid, use leveldb")
+       }
+
+       coreDB := dbm.NewDB("core", config.DBBackend, config.DBDir())
+       store := database.NewStore(coreDB)
+
+       dispatcher := event.NewDispatcher()
+       movCore := mov.NewCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
+       txPool := protocol.NewTxPool(store, []protocol.DustFilterer{movCore}, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, []protocol.Protocoler{movCore}, dispatcher)
+       if err != nil {
+               return err
+       }
+
+       hsm, err := pseudohsm.New(config.KeysDir())
+       if err != nil {
+               return err
+       }
+
+       walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())
+       walletStore := database.NewWalletStore(walletDB)
+       accountStore := database.NewAccountStore(walletDB)
+       accounts := account.NewManager(accountStore, chain)
+       assets := asset.NewRegistry(walletDB, chain)
+       wallet, err := w.NewWallet(walletStore, accounts, assets, hsm, chain, dispatcher, config.Wallet.TxIndex)
+       if err != nil {
+               return err
+       }
+
+       if err := wallet.Rollback(targetHeight); err != nil {
+               return err
+       }
+
+       return chain.Rollback(targetHeight)
+}
+
+func initNodeConfig(config *cfg.Config) error {
+       if err := lockDataDirectory(config); err != nil {
+               log.WithField("err", err).Info("Error: " + err.Error())
+               return err
+       }
+
+       if err := cfg.LoadFederationFile(config.FederationFile(), config); err != nil {
+               log.WithField("err", err).Info("Failed to load federated information")
+               return err
+       }
+
+       if err := consensus.InitActiveNetParams(config.ChainID); err != nil {
+               log.Fatalf("Failed to init ActiveNetParams:[%s]", err.Error())
+       }
+
+       cfg.CommonConfig = config
+       return nil
+}
+
 // find whether config xpubs equal genesis block xpubs
 func checkConfig(chain *protocol.Chain, config *cfg.Config) error {
        fedpegScript := cfg.FederationWScript(config)
@@ -177,7 +236,7 @@ func checkConfig(chain *protocol.Chain, config *cfg.Config) error {
        typedInput := genesisBlock.Transactions[0].Inputs[0].TypedInput
        if v, ok := typedInput.(*types.CoinbaseInput); ok {
                if !reflect.DeepEqual(fedpegScript, v.Arbitrary) {
-                       return errors.New("config xpubs don't equal genesis block xpubs.")
+                       return errors.New("config xpubs don't equal genesis block xpubs")
                }
        }
        return nil
@@ -214,6 +273,7 @@ func (n *Node) initAndstartAPIServer() {
        n.api.StartServer(*listenAddr)
 }
 
+// OnStart implements BaseService
 func (n *Node) OnStart() error {
        if n.miningEnable {
                if _, err := n.wallet.AccountMgr.GetMiningAddress(); err != nil {
@@ -245,6 +305,7 @@ func (n *Node) OnStart() error {
        return nil
 }
 
+// OnStop implements BaseService
 func (n *Node) OnStop() {
        n.notificationMgr.Shutdown()
        n.notificationMgr.WaitForShutdown()
@@ -258,6 +319,7 @@ func (n *Node) OnStop() {
        n.eventDispatcher.Stop()
 }
 
+// RunForever listen to the stop signal
 func (n *Node) RunForever() {
        // Sleep forever and then...
        cmn.TrapSignal(func() {
index 70c9018..f7d08b0 100644 (file)
@@ -16,7 +16,11 @@ import (
 )
 
 const (
-       logModule = "blockproposer"
+       logModule         = "blockproposer"
+       warnTimeNum       = 2
+       warnTimeDenom     = 5
+       criticalTimeNum   = 4
+       criticalTimeDenom = 5
 )
 
 // BlockProposer propose several block in specified time range
@@ -74,7 +78,9 @@ func (b *BlockProposer) generateBlocks() {
                        continue
                }
 
-               block, err := proposal.NewBlockTemplate(b.chain, b.txPool, b.accountManager, nextBlockTime)
+               warnDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*warnTimeNum/warnTimeDenom) * time.Millisecond
+               criticalDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*criticalTimeNum/criticalTimeDenom) * time.Millisecond
+               block, err := proposal.NewBlockTemplate(b.chain, b.accountManager, nextBlockTime, warnDuration, criticalDuration)
                if err != nil {
                        log.WithFields(log.Fields{"module": logModule, "error": err}).Error("failed on create NewBlockTemplate")
                        continue
index 0cdb5ad..1a4d1c8 100644 (file)
@@ -19,12 +19,221 @@ import (
        "github.com/bytom/vapor/protocol/vm/vmutil"
 )
 
-const logModule = "mining"
+const (
+       logModule     = "mining"
+       batchApplyNum = 64
+
+       timeoutOk = iota + 1
+       timeoutWarn
+       timeoutCritical
+)
+
+// NewBlockTemplate returns a new block template that is ready to be solved
+func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
+       builder := newBlockBuilder(chain, accountManager, timestamp, warnDuration, criticalDuration)
+       return builder.build()
+}
+
+type blockBuilder struct {
+       chain          *protocol.Chain
+       accountManager *account.Manager
+
+       block    *types.Block
+       txStatus *bc.TransactionStatus
+       utxoView *state.UtxoViewpoint
+
+       warnTimeoutCh     <-chan time.Time
+       criticalTimeoutCh <-chan time.Time
+       timeoutStatus     uint8
+       gasLeft           int64
+}
+
+func newBlockBuilder(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) *blockBuilder {
+       preBlockHeader := chain.BestBlockHeader()
+       block := &types.Block{
+               BlockHeader: types.BlockHeader{
+                       Version:           1,
+                       Height:            preBlockHeader.Height + 1,
+                       PreviousBlockHash: preBlockHeader.Hash(),
+                       Timestamp:         timestamp,
+                       BlockCommitment:   types.BlockCommitment{},
+                       BlockWitness:      types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
+               },
+       }
+
+       builder := &blockBuilder{
+               chain:             chain,
+               accountManager:    accountManager,
+               block:             block,
+               txStatus:          bc.NewTransactionStatus(),
+               utxoView:          state.NewUtxoViewpoint(),
+               warnTimeoutCh:     time.After(warnDuration),
+               criticalTimeoutCh: time.After(criticalDuration),
+               gasLeft:           int64(consensus.ActiveNetParams.MaxBlockGas),
+               timeoutStatus:     timeoutOk,
+       }
+       return builder
+}
+
+func (b *blockBuilder) applyCoinbaseTransaction() error {
+       coinbaseTx, err := b.createCoinbaseTx()
+       if err != nil {
+               return errors.Wrap(err, "fail on create coinbase tx")
+       }
+
+       gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: b.block.Height}, Transactions: []*bc.Tx{coinbaseTx.Tx}})
+       if err != nil {
+               return err
+       }
+
+       b.block.Transactions = append(b.block.Transactions, coinbaseTx)
+       if err := b.txStatus.SetStatus(0, false); err != nil {
+               return err
+       }
+
+       b.gasLeft -= gasState.GasUsed
+       return nil
+}
+
+func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) error {
+       tempTxs := []*types.Tx{}
+       for i := 0; i < len(txs); i++ {
+               if tempTxs = append(tempTxs, txs[i]); len(tempTxs) < batchApplyNum && i != len(txs)-1 {
+                       continue
+               }
+
+               results, gasLeft := b.preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
+               for _, result := range results {
+                       if result.err != nil && !result.gasOnly {
+                               log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("mining block generation: skip tx due to")
+                               b.chain.GetTxPool().RemoveTransaction(&result.tx.ID)
+                               continue
+                       }
+
+                       if err := b.txStatus.SetStatus(len(b.block.Transactions), result.gasOnly); err != nil {
+                               return err
+                       }
+
+                       b.block.Transactions = append(b.block.Transactions, result.tx)
+               }
+
+               b.gasLeft = gasLeft
+               tempTxs = []*types.Tx{}
+               if b.getTimeoutStatus() >= timeoutStatus {
+                       break
+               }
+       }
+       return nil
+}
+
+func (b *blockBuilder) applyTransactionFromPool() error {
+       txDescList := b.chain.GetTxPool().GetTransactions()
+       sort.Sort(byTime(txDescList))
+
+       poolTxs := make([]*types.Tx, len(txDescList))
+       for i, txDesc := range txDescList {
+               poolTxs[i] = txDesc.Tx
+       }
+
+       return b.applyTransactions(poolTxs, timeoutWarn)
+}
+
+func (b *blockBuilder) applyTransactionFromSubProtocol() error {
+       isTimeout := func() bool {
+               return b.getTimeoutStatus() > timeoutOk
+       }
+
+       for i, p := range b.chain.SubProtocols() {
+               if b.gasLeft <= 0 || isTimeout() {
+                       break
+               }
+
+               subTxs, err := p.BeforeProposalBlock(b.block.Transactions, b.block.Height, b.gasLeft, isTimeout)
+               if err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
+                       continue
+               }
+
+               if err := b.applyTransactions(subTxs, timeoutCritical); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (b *blockBuilder) build() (*types.Block, error) {
+       if err := b.applyCoinbaseTransaction(); err != nil {
+               return nil, err
+       }
+
+       if err := b.applyTransactionFromPool(); err != nil {
+               return nil, err
+       }
+
+       if err := b.applyTransactionFromSubProtocol(); err != nil {
+               return nil, err
+       }
+
+       if err := b.calcBlockCommitment(); err != nil {
+               return nil, err
+       }
+
+       if err := b.chain.SignBlockHeader(&b.block.BlockHeader); err != nil {
+               return nil, err
+       }
+
+       return b.block, nil
+}
+
+func (b *blockBuilder) calcBlockCommitment() (err error) {
+       var txEntries []*bc.Tx
+       for _, tx := range b.block.Transactions {
+               txEntries = append(txEntries, tx.Tx)
+       }
+
+       b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
+       if err != nil {
+               return err
+       }
+
+       b.block.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(b.txStatus.VerifyStatus)
+       return err
+}
 
 // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
 // based on the passed block height to the provided address.  When the address
 // is nil, the coinbase transaction will instead be redeemable by anyone.
-func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
+func (b *blockBuilder) createCoinbaseTx() (*types.Tx, error) {
+       consensusResult, err := b.chain.GetConsensusResultByHash(&b.block.PreviousBlockHash)
+       if err != nil {
+               return nil, err
+       }
+
+       rewards, err := consensusResult.GetCoinbaseRewards(b.block.Height - 1)
+       if err != nil {
+               return nil, err
+       }
+
+       return createCoinbaseTxByReward(b.accountManager, b.block.Height, rewards)
+}
+
+func (b *blockBuilder) getTimeoutStatus() uint8 {
+       if b.timeoutStatus == timeoutCritical {
+               return b.timeoutStatus
+       }
+
+       select {
+       case <-b.criticalTimeoutCh:
+               b.timeoutStatus = timeoutCritical
+       case <-b.warnTimeoutCh:
+               b.timeoutStatus = timeoutWarn
+       default:
+       }
+
+       return b.timeoutStatus
+}
+
+func createCoinbaseTxByReward(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
        arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(blockHeight, 10))...)
        var script []byte
        if accountManager == nil {
@@ -45,6 +254,7 @@ func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64, rewar
        if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
                return nil, err
        }
+
        if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, 0, script)); err != nil {
                return nil, err
        }
@@ -73,113 +283,63 @@ func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64, rewar
        return tx, nil
 }
 
-// NewBlockTemplate returns a new block template that is ready to be solved
-func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager, timestamp uint64) (b *types.Block, err error) {
-       view := state.NewUtxoViewpoint()
-       txStatus := bc.NewTransactionStatus()
-       if err := txStatus.SetStatus(0, false); err != nil {
-               return nil, err
-       }
-       txEntries := []*bc.Tx{nil}
-       gasUsed := uint64(0)
-
-       // get preblock info for generate next block
-       preBlockHeader := c.BestBlockHeader()
-       preBlockHash := preBlockHeader.Hash()
-       nextBlockHeight := preBlockHeader.Height + 1
-
-       b = &types.Block{
-               BlockHeader: types.BlockHeader{
-                       Version:           1,
-                       Height:            nextBlockHeight,
-                       PreviousBlockHash: preBlockHash,
-                       Timestamp:         timestamp,
-                       BlockCommitment:   types.BlockCommitment{},
-                       BlockWitness:      types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
-               },
-       }
-       bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: nextBlockHeight}}
-       b.Transactions = []*types.Tx{nil}
-
-       txs := txPool.GetTransactions()
-       sort.Sort(byTime(txs))
+type validateTxResult struct {
+       tx      *types.Tx
+       gasOnly bool
+       err     error
+}
 
-       entriesTxs := []*bc.Tx{}
-       for _, txDesc := range txs {
-               entriesTxs = append(entriesTxs, txDesc.Tx.Tx)
+func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
+       var results []*validateTxResult
+       bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
+       bcTxs := make([]*bc.Tx, len(txs))
+       for i, tx := range txs {
+               bcTxs[i] = tx.Tx
        }
 
-       validateResults := validation.ValidateTxs(entriesTxs, bcBlock)
-       for i, validateResult := range validateResults {
-               txDesc := txs[i]
-               tx := txDesc.Tx.Tx
+       validateResults := validation.ValidateTxs(bcTxs, bcBlock)
+       for i := 0; i < len(validateResults) && gasLeft > 0; i++ {
                gasOnlyTx := false
-
-               gasStatus := validateResult.GetGasState()
-               if validateResult.GetError() != nil {
+               gasStatus := validateResults[i].GetGasState()
+               if err := validateResults[i].GetError(); err != nil {
                        if !gasStatus.GasValid {
-                               blkGenSkipTxForErr(txPool, &tx.ID, err)
+                               results = append(results, &validateTxResult{tx: txs[i], err: err})
                                continue
                        }
                        gasOnlyTx = true
                }
 
-               if err := c.GetTransactionsUtxo(view, []*bc.Tx{tx}); err != nil {
-                       blkGenSkipTxForErr(txPool, &tx.ID, err)
+               if err := chain.GetTransactionsUtxo(view, []*bc.Tx{bcTxs[i]}); err != nil {
+                       results = append(results, &validateTxResult{tx: txs[i], err: err})
                        continue
                }
 
-               if gasUsed+uint64(gasStatus.GasUsed) > consensus.ActiveNetParams.MaxBlockGas {
+               if gasLeft-gasStatus.GasUsed < 0 {
                        break
                }
 
-               if err := view.ApplyTransaction(bcBlock, tx, gasOnlyTx); err != nil {
-                       blkGenSkipTxForErr(txPool, &tx.ID, err)
+               if err := view.ApplyTransaction(bcBlock, bcTxs[i], gasOnlyTx); err != nil {
+                       results = append(results, &validateTxResult{tx: txs[i], err: err})
                        continue
                }
 
-               if err := txStatus.SetStatus(len(b.Transactions), gasOnlyTx); err != nil {
-                       return nil, err
-               }
-
-               b.Transactions = append(b.Transactions, txDesc.Tx)
-               txEntries = append(txEntries, tx)
-               gasUsed += uint64(gasStatus.GasUsed)
-               if gasUsed == consensus.ActiveNetParams.MaxBlockGas {
-                       break
+               if err := b.validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
+                       results = append(results, &validateTxResult{tx: txs[i], err: err})
+                       continue
                }
 
+               results = append(results, &validateTxResult{tx: txs[i], gasOnly: gasOnlyTx, err: validateResults[i].GetError()})
+               gasLeft -= gasStatus.GasUsed
        }
-
-       consensusResult, err := c.GetConsensusResultByHash(&preBlockHash)
-       if err != nil {
-               return nil, err
-       }
-
-       rewards, err := consensusResult.GetCoinbaseRewards(preBlockHeader.Height)
-       if err != nil {
-               return nil, err
-       }
-
-       // create coinbase transaction
-       b.Transactions[0], err = createCoinbaseTx(accountManager, nextBlockHeight, rewards)
-       if err != nil {
-               return nil, errors.Wrap(err, "fail on createCoinbaseTx")
-       }
-
-       txEntries[0] = b.Transactions[0].Tx
-       b.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
-       if err != nil {
-               return nil, err
-       }
-
-       b.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(txStatus.VerifyStatus)
-
-       _, err = c.SignBlock(b)
-       return b, err
+       return results, gasLeft
 }
 
-func blkGenSkipTxForErr(txPool *protocol.TxPool, txHash *bc.Hash, err error) {
-       log.WithFields(log.Fields{"module": logModule, "error": err}).Error("mining block generation: skip tx due to")
-       txPool.RemoveTransaction(txHash)
+func (b *blockBuilder) validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
+       for _, subProtocol := range subProtocols {
+               verifyResult := &bc.TxVerifyResult{StatusFail: statusFail}
+               if err := subProtocol.ValidateTx(tx, verifyResult, b.block.Height); err != nil {
+                       return err
+               }
+       }
+       return nil
 }
index 73de939..026c627 100644 (file)
@@ -283,7 +283,7 @@ func TestCountCoinbaseTxRewards(t *testing.T) {
                }
 
                // create coinbase transaction
-               c.block.Transactions[0], err = createCoinbaseTx(nil, c.block.Height, rewards)
+               c.block.Transactions[0], err = createCoinbaseTxByReward(nil, c.block.Height, rewards)
                if err != nil {
                        t.Fatal(err)
                }
diff --git a/protocol/asset_filter.go b/protocol/asset_filter.go
new file mode 100644 (file)
index 0000000..cd9fccd
--- /dev/null
@@ -0,0 +1,42 @@
+package protocol
+
+import (
+       "strings"
+
+       "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/protocol/bc/types"
+)
+
+// AssetFilter is struct for allow open federation asset cross chain
+type AssetFilter struct {
+       whitelist map[string]struct{}
+}
+
+// NewAssetFilter returns a assetFilter according a whitelist,
+// which is a strings list cancated via comma
+func NewAssetFilter(whitelist string) *AssetFilter {
+       af := &AssetFilter{whitelist: make(map[string]struct{})}
+       af.whitelist[consensus.BTMAssetID.String()] = struct{}{}
+       for _, assetID := range strings.Split(whitelist, ",") {
+               af.whitelist[strings.ToLower(assetID)] = struct{}{}
+       }
+       return af
+}
+
+// IsDust implements the DustFilterer interface.
+// It filters a transaction as long as there is one asset neither BTM or in the whitelist
+// No need to check the output assets types becauese they must have been cover in input assets types
+func (af *AssetFilter) IsDust(tx *types.Tx) bool {
+       for _, input := range tx.Inputs {
+               if _, ok := input.TypedInput.(*types.CrossChainInput); !ok {
+                       continue
+               }
+
+               assetID := input.AssetID()
+               if _, ok := af.whitelist[assetID.String()]; !ok {
+                       return true
+               }
+       }
+
+       return false
+}
index d62b3ed..06b4859 100644 (file)
@@ -142,8 +142,13 @@ func (c *Chain) validateSign(block *types.Block) error {
 
                if err := c.checkNodeSign(&block.BlockHeader, node, block.Get(node.Order)); err == errDoubleSignBlock {
                        log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String(), "pubKey": pubKey}).Warn("the consensus node double sign the same height of different block")
-                       block.BlockWitness.Delete(node.Order)
-                       continue
+                       // if the blocker double sign & become the mainchain, that means
+                       // all the side chain will reject the main chain make the chain
+                       // fork. All the node will ban each other & can't roll back
+                       if blocker != pubKey {
+                               block.BlockWitness.Delete(node.Order)
+                               continue
+                       }
                } else if err != nil {
                        return err
                }
@@ -199,27 +204,54 @@ func (c *Chain) ProcessBlockSignature(signature, xPub []byte, blockHash *bc.Hash
        return c.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: *blockHash, Signature: signature, XPub: xPub})
 }
 
-// SignBlock signing the block if current node is consensus node
-func (c *Chain) SignBlock(block *types.Block) ([]byte, error) {
+// SignBlockHeader signing the block if current node is consensus node
+func (c *Chain) SignBlockHeader(blockHeader *types.BlockHeader) error {
+       _, err := c.signBlockHeader(blockHeader)
+       return err
+}
+
+func (c *Chain) applyBlockSign(blockHeader *types.BlockHeader) error {
+       signature, err := c.signBlockHeader(blockHeader)
+       if err != nil {
+               return err
+       }
+
+       if len(signature) == 0 {
+               return nil
+       }
+
+       if err := c.store.SaveBlockHeader(blockHeader); err != nil {
+               return err
+       }
+
+       xpub := config.CommonConfig.PrivateKey().XPub()
+       return c.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: blockHeader.Hash(), Signature: signature, XPub: xpub[:]})
+}
+
+func (c *Chain) signBlockHeader(blockHeader *types.BlockHeader) ([]byte, error) {
        xprv := config.CommonConfig.PrivateKey()
-       xpubStr := xprv.XPub().String()
-       node, err := c.getConsensusNode(&block.PreviousBlockHash, xpubStr)
+       xpub := xprv.XPub()
+       node, err := c.getConsensusNode(&blockHeader.PreviousBlockHash, xpub.String())
+       blockHash := blockHeader.Hash()
        if err == errNotFoundConsensusNode {
+               log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String()}).Debug("can't find consensus node of current node")
                return nil, nil
        } else if err != nil {
                return nil, err
        }
 
-       if err := c.checkDoubleSign(&block.BlockHeader, node.XPub.String()); err == errDoubleSignBlock {
+       if len(blockHeader.Get(node.Order)) != 0 {
+               return nil, nil
+       }
+
+       if err := c.checkDoubleSign(blockHeader, node.XPub.String()); err == errDoubleSignBlock {
+               log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String()}).Warn("current node has double sign the block")
                return nil, nil
        } else if err != nil {
                return nil, err
        }
 
-       signature := block.Get(node.Order)
-       if len(signature) == 0 {
-               signature = xprv.Sign(block.Hash().Bytes())
-               block.Set(node.Order, signature)
-       }
+       signature := xprv.Sign(blockHeader.Hash().Bytes())
+       blockHeader.Set(node.Order, signature)
        return signature, nil
 }
index b65b3b7..d302d9b 100644 (file)
@@ -698,6 +698,7 @@ type CrossChainInput struct {
        AssetDefinition    *AssetDefinition  `protobuf:"bytes,4,opt,name=asset_definition,json=assetDefinition" json:"asset_definition,omitempty"`
        WitnessArguments   [][]byte          `protobuf:"bytes,5,rep,name=witness_arguments,json=witnessArguments,proto3" json:"witness_arguments,omitempty"`
        Ordinal            uint64            `protobuf:"varint,6,opt,name=ordinal" json:"ordinal,omitempty"`
+       RawDefinitionByte  []byte            `protobuf:"bytes,7,opt,name=rawDefinitionByte,proto3" json:"rawDefinitionByte,omitempty"`
 }
 
 func (m *CrossChainInput) Reset()                    { *m = CrossChainInput{} }
@@ -747,6 +748,13 @@ func (m *CrossChainInput) GetOrdinal() uint64 {
        return 0
 }
 
+func (m *CrossChainInput) GetRawDefinitionByte() []byte {
+       if m != nil {
+               return m.RawDefinitionByte
+       }
+       return nil
+}
+
 func init() {
        proto.RegisterType((*Hash)(nil), "bc.Hash")
        proto.RegisterType((*Program)(nil), "bc.Program")
@@ -773,65 +781,66 @@ func init() {
 func init() { proto.RegisterFile("bc.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-       // 951 bytes of a gzipped FileDescriptorProto
-       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x5f, 0x6f, 0x23, 0x35,
-       0x10, 0x57, 0x36, 0xdb, 0x24, 0x9d, 0xf4, 0x9a, 0xc4, 0xbd, 0x83, 0xd5, 0xe9, 0x10, 0xd5, 0x4a,
-       0x47, 0x0f, 0x21, 0x55, 0xfd, 0x73, 0xfc, 0x79, 0x40, 0x88, 0xd2, 0x72, 0x5c, 0x1e, 0x4e, 0x87,
-       0xdc, 0x2a, 0xaf, 0x2b, 0x67, 0xd7, 0x49, 0x2c, 0x92, 0x75, 0xb0, 0xbd, 0xa1, 0xd7, 0xaf, 0xc0,
-       0x33, 0x0f, 0x7c, 0x22, 0x1e, 0x10, 0xe2, 0x23, 0x81, 0x3c, 0xeb, 0x4d, 0x36, 0x7f, 0xda, 0x82,
-       0x00, 0x01, 0x6f, 0x3b, 0xe3, 0xf1, 0x6f, 0x7e, 0xf3, 0xf3, 0x8c, 0xd7, 0xd0, 0xe8, 0xc7, 0x87,
-       0x53, 0x25, 0x8d, 0x24, 0x5e, 0x3f, 0x0e, 0x5f, 0x80, 0xff, 0x92, 0xe9, 0x11, 0xd9, 0x05, 0x6f,
-       0x76, 0x14, 0x54, 0xf6, 0x2b, 0xcf, 0x6a, 0xd4, 0x9b, 0x1d, 0xa1, 0x7d, 0x1c, 0x78, 0xce, 0x3e,
-       0x46, 0xfb, 0x24, 0xa8, 0x3a, 0xfb, 0x04, 0xed, 0xd3, 0xc0, 0x77, 0xf6, 0x69, 0xf8, 0x29, 0xd4,
-       0xbf, 0x56, 0x72, 0xa8, 0xd8, 0x84, 0xbc, 0x03, 0x30, 0x9b, 0x44, 0x33, 0xae, 0xb4, 0x90, 0x29,
-       0x42, 0xfa, 0x74, 0x7b, 0x36, 0xe9, 0xe5, 0x0e, 0x42, 0xc0, 0x8f, 0x65, 0xc2, 0x11, 0x7b, 0x87,
-       0xe2, 0x77, 0xd8, 0x85, 0xfa, 0x99, 0xd6, 0xdc, 0x74, 0x2f, 0xfe, 0x32, 0x91, 0x57, 0xd0, 0x44,
-       0xa8, 0xb3, 0x89, 0xcc, 0x52, 0x43, 0xde, 0x83, 0x06, 0xb3, 0x66, 0x24, 0x12, 0x04, 0x6d, 0x9e,
-       0x34, 0x0f, 0xfb, 0xf1, 0xa1, 0xcb, 0x46, 0xeb, 0xb8, 0xd8, 0x4d, 0xc8, 0x5b, 0x50, 0x63, 0xb8,
-       0x03, 0x53, 0xf9, 0xd4, 0x59, 0xe1, 0x10, 0x5a, 0x18, 0x7b, 0xc1, 0x07, 0x22, 0x15, 0xc6, 0x16,
-       0xf0, 0x11, 0xb4, 0x85, 0xd6, 0x19, 0x4b, 0x63, 0x1e, 0x4d, 0xf3, 0x9a, 0xcb, 0xd0, 0x4e, 0x06,
-       0xda, 0x2a, 0x82, 0x0a, 0x5d, 0x9e, 0x80, 0x9f, 0x30, 0xc3, 0x30, 0x41, 0xf3, 0xa4, 0x61, 0x63,
-       0xad, 0xf4, 0x14, 0xbd, 0xe1, 0x18, 0x9a, 0x3d, 0x36, 0xce, 0xf8, 0xa5, 0xcc, 0x54, 0xcc, 0xc9,
-       0x63, 0xa8, 0x2a, 0x3e, 0x70, 0xb8, 0x8b, 0x58, 0xeb, 0x24, 0x4f, 0x61, 0x6b, 0x66, 0x43, 0x1d,
-       0x52, 0x6b, 0x5e, 0x50, 0x5e, 0x33, 0xcd, 0x57, 0xc9, 0x63, 0x68, 0x4c, 0xa5, 0x46, 0xce, 0xa8,
-       0x97, 0x4f, 0xe7, 0x76, 0xf8, 0x2d, 0xb4, 0x31, 0xdb, 0x05, 0xd7, 0x46, 0xa4, 0x0c, 0xeb, 0xfa,
-       0x87, 0x53, 0xfe, 0xe6, 0x41, 0xf3, 0x8b, 0xb1, 0x8c, 0xbf, 0x79, 0xc9, 0x59, 0xc2, 0x15, 0x09,
-       0xa0, 0xbe, 0xdc, 0x23, 0x85, 0x69, 0xcf, 0x62, 0xc4, 0xc5, 0x70, 0x34, 0x3f, 0x8b, 0xdc, 0x22,
-       0xcf, 0xa1, 0x33, 0x55, 0x7c, 0x26, 0x64, 0xa6, 0xa3, 0xbe, 0x45, 0xb2, 0x87, 0x5a, 0x5d, 0xa1,
-       0xdb, 0x2a, 0x42, 0x30, 0x57, 0x37, 0x21, 0x4f, 0x60, 0xdb, 0x88, 0x09, 0xd7, 0x86, 0x4d, 0xa6,
-       0xd8, 0x27, 0x3e, 0x5d, 0x38, 0xc8, 0x87, 0xd0, 0x31, 0x8a, 0xa5, 0x9a, 0xc5, 0x96, 0xa4, 0x8e,
-       0x94, 0x94, 0x26, 0xd8, 0x5a, 0xc1, 0x6c, 0x97, 0x43, 0xa8, 0x94, 0x86, 0x7c, 0x0e, 0x6f, 0x97,
-       0x7c, 0x91, 0x36, 0xcc, 0x64, 0x3a, 0x1a, 0x31, 0x3d, 0x0a, 0x6a, 0x2b, 0x9b, 0x1f, 0x95, 0x02,
-       0x2f, 0x31, 0x0e, 0x07, 0xee, 0x02, 0xc8, 0x3a, 0x42, 0x50, 0xc7, 0xcd, 0x8f, 0xec, 0xe6, 0xab,
-       0xd5, 0x6d, 0xb4, 0xb3, 0x86, 0x44, 0x3e, 0x80, 0xce, 0x77, 0xc2, 0xa4, 0x5c, 0xeb, 0x88, 0xa9,
-       0x61, 0x36, 0xe1, 0xa9, 0xd1, 0x41, 0x63, 0xbf, 0xfa, 0x6c, 0x87, 0xb6, 0xdd, 0xc2, 0x59, 0xe1,
-       0x0f, 0x7f, 0xa8, 0x40, 0xe3, 0xea, 0xfa, 0x5e, 0xf9, 0x0f, 0xa0, 0xa5, 0xb9, 0x12, 0x6c, 0x2c,
-       0x6e, 0x78, 0x12, 0x69, 0x71, 0xc3, 0xdd, 0x39, 0xec, 0x2e, 0xdc, 0x97, 0xe2, 0x86, 0xdb, 0x41,
-       0xb7, 0x42, 0x46, 0x8a, 0xa5, 0x43, 0xee, 0xce, 0x1b, 0xa5, 0xa5, 0xd6, 0x41, 0x0e, 0x00, 0x14,
-       0xd7, 0xd9, 0xd8, 0xce, 0x9e, 0x0e, 0xfc, 0xfd, 0xea, 0x92, 0x2c, 0xdb, 0xf9, 0x5a, 0x37, 0xd1,
-       0xe1, 0x31, 0xec, 0x5e, 0x5d, 0xf7, 0xb8, 0x12, 0x83, 0x37, 0x14, 0x9d, 0xe4, 0x5d, 0x68, 0x3a,
-       0x49, 0x07, 0x4c, 0x8c, 0x91, 0x60, 0x83, 0x42, 0xee, 0x7a, 0xc1, 0xc4, 0x38, 0x1c, 0x40, 0x67,
-       0x4d, 0x9f, 0x3b, 0x4a, 0xfa, 0x18, 0x1e, 0xcc, 0x10, 0xbf, 0xd0, 0xd9, 0x43, 0x36, 0x04, 0x75,
-       0x5e, 0x4a, 0x4d, 0x77, 0xf2, 0xc0, 0x1c, 0x32, 0xfc, 0xa5, 0x02, 0xd5, 0x57, 0xd9, 0x35, 0x79,
-       0x1f, 0xea, 0x1a, 0x07, 0x53, 0x07, 0x15, 0xdc, 0x8a, 0x13, 0x50, 0x1a, 0x58, 0x5a, 0xac, 0x93,
-       0xa7, 0x50, 0x2f, 0x6e, 0x05, 0x6f, 0xfd, 0x56, 0x28, 0xd6, 0xc8, 0x57, 0xf0, 0xb0, 0x38, 0xb9,
-       0x64, 0x31, 0x84, 0x3a, 0xa8, 0x22, 0xfc, 0xc3, 0x39, 0x7c, 0x69, 0x42, 0xe9, 0x9e, 0xdb, 0x51,
-       0xf2, 0xdd, 0xd2, 0x02, 0xfe, 0x2d, 0x2d, 0x20, 0xa1, 0x71, 0x2e, 0x45, 0xda, 0x67, 0x9a, 0x93,
-       0x2f, 0x61, 0x6f, 0x03, 0x03, 0x37, 0xff, 0x9b, 0x09, 0x90, 0x75, 0x02, 0x76, 0xbe, 0x98, 0xea,
-       0x0b, 0xa3, 0x98, 0x7a, 0xe3, 0x2e, 0xf5, 0x85, 0x23, 0xfc, 0xbe, 0x02, 0xed, 0x6e, 0x6a, 0x14,
-       0x3b, 0x1f, 0x31, 0x91, 0xbe, 0xce, 0xcc, 0x34, 0x33, 0xe4, 0x00, 0x6a, 0xb9, 0x5a, 0x2e, 0xd9,
-       0x9a, 0x98, 0x6e, 0x99, 0x3c, 0x87, 0x56, 0x2c, 0x53, 0xa3, 0xe4, 0x38, 0xba, 0x43, 0xd3, 0x5d,
-       0x17, 0x53, 0x5c, 0xb4, 0x01, 0xd4, 0xa5, 0x4a, 0x44, 0xca, 0xc6, 0xae, 0x29, 0x0b, 0x13, 0xd9,
-       0x9c, 0x2b, 0xa9, 0xf5, 0x7f, 0x82, 0xcd, 0x8f, 0x15, 0x80, 0x9e, 0x34, 0xfc, 0x5f, 0xe6, 0x61,
-       0xff, 0xc8, 0x33, 0x69, 0x38, 0x5e, 0x8e, 0x3b, 0x14, 0xbf, 0xc3, 0x9f, 0x2b, 0xb0, 0xdd, 0xe3,
-       0x46, 0x76, 0x53, 0x4b, 0xed, 0x08, 0x5a, 0x7a, 0xca, 0x53, 0x13, 0x49, 0xa4, 0xba, 0xf8, 0x99,
-       0x2e, 0xe6, 0xf9, 0x01, 0x06, 0xe4, 0xa5, 0x74, 0x93, 0xdb, 0x9a, 0xcb, 0xfb, 0x93, 0xcd, 0xb5,
-       0xb1, 0xb9, 0xab, 0x9b, 0x9b, 0xbb, 0x5c, 0xa1, 0xbf, 0xac, 0xf4, 0x6b, 0x00, 0xca, 0x8d, 0x50,
-       0xdc, 0x06, 0xfe, 0x71, 0xa1, 0x4b, 0x80, 0xde, 0x32, 0xe0, 0x4f, 0x15, 0xd8, 0xba, 0x9c, 0xf2,
-       0x34, 0xf9, 0xdf, 0x4b, 0xf3, 0xab, 0x07, 0xad, 0xc5, 0x48, 0xe4, 0xc7, 0xfd, 0x09, 0xec, 0x4d,
-       0x98, 0x48, 0x63, 0xeb, 0xb9, 0xa3, 0xae, 0xce, 0x3c, 0xe8, 0xef, 0xae, 0x6d, 0x43, 0x87, 0x57,
-       0xef, 0xef, 0xf0, 0xcf, 0xa0, 0x9d, 0xbf, 0xf5, 0x92, 0xf9, 0x63, 0x0d, 0xab, 0x6d, 0x9e, 0xec,
-       0xcd, 0xdf, 0x2b, 0x8b, 0x77, 0x1c, 0x6d, 0xb1, 0x95, 0x87, 0xdd, 0x46, 0x45, 0xb7, 0xee, 0x57,
-       0xb4, 0xb6, 0xa4, 0x68, 0xbf, 0x86, 0xaf, 0xeb, 0xd3, 0xdf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x20,
-       0xd9, 0x30, 0x59, 0x69, 0x0b, 0x00, 0x00,
+       // 967 bytes of a gzipped FileDescriptorProto
+       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x4b, 0x6f, 0x23, 0x45,
+       0x10, 0x96, 0xc7, 0x13, 0xdb, 0x29, 0x7b, 0x63, 0xbb, 0xb3, 0x0b, 0xa3, 0xd5, 0x22, 0xa2, 0x91,
+       0x96, 0x2c, 0x02, 0x45, 0x89, 0xb3, 0x3c, 0x0e, 0x08, 0x91, 0x4d, 0x58, 0xd6, 0x87, 0xd5, 0xa2,
+       0x4e, 0xe4, 0xeb, 0xa8, 0x3d, 0xd3, 0xb6, 0x5b, 0xd8, 0xd3, 0xa6, 0xbb, 0xc7, 0x79, 0xfc, 0x05,
+       0xce, 0x1c, 0xf8, 0x45, 0x1c, 0x10, 0x3f, 0x09, 0x81, 0xba, 0x66, 0xc6, 0xe3, 0x57, 0x12, 0x10,
+       0x20, 0xd8, 0xdb, 0xd4, 0xa3, 0xbf, 0xaa, 0xfa, 0xaa, 0xaa, 0xa7, 0xa1, 0xd6, 0x0f, 0x0f, 0xa6,
+       0x4a, 0x1a, 0x49, 0x9c, 0x7e, 0xe8, 0xbf, 0x04, 0xf7, 0x15, 0xd3, 0x23, 0xb2, 0x03, 0xce, 0xec,
+       0xd0, 0x2b, 0xed, 0x95, 0x9e, 0x55, 0xa8, 0x33, 0x3b, 0x44, 0xf9, 0xc8, 0x73, 0x32, 0xf9, 0x08,
+       0xe5, 0x8e, 0x57, 0xce, 0xe4, 0x0e, 0xca, 0xc7, 0x9e, 0x9b, 0xc9, 0xc7, 0xfe, 0x17, 0x50, 0xfd,
+       0x56, 0xc9, 0xa1, 0x62, 0x13, 0xf2, 0x1e, 0xc0, 0x6c, 0x12, 0xcc, 0xb8, 0xd2, 0x42, 0xc6, 0x08,
+       0xe9, 0xd2, 0xed, 0xd9, 0xa4, 0x97, 0x2a, 0x08, 0x01, 0x37, 0x94, 0x11, 0x47, 0xec, 0x06, 0xc5,
+       0x6f, 0xbf, 0x0b, 0xd5, 0x13, 0xad, 0xb9, 0xe9, 0x9e, 0xfd, 0xed, 0x44, 0x5e, 0x43, 0x1d, 0xa1,
+       0x4e, 0x26, 0x32, 0x89, 0x0d, 0xf9, 0x00, 0x6a, 0xcc, 0x8a, 0x81, 0x88, 0x10, 0xb4, 0xde, 0xa9,
+       0x1f, 0xf4, 0xc3, 0x83, 0x2c, 0x1a, 0xad, 0xa2, 0xb1, 0x1b, 0x91, 0x77, 0xa0, 0xc2, 0xf0, 0x04,
+       0x86, 0x72, 0x69, 0x26, 0xf9, 0x43, 0x68, 0xa2, 0xef, 0x19, 0x1f, 0x88, 0x58, 0x18, 0x5b, 0xc0,
+       0xa7, 0xd0, 0x12, 0x5a, 0x27, 0x2c, 0x0e, 0x79, 0x30, 0x4d, 0x6b, 0x5e, 0x84, 0xce, 0x68, 0xa0,
+       0xcd, 0xdc, 0x29, 0xe7, 0xe5, 0x09, 0xb8, 0x11, 0x33, 0x0c, 0x03, 0xd4, 0x3b, 0x35, 0xeb, 0x6b,
+       0xa9, 0xa7, 0xa8, 0xf5, 0xc7, 0x50, 0xef, 0xb1, 0x71, 0xc2, 0xcf, 0x65, 0xa2, 0x42, 0x4e, 0x1e,
+       0x43, 0x59, 0xf1, 0x41, 0x86, 0x5b, 0xf8, 0x5a, 0x25, 0x79, 0x0a, 0x5b, 0x33, 0xeb, 0x9a, 0x21,
+       0x35, 0xe7, 0x05, 0xa5, 0x35, 0xd3, 0xd4, 0x4a, 0x1e, 0x43, 0x6d, 0x2a, 0x35, 0xe6, 0x8c, 0x7c,
+       0xb9, 0x74, 0x2e, 0xfb, 0xdf, 0x43, 0x0b, 0xa3, 0x9d, 0x71, 0x6d, 0x44, 0xcc, 0xb0, 0xae, 0x7f,
+       0x39, 0xe4, 0xef, 0x0e, 0xd4, 0x5f, 0x8c, 0x65, 0xf8, 0xdd, 0x2b, 0xce, 0x22, 0xae, 0x88, 0x07,
+       0xd5, 0xe5, 0x19, 0xc9, 0x45, 0xdb, 0x8b, 0x11, 0x17, 0xc3, 0xd1, 0xbc, 0x17, 0xa9, 0x44, 0x9e,
+       0x43, 0x7b, 0xaa, 0xf8, 0x4c, 0xc8, 0x44, 0x07, 0x7d, 0x8b, 0x64, 0x9b, 0x5a, 0x5e, 0x49, 0xb7,
+       0x99, 0xbb, 0x60, 0xac, 0x6e, 0x44, 0x9e, 0xc0, 0xb6, 0x11, 0x13, 0xae, 0x0d, 0x9b, 0x4c, 0x71,
+       0x4e, 0x5c, 0x5a, 0x28, 0xc8, 0x27, 0xd0, 0x36, 0x8a, 0xc5, 0x9a, 0x85, 0x36, 0x49, 0x1d, 0x28,
+       0x29, 0x8d, 0xb7, 0xb5, 0x82, 0xd9, 0x5a, 0x74, 0xa1, 0x52, 0x1a, 0xf2, 0x15, 0xbc, 0xbb, 0xa0,
+       0x0b, 0xb4, 0x61, 0x26, 0xd1, 0xc1, 0x88, 0xe9, 0x91, 0x57, 0x59, 0x39, 0xfc, 0x68, 0xc1, 0xf1,
+       0x1c, 0xfd, 0x70, 0xe1, 0xce, 0x80, 0xac, 0x23, 0x78, 0x55, 0x3c, 0xfc, 0xc8, 0x1e, 0xbe, 0x58,
+       0x3d, 0x46, 0xdb, 0x6b, 0x48, 0xe4, 0x23, 0x68, 0x5f, 0x0a, 0x13, 0x73, 0xad, 0x03, 0xa6, 0x86,
+       0xc9, 0x84, 0xc7, 0x46, 0x7b, 0xb5, 0xbd, 0xf2, 0xb3, 0x06, 0x6d, 0x65, 0x86, 0x93, 0x5c, 0xef,
+       0xff, 0x58, 0x82, 0xda, 0xc5, 0xd5, 0xbd, 0xf4, 0xef, 0x43, 0x53, 0x73, 0x25, 0xd8, 0x58, 0xdc,
+       0xf0, 0x28, 0xd0, 0xe2, 0x86, 0x67, 0x7d, 0xd8, 0x29, 0xd4, 0xe7, 0xe2, 0x86, 0xdb, 0x45, 0xb7,
+       0x44, 0x06, 0x8a, 0xc5, 0x43, 0x9e, 0xf5, 0x1b, 0xa9, 0xa5, 0x56, 0x41, 0xf6, 0x01, 0x14, 0xd7,
+       0xc9, 0xd8, 0xee, 0x9e, 0xf6, 0xdc, 0xbd, 0xf2, 0x12, 0x2d, 0xdb, 0xa9, 0xad, 0x1b, 0x69, 0xff,
+       0x08, 0x76, 0x2e, 0xae, 0x7a, 0x5c, 0x89, 0xc1, 0x35, 0x45, 0x25, 0x79, 0x1f, 0xea, 0x19, 0xa5,
+       0x03, 0x26, 0xc6, 0x98, 0x60, 0x8d, 0x42, 0xaa, 0x7a, 0xc9, 0xc4, 0xd8, 0x1f, 0x40, 0x7b, 0x8d,
+       0x9f, 0x3b, 0x4a, 0xfa, 0x0c, 0x1e, 0xcc, 0x10, 0x3f, 0xe7, 0xd9, 0xc1, 0x6c, 0x08, 0xf2, 0xbc,
+       0x14, 0x9a, 0x36, 0x52, 0xc7, 0x14, 0xd2, 0xff, 0xb5, 0x04, 0xe5, 0xd7, 0xc9, 0x15, 0xf9, 0x10,
+       0xaa, 0x1a, 0x17, 0x53, 0x7b, 0x25, 0x3c, 0x8a, 0x1b, 0xb0, 0xb0, 0xb0, 0x34, 0xb7, 0x93, 0xa7,
+       0x50, 0xcd, 0x6f, 0x05, 0x67, 0xfd, 0x56, 0xc8, 0x6d, 0xe4, 0x1b, 0x78, 0x98, 0x77, 0x2e, 0x2a,
+       0x96, 0x50, 0x7b, 0x65, 0x84, 0x7f, 0x38, 0x87, 0x5f, 0xd8, 0x50, 0xba, 0x9b, 0x9d, 0x58, 0xd0,
+       0xdd, 0x32, 0x02, 0xee, 0x2d, 0x23, 0x20, 0xa1, 0x76, 0x2a, 0x45, 0xdc, 0x67, 0x9a, 0x93, 0xaf,
+       0x61, 0x77, 0x43, 0x06, 0xd9, 0xfe, 0x6f, 0x4e, 0x80, 0xac, 0x27, 0x60, 0xf7, 0x8b, 0xa9, 0xbe,
+       0x30, 0x8a, 0xa9, 0xeb, 0xec, 0x52, 0x2f, 0x14, 0xfe, 0x0f, 0x25, 0x68, 0x75, 0x63, 0xa3, 0xd8,
+       0xe9, 0x88, 0x89, 0xf8, 0x4d, 0x62, 0xa6, 0x89, 0x21, 0xfb, 0x50, 0x49, 0xd9, 0xca, 0x82, 0xad,
+       0x91, 0x99, 0x99, 0xc9, 0x73, 0x68, 0x86, 0x32, 0x36, 0x4a, 0x8e, 0x83, 0x3b, 0x38, 0xdd, 0xc9,
+       0x7c, 0xf2, 0x8b, 0xd6, 0x83, 0xaa, 0x54, 0x91, 0x88, 0xd9, 0x38, 0x1b, 0xca, 0x5c, 0xc4, 0x6c,
+       0x4e, 0x95, 0xd4, 0xfa, 0x7f, 0x91, 0xcd, 0x4f, 0x25, 0x80, 0x9e, 0x34, 0xfc, 0x3f, 0xce, 0xc3,
+       0xfe, 0x91, 0x67, 0xd2, 0x70, 0xbc, 0x1c, 0x1b, 0x14, 0xbf, 0xfd, 0x5f, 0x4a, 0xb0, 0xdd, 0xe3,
+       0x46, 0x76, 0x63, 0x9b, 0xda, 0x21, 0x34, 0xf5, 0x94, 0xc7, 0x26, 0x90, 0x98, 0x6a, 0xf1, 0x33,
+       0x2d, 0xf6, 0xf9, 0x01, 0x3a, 0xa4, 0xa5, 0x74, 0xa3, 0xdb, 0x86, 0xcb, 0xf9, 0x8b, 0xc3, 0xb5,
+       0x71, 0xb8, 0xcb, 0x9b, 0x87, 0x7b, 0xb1, 0x42, 0x77, 0x99, 0xe9, 0x37, 0x00, 0x94, 0x1b, 0xa1,
+       0xb8, 0x75, 0xfc, 0xf3, 0x44, 0x2f, 0x00, 0x3a, 0xcb, 0x80, 0x3f, 0x97, 0x60, 0xeb, 0x7c, 0xca,
+       0xe3, 0xe8, 0xad, 0xa7, 0xe6, 0x37, 0x07, 0x9a, 0xc5, 0x4a, 0xa4, 0xed, 0xfe, 0x1c, 0x76, 0x27,
+       0x4c, 0xc4, 0xa1, 0xd5, 0xdc, 0x51, 0x57, 0x7b, 0xee, 0xf4, 0x4f, 0xd7, 0xb6, 0x61, 0xc2, 0xcb,
+       0xf7, 0x4f, 0xf8, 0x97, 0xd0, 0x4a, 0xdf, 0x7a, 0xd1, 0xfc, 0xb1, 0x86, 0xd5, 0xd6, 0x3b, 0xbb,
+       0xf3, 0xf7, 0x4a, 0xf1, 0x8e, 0xa3, 0x4d, 0xb6, 0xf2, 0xb0, 0xdb, 0xc8, 0xe8, 0xd6, 0xfd, 0x8c,
+       0x56, 0x96, 0xd7, 0xe9, 0x63, 0x68, 0x2b, 0x76, 0x59, 0xe0, 0xbe, 0xb8, 0x36, 0x1c, 0x7f, 0xec,
+       0x0d, 0xba, 0x6e, 0xe8, 0x57, 0xf0, 0x2d, 0x7e, 0xfc, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a,
+       0xe9, 0x2b, 0xb1, 0x97, 0x0b, 0x00, 0x00,
 }
index 59190d4..ddc90bd 100644 (file)
@@ -132,4 +132,5 @@ message CrossChainInput {
   AssetDefinition  asset_definition          = 4;
   repeated bytes   witness_arguments         = 5;
   uint64           ordinal                   = 6;
+  bytes            rawDefinitionByte         = 7;
 }
index e9014c2..08a7cc7 100644 (file)
@@ -22,11 +22,12 @@ func (cci *CrossChainInput) SetDestination(id *Hash, val *AssetAmount, pos uint6
 }
 
 // NewCrossChainInput creates a new CrossChainInput.
-func NewCrossChainInput(mainchainOutputID *Hash, prog *Program, ordinal uint64, assetDef *AssetDefinition) *CrossChainInput {
+func NewCrossChainInput(mainchainOutputID *Hash, prog *Program, ordinal uint64, assetDef *AssetDefinition, rawDefinitionByte []byte) *CrossChainInput {
        return &CrossChainInput{
                MainchainOutputId: mainchainOutputID,
                Ordinal:           ordinal,
                ControlProgram:    prog,
                AssetDefinition:   assetDef,
+               RawDefinitionByte: rawDefinitionByte,
        }
 }
index 34520be..8ce9fbc 100644 (file)
@@ -76,6 +76,7 @@ func TestEntryID(t *testing.T) {
                                        IssuanceProgram: &Program{VmVersion: 1, Code: []byte{1, 2, 3, 4}},
                                        Data:            &Hash{V0: 0, V1: 1, V2: 2, V3: 3},
                                },
+                               []byte{},
                        ),
                        expectEntryID: "14bb3f6e68f37d037b1f1539a21ab41e182b8d59d703a1af6c426d52cfc775d9",
                },
index 654da54..621935a 100644 (file)
@@ -102,6 +102,7 @@ func (b *Block) readFrom(r *blockchain.Reader) error {
        return nil
 }
 
+// WriteTo write block to io.Writer
 func (b *Block) WriteTo(w io.Writer) (int64, error) {
        ew := errors.NewWriter(w)
        if err := b.writeTo(ew, SerBlockFull); err != nil {
index a756cd3..f05ddc3 100644 (file)
@@ -6,6 +6,7 @@ import (
        "github.com/bytom/vapor/encoding/blockchain"
 )
 
+// BlockWitness save the consensus node sign
 type BlockWitness struct {
        // Witness is a vector of arguments  for validating this block.
        Witness [][]byte
@@ -21,6 +22,7 @@ func (bw *BlockWitness) writeTo(w io.Writer) error {
        return err
 }
 
+// Set save data to index position
 func (bw *BlockWitness) Set(index uint64, data []byte) {
        if uint64(len(bw.Witness)) <= index {
                newWitness := make([][]byte, index+1, index+1)
@@ -30,12 +32,14 @@ func (bw *BlockWitness) Set(index uint64, data []byte) {
        bw.Witness[index] = data
 }
 
+// Delete remove data from index position
 func (bw *BlockWitness) Delete(index uint64) {
        if uint64(len(bw.Witness)) > index {
                bw.Witness[index] = nil
        }
 }
 
+// Get return data from index position
 func (bw *BlockWitness) Get(index uint64) []byte {
        if uint64(len(bw.Witness)) > index {
                return bw.Witness[index]
index 6c3222b..017cbcb 100644 (file)
@@ -28,4 +28,5 @@ func NewCrossChainOutput(assetID bc.AssetID, amount uint64, controlProgram []byt
        }
 }
 
+// OutputType implement the txout interface
 func (it *CrossChainOutput) OutputType() uint8 { return CrossChainOutputType }
index 55e2773..efc74b3 100644 (file)
@@ -28,4 +28,5 @@ func NewIntraChainOutput(assetID bc.AssetID, amount uint64, controlProgram []byt
        }
 }
 
+// OutputType implement the txout interface
 func (it *IntraChainOutput) OutputType() uint8 { return IntraChainOutputType }
index 5a86689..65533e2 100644 (file)
@@ -163,7 +163,7 @@ func mapTx(tx *TxData) (headerID bc.Hash, hdr *bc.TxHeader, entryMap map[bc.Hash
                                },
                        }
 
-                       crossIn := bc.NewCrossChainInput(&mainchainOutputID, prog, uint64(i), assetDef)
+                       crossIn := bc.NewCrossChainInput(&mainchainOutputID, prog, uint64(i), assetDef, inp.AssetDefinition)
                        crossIn.WitnessArguments = inp.Arguments
                        crossInID := addEntry(crossIn)
                        muxSources[i] = &bc.ValueSource{
index ffeb595..673b2b5 100644 (file)
@@ -30,4 +30,5 @@ func NewVoteOutput(assetID bc.AssetID, amount uint64, controlProgram []byte, vot
        }
 }
 
+// OutputType implement the txout interface
 func (it *VoteOutput) OutputType() uint8 { return VoteOutputType }
index 937ba5a..52eab1b 100644 (file)
@@ -3,9 +3,7 @@ package protocol
 import (
        log "github.com/sirupsen/logrus"
 
-       "github.com/bytom/vapor/config"
        "github.com/bytom/vapor/errors"
-       "github.com/bytom/vapor/event"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/bc/types"
        "github.com/bytom/vapor/protocol/state"
@@ -106,10 +104,25 @@ func (c *Chain) connectBlock(block *types.Block) (err error) {
        if err != nil {
                return err
        }
+
        if err := consensusResult.ApplyBlock(block); err != nil {
                return err
        }
 
+       for _, p := range c.subProtocols {
+               if err := c.syncProtocolStatus(p); err != nil {
+                       return errors.Wrap(err, p.Name(), "sync sub protocol status")
+               }
+
+               if err := p.ApplyBlock(block); err != nil {
+                       return errors.Wrap(err, p.Name(), "sub protocol connect block")
+               }
+       }
+
+       if err := c.applyBlockSign(&block.BlockHeader); err != nil {
+               return err
+       }
+
        irrBlockHeader := c.lastIrrBlockHeader
        if c.isIrreversible(&block.BlockHeader) && block.Height > irrBlockHeader.Height {
                irrBlockHeader = &block.BlockHeader
@@ -125,51 +138,140 @@ func (c *Chain) connectBlock(block *types.Block) (err error) {
        return nil
 }
 
-func (c *Chain) reorganizeChain(blockHeader *types.BlockHeader) error {
-       attachBlockHeaders, detachBlockHeaders, err := c.calcReorganizeChain(blockHeader, c.bestBlockHeader)
+func (c *Chain) detachBlock(detachBlockHeader *types.BlockHeader, consensusResult *state.ConsensusResult, utxoView *state.UtxoViewpoint) (*types.Block, error) {
+       detachHash := detachBlockHeader.Hash()
+       block, err := c.store.GetBlock(&detachHash)
        if err != nil {
-               return err
+               return block, err
        }
 
+       detachBlock := types.MapBlock(block)
+       if err := consensusResult.DetachBlock(block); err != nil {
+               return block, err
+       }
+
+       if err := c.store.GetTransactionsUtxo(utxoView, detachBlock.Transactions); err != nil {
+               return block, err
+       }
+
+       txStatus, err := c.GetTransactionStatus(&detachBlock.ID)
+       if err != nil {
+               return block, err
+       }
+
+       if err := utxoView.DetachBlock(detachBlock, txStatus); err != nil {
+               return block, err
+       }
+
+       for _, p := range c.subProtocols {
+               if err := p.DetachBlock(block); err != nil {
+                       return block, errors.Wrap(err, p.Name(), "sub protocol detach block")
+               }
+       }
+
+       log.WithFields(log.Fields{"module": logModule, "height": detachBlockHeader.Height, "hash": detachHash.String()}).Debug("detach from mainchain")
+       return block, nil
+}
+
+func (c *Chain) syncSubProtocols() error {
+       for _, p := range c.subProtocols {
+               if err := c.syncProtocolStatus(p); err != nil {
+                       return errors.Wrap(err, p.Name(), "sync sub protocol status")
+               }
+       }
+       return nil
+}
+
+// Rollback rollback the chain from one blockHeight to targetBlockHeight
+// WARNING: we recommend to use this only in commond line
+func (c *Chain) Rollback(targetHeight uint64) error {
+       c.cond.L.Lock()
+       defer c.cond.L.Unlock()
+
        utxoView := state.NewUtxoViewpoint()
-       consensusResults := []*state.ConsensusResult{}
        consensusResult, err := c.getBestConsensusResult()
        if err != nil {
                return err
        }
 
-       txsToRestore := map[bc.Hash]*types.Tx{}
-       for _, detachBlockHeader := range detachBlockHeaders {
-               detachHash := detachBlockHeader.Hash()
-               b, err := c.store.GetBlock(&detachHash)
+       if err = c.syncSubProtocols(); err != nil {
+               return err
+       }
+
+       targetBlockHeader, err := c.GetHeaderByHeight(targetHeight)
+       if err != nil {
+               return err
+       }
+
+       _, deletedBlockHeaders, err := c.calcReorganizeChain(targetBlockHeader, c.bestBlockHeader)
+       if err != nil {
+               return err
+       }
+
+       deletedBlocks := []*types.Block{}
+       for _, deletedBlockHeader := range deletedBlockHeaders {
+               block, err := c.detachBlock(deletedBlockHeader, consensusResult, utxoView)
                if err != nil {
                        return err
                }
 
-               detachBlock := types.MapBlock(b)
-               if err := c.store.GetTransactionsUtxo(utxoView, detachBlock.Transactions); err != nil {
-                       return err
-               }
+               deletedBlocks = append(deletedBlocks, block)
+       }
 
-               txStatus, err := c.GetTransactionStatus(&detachBlock.ID)
-               if err != nil {
+       setIrrBlockHeader := c.lastIrrBlockHeader
+       if c.lastIrrBlockHeader.Height > targetBlockHeader.Height {
+               setIrrBlockHeader = targetBlockHeader
+       }
+
+       startSeq := state.CalcVoteSeq(c.bestBlockHeader.Height)
+
+       if err = c.setState(targetBlockHeader, setIrrBlockHeader, nil, utxoView, []*state.ConsensusResult{consensusResult.Fork()}); err != nil {
+               return err
+       }
+
+       for _, block := range deletedBlocks {
+               if err := c.store.DeleteBlock(block); err != nil {
                        return err
                }
+       }
 
-               if err := utxoView.DetachBlock(detachBlock, txStatus); err != nil {
+       endSeq := state.CalcVoteSeq(targetHeight)
+       for nowSeq := startSeq; nowSeq > endSeq; nowSeq-- {
+               if err := c.store.DeleteConsensusResult(nowSeq); err != nil {
                        return err
                }
+       }
 
-               if err := consensusResult.DetachBlock(b); err != nil {
+       return nil
+}
+
+func (c *Chain) reorganizeChain(blockHeader *types.BlockHeader) error {
+       attachBlockHeaders, detachBlockHeaders, err := c.calcReorganizeChain(blockHeader, c.bestBlockHeader)
+       if err != nil {
+               return err
+       }
+
+       utxoView := state.NewUtxoViewpoint()
+       consensusResults := []*state.ConsensusResult{}
+       consensusResult, err := c.getBestConsensusResult()
+       if err != nil {
+               return err
+       }
+
+       if err = c.syncSubProtocols(); err != nil {
+               return err
+       }
+
+       txsToRestore := map[bc.Hash]*types.Tx{}
+       for _, detachBlockHeader := range detachBlockHeaders {
+               b, err := c.detachBlock(detachBlockHeader, consensusResult, utxoView)
+               if err != nil {
                        return err
                }
 
                for _, tx := range b.Transactions {
                        txsToRestore[tx.ID] = tx
                }
-
-               blockHash := blockHeader.Hash()
-               log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height, "hash": blockHash.String()}).Debug("detach from mainchain")
        }
 
        txsToRemove := map[bc.Hash]*types.Tx{}
@@ -199,10 +301,20 @@ func (c *Chain) reorganizeChain(blockHeader *types.BlockHeader) error {
                        return err
                }
 
+               for _, p := range c.subProtocols {
+                       if err := p.ApplyBlock(b); err != nil {
+                               return errors.Wrap(err, p.Name(), "sub protocol attach block")
+                       }
+               }
+
                if consensusResult.IsFinalize() {
                        consensusResults = append(consensusResults, consensusResult.Fork())
                }
 
+               if err := c.applyBlockSign(attachBlockHeader); err != nil {
+                       return err
+               }
+
                if c.isIrreversible(attachBlockHeader) && attachBlockHeader.Height > irrBlockHeader.Height {
                        irrBlockHeader = attachBlockHeader
                }
@@ -275,22 +387,17 @@ func (c *Chain) saveBlock(block *types.Block) error {
                return errors.Sub(ErrBadBlock, err)
        }
 
-       signature, err := c.SignBlock(block)
-       if err != nil {
-               return errors.Sub(ErrBadBlock, err)
+       for _, p := range c.subProtocols {
+               if err := p.ValidateBlock(block, bcBlock.TransactionStatus.GetVerifyStatus()); err != nil {
+                       return errors.Wrap(err, "sub protocol save block")
+               }
        }
 
        if err := c.store.SaveBlock(block, bcBlock.TransactionStatus); err != nil {
                return err
        }
-       c.orphanManage.Delete(&bcBlock.ID)
 
-       if len(signature) != 0 {
-               xPub := config.CommonConfig.PrivateKey().XPub()
-               if err := c.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: block.Hash(), Signature: signature, XPub: xPub[:]}); err != nil {
-                       return err
-               }
-       }
+       c.orphanManage.Delete(&bcBlock.ID)
        return nil
 }
 
index e32826f..ba13dbe 100644 (file)
@@ -23,9 +23,11 @@ func (s *mStore) GetStoreStatus() *BlockStoreState                             {
 func (s *mStore) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil }
 func (s *mStore) GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error     { return nil }
 func (s *mStore) GetUtxo(*bc.Hash) (*storage.UtxoEntry, error)                 { return nil, nil }
-func (s *mStore) GetConsensusResult(uint64) (*state.ConsensusResult, error)              { return nil, nil }
+func (s *mStore) GetConsensusResult(uint64) (*state.ConsensusResult, error)    { return nil, nil }
 func (s *mStore) GetMainChainHash(uint64) (*bc.Hash, error)                    { return nil, nil }
 func (s *mStore) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error)            { return nil, nil }
+func (s *mStore) DeleteConsensusResult(seq uint64) error                       { return nil }
+func (s *mStore) DeleteBlock(*types.Block) error                               { return nil }
 func (s *mStore) SaveBlock(*types.Block, *bc.TransactionStatus) error          { return nil }
 func (s *mStore) SaveBlockHeader(blockHeader *types.BlockHeader) error {
        s.blockHeaders[blockHeader.Hash()] = blockHeader
index 76e3428..5647126 100644 (file)
@@ -299,7 +299,7 @@ func TestGetConsensusNodes(t *testing.T) {
                                        Height:            1202,
                                        PreviousBlockHash: testutil.MustDecodeHash("a5be1d1177eb027327baedb869f902f74850476d0b9432a30391a3165d3af7cc"),
                                },
-                               // fork chain, fork height in 1198, rollback 1200, 1199, append 1199, 1200 
+                               // fork chain, fork height in 1198, rollback 1200, 1199, append 1199, 1200
                                {
                                        Height:            1199,
                                        PreviousBlockHash: testutil.MustDecodeHash("ef24de31371b4d34363011b6c8b065b1acaad9264d9abae2253d584e0d3a8739"),
@@ -735,12 +735,20 @@ func (s *dummyStore) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error) {
        return nil, nil
 }
 
+func (s *dummyStore) DeleteConsensusResult(seq uint64) error {
+       return nil
+}
+
 func (s *dummyStore) SaveBlock(block *types.Block, _ *bc.TransactionStatus) error {
        hash := block.Hash()
        s.blocks[hash.String()] = block
        return nil
 }
 
+func (s *dummyStore) DeleteBlock(block *types.Block) error {
+       return nil
+}
+
 func (s *dummyStore) SaveBlockHeader(header *types.BlockHeader) error {
        hash := header.Hash()
        s.blockHeaders[hash.String()] = header
index 56c85fb..a1cbb70 100644 (file)
@@ -7,6 +7,7 @@ import (
 
        "github.com/bytom/vapor/common"
        "github.com/bytom/vapor/config"
+       "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/event"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/bc/types"
@@ -18,12 +19,25 @@ const (
        maxKnownTxs           = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
 )
 
+// Protocoler is interface for layer 2 consensus protocol
+type Protocoler interface {
+       Name() string
+       StartHeight() uint64
+       BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
+       ChainStatus() (uint64, *bc.Hash, error)
+       ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error
+       ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error
+       ApplyBlock(block *types.Block) error
+       DetachBlock(block *types.Block) error
+}
+
 // Chain provides functions for working with the Bytom block chain.
 type Chain struct {
        orphanManage   *OrphanManage
        txPool         *TxPool
        store          Store
        processBlockCh chan *processBlockMsg
+       subProtocols   []Protocoler
 
        signatureCache  *common.Cache
        eventDispatcher *event.Dispatcher
@@ -36,12 +50,13 @@ type Chain struct {
 }
 
 // NewChain returns a new Chain using store as the underlying storage.
-func NewChain(store Store, txPool *TxPool, eventDispatcher *event.Dispatcher) (*Chain, error) {
+func NewChain(store Store, txPool *TxPool, subProtocols []Protocoler, eventDispatcher *event.Dispatcher) (*Chain, error) {
        knownTxs, _ := common.NewOrderedSet(maxKnownTxs)
        c := &Chain{
                orphanManage:    NewOrphanManage(),
                txPool:          txPool,
                store:           store,
+               subProtocols:    subProtocols,
                signatureCache:  common.NewCache(maxSignatureCacheSize),
                eventDispatcher: eventDispatcher,
                processBlockCh:  make(chan *processBlockMsg, maxProcessBlockChSize),
@@ -67,6 +82,13 @@ func NewChain(store Store, txPool *TxPool, eventDispatcher *event.Dispatcher) (*
        if err != nil {
                return nil, err
        }
+
+       for _, p := range c.subProtocols {
+               if err := c.syncProtocolStatus(p); err != nil {
+                       return nil, errors.Wrap(err, p.Name(), "sync sub protocol status")
+               }
+       }
+
        go c.blockProcesser()
        return c, nil
 }
@@ -90,7 +112,13 @@ func (c *Chain) initChainStatus() error {
                return err
        }
 
-       consensusResults := []*state.ConsensusResult{&state.ConsensusResult{
+       for _, subProtocol := range c.subProtocols {
+               if err := subProtocol.ApplyBlock(genesisBlock); err != nil {
+                       return err
+               }
+       }
+
+       consensusResults := []*state.ConsensusResult{{
                Seq:            0,
                NumOfVote:      make(map[string]uint64),
                CoinbaseReward: make(map[string]uint64),
@@ -146,6 +174,11 @@ func (c *Chain) InMainChain(hash bc.Hash) bool {
        return *blockHash == hash
 }
 
+// SubProtocols return list of layer 2 consensus protocol
+func (c *Chain) SubProtocols() []Protocoler {
+       return c.subProtocols
+}
+
 // trace back to the tail of the chain from the given block header
 func (c *Chain) traceLongestChainTail(blockHeader *types.BlockHeader) (*types.BlockHeader, error) {
        longestTail, workQueue := blockHeader, []*types.BlockHeader{blockHeader}
@@ -182,6 +215,50 @@ func (c *Chain) markTransactions(txs ...*types.Tx) {
        }
 }
 
+func (c *Chain) syncProtocolStatus(subProtocol Protocoler) error {
+       if c.bestBlockHeader.Height < subProtocol.StartHeight() {
+               return nil
+       }
+
+       protocolHeight, protocolHash, err := subProtocol.ChainStatus()
+       if err != nil {
+               return errors.Wrap(err, "failed on get sub protocol status")
+       }
+
+       if *protocolHash == c.bestBlockHeader.Hash() {
+               return nil
+       }
+
+       for !c.InMainChain(*protocolHash) {
+               block, err := c.GetBlockByHash(protocolHash)
+               if err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "can't get block by hash in chain")
+               }
+
+               if err := subProtocol.DetachBlock(block); err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "sub protocol detach block err")
+               }
+
+               protocolHeight, protocolHash = block.Height-1, &block.PreviousBlockHash
+       }
+
+       for height := protocolHeight + 1; height <= c.bestBlockHeader.Height; height++ {
+               block, err := c.GetBlockByHeight(height)
+               if err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "can't get block by height in chain")
+               }
+
+               if err := subProtocol.ApplyBlock(block); err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "sub protocol apply block err")
+               }
+
+               blockHash := block.Hash()
+               protocolHeight, protocolHash = block.Height, &blockHash
+       }
+
+       return nil
+}
+
 // This function must be called with mu lock in above level
 func (c *Chain) setState(blockHeader, irrBlockHeader *types.BlockHeader, mainBlockHeaders []*types.BlockHeader, view *state.UtxoViewpoint, consensusResults []*state.ConsensusResult) error {
        if err := c.store.SaveChainStatus(blockHeader, irrBlockHeader, mainBlockHeaders, view, consensusResults); err != nil {
index 808b289..b108ab8 100644 (file)
@@ -9,6 +9,7 @@ import (
        "github.com/bytom/vapor/protocol/state"
 )
 
+// predefine errors
 var (
        ErrNotFoundConsensusResult = errors.New("can't find the vote result by given sequence")
 )
@@ -27,6 +28,8 @@ type Store interface {
        GetMainChainHash(uint64) (*bc.Hash, error)
        GetBlockHashesByHeight(uint64) ([]*bc.Hash, error)
 
+       DeleteConsensusResult(uint64) error
+       DeleteBlock(*types.Block) error
        SaveBlock(*types.Block, *bc.TransactionStatus) error
        SaveBlockHeader(*types.BlockHeader) error
        SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.ConsensusResult) error
index b5b4ec5..1206916 100644 (file)
@@ -52,6 +52,14 @@ func (c *Chain) validateTx(tx *types.Tx, bh *types.BlockHeader) (bool, error) {
                return false, err
        }
 
+       txVerifyResult := &bc.TxVerifyResult{StatusFail: err != nil}
+       for _, p := range c.subProtocols {
+               if err := p.ValidateTx(tx, txVerifyResult, bh.Height); err != nil {
+                       c.txPool.AddErrCache(&tx.ID, err)
+                       return false, err
+               }
+       }
+
        if err != nil {
                log.WithFields(log.Fields{"module": logModule, "tx_id": tx.Tx.ID.String(), "error": err}).Info("transaction status fail")
        }
index 841e549..9413fe4 100644 (file)
@@ -43,6 +43,12 @@ var (
        ErrDustTx = errors.New("transaction is dust tx")
 )
 
+// DustFilterer is inerface for dust transaction filter rule
+type DustFilterer interface {
+       IsDust(tx *types.Tx) bool
+}
+
+// TxMsgEvent is message wrap for subscribe event
 type TxMsgEvent struct{ TxMsg *TxPoolMsg }
 
 // TxDesc store tx and related info for mining strategy
@@ -76,11 +82,12 @@ type TxPool struct {
        orphans         map[bc.Hash]*orphanTx
        orphansByPrev   map[bc.Hash]map[bc.Hash]*orphanTx
        errCache        *lru.Cache
+       filters         []DustFilterer
        eventDispatcher *event.Dispatcher
 }
 
 // NewTxPool init a new TxPool
-func NewTxPool(store Store, dispatcher *event.Dispatcher) *TxPool {
+func NewTxPool(store Store, filters []DustFilterer, dispatcher *event.Dispatcher) *TxPool {
        tp := &TxPool{
                lastUpdated:     time.Now().Unix(),
                store:           store,
@@ -89,6 +96,7 @@ func NewTxPool(store Store, dispatcher *event.Dispatcher) *TxPool {
                orphans:         make(map[bc.Hash]*orphanTx),
                orphansByPrev:   make(map[bc.Hash]map[bc.Hash]*orphanTx),
                errCache:        lru.New(maxCachedErrTxs),
+               filters:         filters,
                eventDispatcher: dispatcher,
        }
        go tp.orphanExpireWorker()
@@ -210,9 +218,31 @@ func isTransactionZeroOutput(tx *types.Tx) bool {
        return false
 }
 
+func isNoGasStatusFail(tx *types.Tx, statusFail bool) bool {
+       if !statusFail {
+               return false
+       }
+
+       for _, input := range tx.TxData.Inputs {
+               if *consensus.BTMAssetID == input.AssetID() {
+                       return false
+               }
+       }
+       return true
+}
+
 //IsDust checks if a tx has zero output
 func (tp *TxPool) IsDust(tx *types.Tx) bool {
-       return isTransactionZeroOutput(tx)
+       if ok := isTransactionZeroOutput(tx); ok {
+               return ok
+       }
+
+       for _, filter := range tp.filters {
+               if ok := filter.IsDust(tx); ok {
+                       return ok
+               }
+       }
+       return false
 }
 
 func (tp *TxPool) processTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) {
@@ -249,6 +279,11 @@ func (tp *TxPool) ProcessTransaction(tx *types.Tx, statusFail bool, height, fee
                log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("dust tx")
                return false, nil
        }
+
+       if isNoGasStatusFail(tx, statusFail) {
+               log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("drop no gas status fail tx")
+               return false, nil
+       }
        return tp.processTransaction(tx, statusFail, height, fee)
 }
 
index 343eb07..50e448e 100644 (file)
@@ -122,6 +122,8 @@ func (s *mockStore) GetConsensusResult(uint64) (*state.ConsensusResult, error)
 func (s *mockStore) GetMainChainHash(uint64) (*bc.Hash, error)                    { return nil, nil }
 func (s *mockStore) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error)            { return nil, nil }
 func (s *mockStore) SaveBlock(*types.Block, *bc.TransactionStatus) error          { return nil }
+func (s *mockStore) DeleteConsensusResult(seq uint64) error                       { return nil }
+func (s *mockStore) DeleteBlock(*types.Block) error                               { return nil }
 func (s *mockStore) SaveBlockHeader(*types.BlockHeader) error                     { return nil }
 func (s *mockStore) SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.ConsensusResult) error {
        return nil
@@ -672,7 +674,9 @@ func (s *mockStore1) GetUtxo(*bc.Hash) (*storage.UtxoEntry, error)
 func (s *mockStore1) GetConsensusResult(uint64) (*state.ConsensusResult, error) { return nil, nil }
 func (s *mockStore1) GetMainChainHash(uint64) (*bc.Hash, error)                 { return nil, nil }
 func (s *mockStore1) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error)         { return nil, nil }
+func (s *mockStore1) DeleteBlock(*types.Block) error                            { return nil }
 func (s *mockStore1) SaveBlock(*types.Block, *bc.TransactionStatus) error       { return nil }
+func (s *mockStore1) DeleteConsensusResult(seq uint64) error                    { return nil }
 func (s *mockStore1) SaveBlockHeader(*types.BlockHeader) error                  { return nil }
 func (s *mockStore1) SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.ConsensusResult) error {
        return nil
index bb82479..44287e9 100644 (file)
@@ -3,8 +3,10 @@ package validation
 import (
        "fmt"
        "math"
+       "runtime"
        "sync"
 
+       "github.com/bytom/vapor/common"
        "github.com/bytom/vapor/config"
        "github.com/bytom/vapor/consensus"
        "github.com/bytom/vapor/errors"
@@ -13,10 +15,6 @@ import (
        "github.com/bytom/vapor/protocol/vm"
 )
 
-const (
-       validateWorkerNum = 32
-)
-
 // validate transaction error
 var (
        ErrTxVersion                 = errors.New("invalid transaction version")
@@ -278,8 +276,12 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                }
 
                prog := &bc.Program{
-                       VmVersion: e.ControlProgram.VmVersion,
-                       Code:      config.FederationWScript(config.CommonConfig),
+                       VmVersion: e.AssetDefinition.IssuanceProgram.VmVersion,
+                       Code:      e.AssetDefinition.IssuanceProgram.Code,
+               }
+
+               if !common.IsOpenFederationIssueAsset(e.RawDefinitionByte) {
+                       prog.Code = config.FederationWScript(config.CommonConfig)
                }
 
                if _, err := vm.Verify(NewTxVMContext(vs, e, prog, e.WitnessArguments), consensus.ActiveNetParams.DefaultGasCredit); err != nil {
@@ -663,6 +665,7 @@ func validateTxWorker(workCh chan *validateTxWork, resultCh chan *ValidateTxResu
 // ValidateTxs validates txs in async mode
 func ValidateTxs(txs []*bc.Tx, block *bc.Block) []*ValidateTxResult {
        txSize := len(txs)
+       validateWorkerNum := runtime.NumCPU()
        //init the goroutine validate worker
        var wg sync.WaitGroup
        workCh := make(chan *validateTxWork, txSize)
index 619a02e..6f5c925 100644 (file)
@@ -7,6 +7,7 @@ import (
        "github.com/davecgh/go-spew/spew"
 
        "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/crypto/ed25519/chainkd"
        "github.com/bytom/vapor/crypto/sha3pool"
        "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/protocol/bc"
@@ -877,6 +878,341 @@ func TestValidateTxVersion(t *testing.T) {
        }
 }
 
+func TestMagneticContractTx(t *testing.T) {
+       buyerArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 1},
+               RatioNumerator:   1,
+               RatioDenominator: 2,
+               SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+               SellerKey:        testutil.MustDecodeHexString("af1927316233365dd525d3b48f2869f125a656958ee3946286f42904c35b9c91"),
+       }
+
+       sellerArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 2},
+               RatioNumerator:   2,
+               RatioDenominator: 1,
+               SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+               SellerKey:        testutil.MustDecodeHexString("af1927316233365dd525d3b48f2869f125a656958ee3946286f42904c35b9c91"),
+       }
+
+       programBuyer, err := vmutil.P2WMCProgram(buyerArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       programSeller, err := vmutil.P2WMCProgram(sellerArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       cases := []struct {
+               desc  string
+               block *bc.Block
+               err   error
+       }{
+               {
+                       desc: "contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "first contract partial trade, second contract full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, programSeller),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "first contract full trade, second contract partial trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "cancel magnetic contract",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{testutil.MustDecodeHexString("851a14d69076507e202a94a884cdfb3b9f1ecbc1fb0634d2f0d1f9c1a275fdbdf921af0c5309d2d0a0deb85973cba23a4076d2c169c7f08ade2af4048d91d209"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "wrong signature with cancel magnetic contract",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{testutil.MustDecodeHexString("686b983a8de1893ef723144389fd1f07b12b048f52f389faa863243195931d5732dbfd15470b43ed63d5067900718cf94f137073f4a972d277bbd967b022545d"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+               {
+                       desc: "wrong output amount with contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, []byte{0x55}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+               {
+                       desc: "wrong output assetID with contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+                                                       types.NewSpendInput(nil, bc.Hash{V0: 30}, bc.AssetID{V0: 1}, 200000000, 0, []byte{0x51}),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(bc.AssetID{V0: 1}, 200000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, []byte{0x55}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+               {
+                       desc: "wrong output change program with first contract partial trade and second contract full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, []byte{0x55}),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+       }
+
+       for i, c := range cases {
+               if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
+                       t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
+               }
+       }
+}
+
+func TestRingMagneticContractTx(t *testing.T) {
+       aliceArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 1},
+               RatioNumerator:   2,
+               RatioDenominator: 1,
+               SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+               SellerKey:        testutil.MustDecodeHexString("960ecabafb88ba460a40912841afecebf0e84884178611ac97210e327c0d1173"),
+       }
+
+       bobArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 2},
+               RatioNumerator:   2,
+               RatioDenominator: 1,
+               SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+               SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
+       }
+
+       jackArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 3},
+               RatioNumerator:   1,
+               RatioDenominator: 4,
+               SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+               SellerKey:        testutil.MustDecodeHexString("9c19a91988c62046c2767bd7e9999b0c142891b9ebf467bfa59210b435cb0de7"),
+       }
+
+       aliceProgram, err := vmutil.P2WMCProgram(aliceArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       bobProgram, err := vmutil.P2WMCProgram(bobArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       jackProgram, err := vmutil.P2WMCProgram(jackArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       cases := []struct {
+               desc  string
+               block *bc.Block
+               err   error
+       }{
+               {
+                       desc: "contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, jackArgs.RequestedAsset, 100000000, 0, aliceProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, aliceArgs.RequestedAsset, 200000000, 0, bobProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(aliceArgs.RequestedAsset, 199800000, aliceArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(bobArgs.RequestedAsset, 399600000, bobArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(jackArgs.RequestedAsset, 99900000, jackArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000, []byte{0x51}),
+                                                       types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000, []byte{0x51}),
+                                                       types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+       }
+
+       for i, c := range cases {
+               if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
+                       t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
+               }
+       }
+}
+
+func TestValidateOpenFederationIssueAsset(t *testing.T) {
+       tx := &types.Tx{TxData: types.TxData{Version: 1}}
+       tx.Inputs = append(tx.Inputs, types.NewCrossChainInput(nil,
+               testutil.MustDecodeHash("449143cb95389d19a1939879681168f78cc62614f4e0fb41f17b3232528a709d"),
+               testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
+               100000000,
+               0,
+               1,
+               testutil.MustDecodeHexString("7b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b0a202020202269737375655f61737365745f616374696f6e223a20226f70656e5f66656465726174696f6e5f63726f73735f636861696e220a20207d2c0a2020226e616d65223a2022454f53222c0a20202271756f72756d223a20312c0a20202272656973737565223a202274727565222c0a20202273796d626f6c223a2022454f53220a7d"),
+               testutil.MustDecodeHexString("ae20d827c281d47f5de93f98544b20468feaac046bf8b89bd51102f6e971f09d215920be43bb856fe337b37f5f09040c2b6cdbe23eaf5aa4770b16ea51fdfc45514c295152ad"),
+       ))
+
+       tx.Outputs = append(tx.Outputs, types.NewIntraChainOutput(
+               testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
+               100000000,
+               testutil.MustDecodeHexString("0014d8dd58f374f58cffb1b1a7cc1e18a712b4fe67b5"),
+       ))
+
+       byteData, err := tx.MarshalText()
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       tx.SerializedSize = uint64(len(byteData))
+       tx = types.NewTx(tx.TxData)
+
+       xPrv := chainkd.XPrv(toByte64("f0293101b509a0e919b4775d849372f97c688af8bd85a9d369fc1a4528baa94c0d74dd09aa6eaeed582df47d391c816b916a0537302291b09743903b730333f9"))
+       signature := xPrv.Sign(tx.SigHash(0).Bytes())
+       tx.Inputs[0].SetArguments([][]byte{signature})
+       tx = types.NewTx(tx.TxData)
+
+       if _, err := ValidateTx(tx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
+               t.Fatal(err)
+       }
+}
+
+func toByte64(str string) [64]byte {
+       var result [64]byte
+       bytes := testutil.MustDecodeHexString(str)
+       for i := range bytes {
+               result[i] = bytes[i]
+       }
+       return result
+}
+
 // A txFixture is returned by sample (below) to produce a sample
 // transaction, which takes a separate, optional _input_ txFixture to
 // affect the transaction that's built. The components of the
index 50c000d..16dd185 100644 (file)
@@ -98,12 +98,17 @@ func NewTxVMContext(vs *validationState, entry bc.Entry, prog *bc.Program, args
 }
 
 func witnessProgram(prog []byte) []byte {
-       if segwit.IsP2WPKHScript(prog) {
-               if witnessProg, err := segwit.ConvertP2PKHSigProgram([]byte(prog)); err == nil {
+       switch {
+       case segwit.IsP2WPKHScript(prog):
+               if witnessProg, err := segwit.ConvertP2PKHSigProgram(prog); err == nil {
                        return witnessProg
                }
-       } else if segwit.IsP2WSHScript(prog) {
-               if witnessProg, err := segwit.ConvertP2SHProgram([]byte(prog)); err == nil {
+       case segwit.IsP2WSHScript(prog):
+               if witnessProg, err := segwit.ConvertP2SHProgram(prog); err == nil {
+                       return witnessProg
+               }
+       case segwit.IsP2WMCScript(prog):
+               if witnessProg, err := segwit.ConvertP2MCProgram(prog); err == nil {
                        return witnessProg
                }
        }
index 0845c9c..5a388ec 100644 (file)
@@ -2,6 +2,7 @@ package vm
 
 import (
        "math"
+       "math/big"
 
        "github.com/bytom/vapor/math/checked"
 )
@@ -457,3 +458,36 @@ func opWithin(vm *virtualMachine) error {
        }
        return vm.pushBool(x >= min && x < max, true)
 }
+
+func opMulFraction(vm *virtualMachine) error {
+       if err := vm.applyCost(8); err != nil {
+               return err
+       }
+
+       z, err := vm.popInt64(true)
+       if err != nil {
+               return err
+       }
+
+       if z == 0 {
+               return ErrDivZero
+       }
+
+       y, err := vm.popInt64(true)
+       if err != nil {
+               return err
+       }
+
+       x, err := vm.popInt64(true)
+       if err != nil {
+               return err
+       }
+
+       res := big.NewInt(x)
+       res.Mul(res, big.NewInt(y)).Quo(res, big.NewInt(z))
+       if !res.IsInt64() {
+               return ErrRange
+       }
+
+       return vm.pushInt64(res.Int64(), true)
+}
index 5275998..d15d119 100644 (file)
@@ -446,6 +446,40 @@ func TestNumericOps(t *testing.T) {
                        deferredCost: -18,
                        dataStack:    [][]byte{{1}},
                },
+       }, {
+               op: OP_MULFRACTION,
+               startVM: &virtualMachine{
+                       runLimit:  50000,
+                       dataStack: [][]byte{{15}, {2}, {3}},
+               },
+               wantVM: &virtualMachine{
+                       runLimit:     49992,
+                       deferredCost: -18,
+                       dataStack:    [][]byte{{10}},
+               },
+       }, {
+               op: OP_MULFRACTION,
+               startVM: &virtualMachine{
+                       runLimit:  50000,
+                       dataStack: [][]byte{Int64Bytes(-654), {3}, {2}},
+               },
+               wantVM: &virtualMachine{
+                       runLimit:     49992,
+                       deferredCost: -18,
+                       dataStack:    [][]byte{Int64Bytes(-981)},
+               },
+       }, {
+               op: OP_MULFRACTION,
+               startVM: &virtualMachine{
+                       runLimit:  50000,
+                       dataStack: [][]byte{Int64Bytes(-654), {3}, {}},
+               },
+               wantVM: &virtualMachine{
+                       runLimit:     49992,
+                       deferredCost: -18,
+                       dataStack:    [][]byte{},
+               },
+               wantErr: ErrDivZero,
        }}
 
        numops := []Op{
@@ -453,6 +487,7 @@ func TestNumericOps(t *testing.T) {
                OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT, OP_BOOLAND,
                OP_BOOLOR, OP_NUMEQUAL, OP_NUMEQUALVERIFY, OP_NUMNOTEQUAL, OP_LESSTHAN,
                OP_LESSTHANOREQUAL, OP_GREATERTHAN, OP_GREATERTHANOREQUAL, OP_MIN, OP_MAX, OP_WITHIN,
+               OP_MULFRACTION,
        }
 
        for _, op := range numops {
@@ -536,6 +571,14 @@ func TestRangeErrs(t *testing.T) {
                {fmt.Sprintf("%d 1 LSHIFT", int64(math.MinInt64)), true},
                {fmt.Sprintf("%d 1 LSHIFT", int64(math.MinInt64)/2), false},
                {fmt.Sprintf("%d 2 LSHIFT", int64(math.MinInt64)/2), true},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64)/2-1, 2, 1), false},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64)/2+1, 2, 1), true},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64)/2+1, 2, 1), false},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64)/2-1, 2, 1), true},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64), 3, 2), true},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64), 2, 3), false},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64), 3, 2), true},
+               {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64), 2, 3), false},
        }
 
        for i, c := range cases {
index 0659fb7..88dfe1e 100644 (file)
@@ -194,6 +194,7 @@ const (
        OP_MIN                Op = 0xa3
        OP_MAX                Op = 0xa4
        OP_WITHIN             Op = 0xa5
+       OP_MULFRACTION        Op = 0xa6
 
        OP_SHA256        Op = 0xa8
        OP_SHA3          Op = 0xaa
@@ -300,6 +301,7 @@ var (
                OP_MIN:                {OP_MIN, "MIN", opMin},
                OP_MAX:                {OP_MAX, "MAX", opMax},
                OP_WITHIN:             {OP_WITHIN, "WITHIN", opWithin},
+               OP_MULFRACTION:        {OP_MULFRACTION, "MULFRACTION", opMulFraction},
 
                OP_SHA256:        {OP_SHA256, "SHA256", opSha256},
                OP_SHA3:          {OP_SHA3, "SHA3", opSha3},
index 849ec69..21cc0d4 100644 (file)
@@ -3,6 +3,7 @@ package vmutil
 import (
        "github.com/bytom/vapor/crypto/ed25519"
        "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/vm"
 )
 
@@ -12,6 +13,15 @@ var (
        ErrMultisigFormat = errors.New("bad multisig program format")
 )
 
+// MagneticContractArgs is a struct for magnetic contract arguments
+type MagneticContractArgs struct {
+       RequestedAsset   bc.AssetID
+       RatioNumerator   int64
+       RatioDenominator int64
+       SellerProgram    []byte
+       SellerKey        []byte
+}
+
 // IsUnspendable checks if a contorl program is absolute failed
 func IsUnspendable(prog []byte) bool {
        return len(prog) > 0 && prog[0] == byte(vm.OP_FAIL)
@@ -133,3 +143,239 @@ func checkMultiSigParams(nrequired, npubkeys int64) error {
        }
        return nil
 }
+
+// P2WMCProgram return the segwit pay to magnetic contract
+func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
+       builder := NewBuilder()
+       builder.AddInt64(0)
+       builder.AddData(magneticContractArgs.RequestedAsset.Bytes())
+       builder.AddInt64(magneticContractArgs.RatioNumerator)
+       builder.AddInt64(magneticContractArgs.RatioDenominator)
+       builder.AddData(magneticContractArgs.SellerProgram)
+       builder.AddData(magneticContractArgs.SellerKey)
+       return builder.Build()
+}
+
+// P2MCProgram generates the script for control with magnetic contract
+//
+// MagneticContract source code:
+// contract MagneticContract(requestedAsset: Asset,
+//                           ratioNumerator: Integer,
+//                           ratioDenominator: Integer,
+//                           sellerProgram: Program,
+//                           standardProgram: Program,
+//                           sellerKey: PublicKey) locks valueAmount of valueAsset {
+//  clause partialTrade(exchangeAmount: Amount) {
+//   define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
+//   verify actualAmount > 0 && actualAmount < valueAmount
+//   define receiveAmount: Integer = exchangeAmount * 999 / 1000
+//   lock receiveAmount of requestedAsset with sellerProgram
+//   lock valueAmount-actualAmount of valueAsset with standardProgram
+//   unlock actualAmount of valueAsset
+//  }
+//  clause fullTrade() {
+//   define requestedAmount: Integer = valueAmount * ratioNumerator / ratioDenominator
+//   define requestedAmount: Integer = requestedAmount * 999 / 1000
+//   verify requestedAmount > 0
+//   lock requestedAmount of requestedAsset with sellerProgram
+//   unlock valueAmount of valueAsset
+//  }
+//  clause cancel(sellerSig: Signature) {
+//   verify checkTxSig(sellerKey, sellerSig)
+//   unlock valueAmount of valueAsset
+//  }
+// }
+//
+// contract stack flow:
+// 7                        [... <position> <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 7]
+// ROLL                     [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <position>]
+// TOALTSTACK               [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// 6                        [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 6]
+// ROLL                     [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector>]
+// DUP                      [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector> <clause selector>]
+// 2                        [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector> <clause selector> 2]
+// NUMEQUAL                 [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector> (<clause selector> == 2)]
+// JUMPIF:$cancel           [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector>]
+// JUMPIF:$fullTrade        [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// $partialTrade            [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// 6                        [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 6]
+// PICK                     [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset exchangeAmount]
+// 3                        [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset exchangeAmount 3]
+// ROLL                     [... exchangeAmount sellerKey standardProgram sellerProgram ratioNumerator requestedAsset exchangeAmount ratioDenominator]
+// 3                        [... exchangeAmount sellerKey standardProgram sellerProgram ratioNumerator requestedAsset exchangeAmount ratioDenominator 3]
+// ROLL                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset exchangeAmount ratioDenominator ratioNumerator]
+// MULFRACTION              [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount]
+// AMOUNT                   [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount]
+// OVER                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount]
+// 0                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount 0]
+// GREATERTHAN              [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0)]
+// 2                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) 2]
+// PICK                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount]
+// 2                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount 2]
+// ROLL                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount (actualAmount > 0) actualAmount valueAmount]
+// LESSTHAN                 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount (actualAmount > 0) (actualAmount < valueAmount)]
+// BOOLAND                  [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount ((actualAmount > 0) && (actualAmount < valueAmount))]
+// VERIFY                   [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount]
+// FROMALTSTACK             [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
+// DUP                      [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> <position>]
+// TOALTSTACK               [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
+// 6                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> 6]
+// ROLL                     [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount]
+// 999                      [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 999]
+// 1000                     [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 1000]
+// MULFRACTION              [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount]
+// 3                        [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount 3]
+// ROLL                     [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset]
+// 1                        [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1]
+// 5                        [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1 5]
+// ROLL                     [... sellerKey standardProgram actualAmount <position> receiveAmount requestedAsset 1 sellerProgram]
+// CHECKOUTPUT              [... sellerKey standardProgram actualAmount checkOutput(receiveAmount, requestedAsset, sellerProgram)]
+// VERIFY                   [... sellerKey standardProgram actualAmount]
+// FROMALTSTACK             [... sellerKey standardProgram actualAmount <position>]
+// 1                        [... sellerKey standardProgram actualAmount <position> 1]
+// ADD                      [... sellerKey standardProgram actualAmount (<position> + 1)]
+// AMOUNT                   [... sellerKey standardProgram actualAmount (<position> + 1) valueAmount]
+// 2                        [... sellerKey standardProgram actualAmount (<position> + 1) valueAmount 2]
+// ROLL                     [... sellerKey standardProgram (<position> + 1) valueAmount actualAmount]
+// SUB                      [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount)]
+// ASSET                    [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset]
+// 1                        [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset 1]
+// 4                        [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset 1 4]
+// ROLL                     [... sellerKey (<position> + 1) (valueAmount - actualAmount) valueAsset 1 standardProgram]
+// CHECKOUTPUT              [... sellerKey checkOutput((valueAmount - actualAmount), valueAsset, standardProgram)]
+// JUMP:$_end               [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// $fullTrade               [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// AMOUNT                   [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset valueAmount]
+// 2                        [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset valueAmount 2]
+// ROLL                     [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioNumerator]
+// 3                        [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioNumerator 3]
+// ROLL                     [... sellerKey standardProgram sellerProgram requestedAsset valueAmount ratioNumerator ratioDenominator]
+// MULFRACTION              [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// 999                      [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999]
+// 1000                     [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999 1000]
+// MULFRACTION              [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// DUP                      [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount]
+// 0                        [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount 0]
+// GREATERTHAN              [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)]
+// VERIFY                   [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// FROMALTSTACK             [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount <position>]
+// SWAP                     [... sellerKey standardProgram sellerProgram requestedAsset <position> requestedAmount]
+// 2                        [... sellerKey standardProgram sellerProgram requestedAsset <position> requestedAmount 2]
+// ROLL                     [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset]
+// 1                        [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset 1]
+// 4                        [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset 1 4]
+// ROLL                     [... sellerKey standardProgram <position> requestedAmount requestedAsset 1 sellerProgram]
+// CHECKOUTPUT              [... sellerKey standardProgram checkOutput(requestedAmount, requestedAsset, sellerProgram)]
+// JUMP:$_end               [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// $cancel                  [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector>]
+// DROP                     [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// 6                        [... sellerSig sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 6]
+// ROLL                     [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset sellerSig]
+// 6                        [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset sellerSig 6]
+// ROLL                     [... standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset sellerSig sellerKey]
+// TXSIGHASH SWAP CHECKSIG  [... standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset checkTxSig(sellerKey, sellerSig)]
+// $_end                    [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
+       standardProgram, err := P2WMCProgram(magneticContractArgs)
+       if err != nil {
+               return nil, err
+       }
+
+       builder := NewBuilder()
+       // contract arguments
+       builder.AddData(magneticContractArgs.SellerKey)
+       builder.AddData(standardProgram)
+       builder.AddData(magneticContractArgs.SellerProgram)
+       builder.AddInt64(magneticContractArgs.RatioDenominator)
+       builder.AddInt64(magneticContractArgs.RatioNumerator)
+       builder.AddData(magneticContractArgs.RequestedAsset.Bytes())
+
+       // contract instructions
+       builder.AddOp(vm.OP_7)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_TOALTSTACK)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_2)
+       builder.AddOp(vm.OP_NUMEQUAL)
+       builder.AddJumpIf(0)
+       builder.AddJumpIf(1)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_PICK)
+       builder.AddOp(vm.OP_3)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_3)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_MULFRACTION)
+       builder.AddOp(vm.OP_AMOUNT)
+       builder.AddOp(vm.OP_OVER)
+       builder.AddOp(vm.OP_0)
+       builder.AddOp(vm.OP_GREATERTHAN)
+       builder.AddOp(vm.OP_2)
+       builder.AddOp(vm.OP_PICK)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_LESSTHAN)
+       builder.AddOp(vm.OP_BOOLAND)
+       builder.AddOp(vm.OP_VERIFY)
+       builder.AddOp(vm.OP_FROMALTSTACK)
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_TOALTSTACK)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddInt64(999)
+       builder.AddInt64(1000)
+       builder.AddOp(vm.OP_MULFRACTION)
+       builder.AddOp(vm.OP_3)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_5)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_CHECKOUTPUT)
+       builder.AddOp(vm.OP_VERIFY)
+       builder.AddOp(vm.OP_FROMALTSTACK)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_ADD)
+       builder.AddOp(vm.OP_AMOUNT)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_SUB)
+       builder.AddOp(vm.OP_ASSET)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_4)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_CHECKOUTPUT)
+       builder.AddJump(2)
+       builder.SetJumpTarget(1)
+       builder.AddOp(vm.OP_AMOUNT)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_3)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_MULFRACTION)
+       builder.AddInt64(999)
+       builder.AddInt64(1000)
+       builder.AddOp(vm.OP_MULFRACTION)
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_0)
+       builder.AddOp(vm.OP_GREATERTHAN)
+       builder.AddOp(vm.OP_VERIFY)
+       builder.AddOp(vm.OP_FROMALTSTACK)
+       builder.AddOp(vm.OP_SWAP)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_4)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_CHECKOUTPUT)
+       builder.AddJump(3)
+       builder.SetJumpTarget(0)
+       builder.AddOp(vm.OP_DROP)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_TXSIGHASH)
+       builder.AddOp(vm.OP_SWAP)
+       builder.AddOp(vm.OP_CHECKSIG)
+       builder.SetJumpTarget(2)
+       builder.SetJumpTarget(3)
+       return builder.Build()
+}
index 4fed782..d312889 100644 (file)
@@ -189,9 +189,9 @@ func mockAccountManager(t *testing.T) *mockAccManager {
        dispatcher := event.NewDispatcher()
        store := database.NewStore(testDB)
        accountStore := database.NewAccountStore(testDB)
-       txPool := protocol.NewTxPool(store, dispatcher)
+       txPool := protocol.NewTxPool(store, nil, dispatcher)
        config.CommonConfig = config.DefaultConfig()
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
        if err != nil {
                t.Fatal(err)
        }
index 77cf9c1..9da5e3d 100644 (file)
@@ -141,8 +141,8 @@ func GenerateChainData(dirPath string, testDB dbm.DB, txNumber, otherAssetNum in
        config.CommonConfig = config.DefaultConfig()
        store := database.NewStore(testDB)
        dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       txPool := protocol.NewTxPool(store, nil, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
        if err != nil {
                return nil, nil, nil, err
        }
@@ -159,7 +159,7 @@ func InsertChain(chain *protocol.Chain, txPool *protocol.TxPool, txs []*types.Tx
                }
        }
 
-       block, err := proposal.NewBlockTemplate(chain, txPool, nil, uint64(time.Now().UnixNano()/1e6))
+       block, err := proposal.NewBlockTemplate(chain, nil, uint64(time.Now().UnixNano()/1e6), time.Minute, time.Minute)
        if err != nil {
                return err
        }
index a2bec3c..18be6f9 100644 (file)
@@ -22,3 +22,7 @@ func (m *Mempool) AddTx(tx *types.Tx) {
 func (m *Mempool) GetTransactions() []*protocol.TxDesc {
        return m.txs
 }
+
+func (m *Mempool) IsDust(tx *types.Tx) bool {
+       return false
+}
index 22a8720..4fc72b6 100644 (file)
@@ -17,7 +17,7 @@ func BenchmarkNewBlockTpl(b *testing.B) {
        testDB := dbm.NewDB("testdb", "leveldb", "temp")
        defer os.RemoveAll("temp")
 
-       chain, _, txPool, err := test.MockChain(testDB)
+       chain, _, _, err := test.MockChain(testDB)
        if err != nil {
                b.Fatal(err)
        }
@@ -26,6 +26,6 @@ func BenchmarkNewBlockTpl(b *testing.B) {
 
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
-               proposal.NewBlockTemplate(chain, txPool, accountManager, uint64(time.Now().UnixNano()/1e6))
+               proposal.NewBlockTemplate(chain, accountManager, uint64(time.Now().UnixNano()/1e6), time.Minute, time.Minute)
        }
 }
diff --git a/test/rollback_test.go b/test/rollback_test.go
new file mode 100644 (file)
index 0000000..d40f9ba
--- /dev/null
@@ -0,0 +1,1571 @@
+package test
+
+import (
+       "os"
+       "testing"
+
+       "github.com/bytom/vapor/application/mov"
+       movDatabase "github.com/bytom/vapor/application/mov/database"
+       "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/database"
+       dbm "github.com/bytom/vapor/database/leveldb"
+       "github.com/bytom/vapor/database/storage"
+       "github.com/bytom/vapor/protocol"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/protocol/state"
+       "github.com/bytom/vapor/testutil"
+)
+
+type chainData struct {
+       bestBlockHeader    *types.BlockHeader
+       lastIrrBlockHeader *types.BlockHeader
+       utxoViewPoint      *state.UtxoViewpoint
+       storedBlocks       []*types.Block
+       consensusResults   []*state.ConsensusResult
+}
+
+func TestRollback(t *testing.T) {
+       cases := []struct {
+               desc                   string
+               movStartHeight         uint64
+               RoundVoteBlockNums     uint64
+               beforeChainData        *chainData
+               wantChainData          *chainData
+               rollbackToTargetHeight uint64
+       }{
+               {
+                       desc:                   "rollback from height 1 to 0",
+                       movStartHeight:         10,
+                       RoundVoteBlockNums:     1200,
+                       rollbackToTargetHeight: 0,
+                       beforeChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               BlockHeight:    1,
+                                               CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + 10000000000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+                       wantChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height: 0,
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height: 0,
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+               },
+               {
+                       desc:                   "rollback from height 2 to 0",
+                       movStartHeight:         10,
+                       RoundVoteBlockNums:     1200,
+                       rollbackToTargetHeight: 0,
+                       beforeChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                               testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(2) + 10000000000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+                       wantChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height: 0,
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height: 0,
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+               },
+               {
+                       desc:                   "rollback from height 2 to 1",
+                       movStartHeight:         10,
+                       RoundVoteBlockNums:     1200,
+                       rollbackToTargetHeight: 1,
+                       beforeChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                               testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 500},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+                       wantChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               BlockHeight:    1,
+                                               CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1)},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+               },
+               {
+                       desc:                   "rollback from height 2 to 1, RoundVoteBlockNums is 2",
+                       movStartHeight:         10,
+                       RoundVoteBlockNums:     2,
+                       rollbackToTargetHeight: 1,
+                       beforeChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                               testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 500},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+                       wantChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               BlockHeight:    1,
+                                               CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1)},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+               },
+               {
+                       desc:                   "rollback from height 3 to 1, RoundVoteBlockNums is 2",
+                       movStartHeight:         10,
+                       RoundVoteBlockNums:     2,
+                       rollbackToTargetHeight: 1,
+                       beforeChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            3,
+                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            3,
+                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("5b4d53fbc2a489847f34dd0e0c085797fe7cf0a3a9a2f3231d11bdad16dea2be"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+                                               testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                               testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            3,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 2,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 609998000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("0c1cd1c0a6e6161f437c382cca21ce28921234ed7c4f252f7e4bbc9a523b74ac"),
+                                               BlockHeight:    3,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + 240000000},
+                                       },
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+                       wantChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               BlockHeight:    1,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + 2000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+               },
+               {
+                       desc:                   "rollback from height 3 to 2, RoundVoteBlockNums is 2",
+                       movStartHeight:         10,
+                       RoundVoteBlockNums:     2,
+                       rollbackToTargetHeight: 2,
+                       beforeChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            3,
+                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            3,
+                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("5b4d53fbc2a489847f34dd0e0c085797fe7cf0a3a9a2f3231d11bdad16dea2be"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+                                               testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                               testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            3,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 2,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 609998000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("0c1cd1c0a6e6161f437c382cca21ce28921234ed7c4f252f7e4bbc9a523b74ac"),
+                                               BlockHeight:    3,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + 240000000},
+                                       },
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+                       wantChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                               testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000 + 250000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+               },
+               {
+                       desc:                   "rollback from height 4 to 2, there is two chain , and round vote block nums is 2",
+                       movStartHeight:         10,
+                       RoundVoteBlockNums:     2,
+                       rollbackToTargetHeight: 2,
+                       beforeChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            5,
+                                       Timestamp:         uint64(1528945008),
+                                       PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            5,
+                                       Timestamp:         uint64(1528945008),
+                                       PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("3c07f3159d4e2a0527129d644a8fcd09ce26555e94c9c7f348464120ef463275"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 5, Spent: true},
+                                               testutil.MustDecodeHash("927144d2a391e17dc12184f5ae163b994984132ad72c34d854bb9009b68cd4cc"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 4, Spent: true},
+                                               testutil.MustDecodeHash("fa43f4ca43bcb0e94d43b52c56d1740dea1329b59a44f6ee045d70446881c514"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+                                               testutil.MustDecodeHash("f081ccd0c97ae34bc5580a0405d9b1ed0b0ed9e1410f1786b7112b348a412e3d"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 4, Spent: true},
+                                               testutil.MustDecodeHash("2704fa67c76e020b08ffa3f93a500acebcaf68b45ba43d8b3b08b68c5bb1eff1"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+                                               testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                               testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                       },
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            3,
+                                                       Timestamp:         uint64(1528945000),
+                                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 440000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            4,
+                                                       Timestamp:         uint64(1528945005),
+                                                       PreviousBlockHash: testutil.MustDecodeHash("bec3dd0d6fecb80a6f3a0373ec2ae676cc1ce72af83546f3d4672231c9b080e6"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 500000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            3,
+                                                       Timestamp:         uint64(1528945001),
+                                                       PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 402000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 200000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            4,
+                                                       Timestamp:         uint64(1528945006),
+                                                       PreviousBlockHash: testutil.MustDecodeHash("1d2d01a97d1239de51b4e7d0fb522f71771d2d4f9a0a559154519859cc44a230"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 410000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 170000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            5,
+                                                       Timestamp:         uint64(1528945008),
+                                                       PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(3)+consensus.BlockSubsidy(4)+520000000, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400004000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 160004000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 3,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 980002000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("075ce54f7d4c1b524474265219be52238beec98138f0c0a4d21f1a6b0047914a"),
+                                               BlockHeight:    5,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(5) + 240000000},
+                                       },
+                                       {
+                                               Seq: 2,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 819998000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+                                               BlockHeight:    4,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + consensus.BlockSubsidy(4) + 442000000},
+                                       },
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                       },
+                       wantChainData: &chainData{
+                               bestBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               lastIrrBlockHeader: &types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                               },
+                               storedBlocks: []*types.Block{
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height: 0,
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            1,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                                       {
+                                               BlockHeader: types.BlockHeader{
+                                                       Height:            2,
+                                                       PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+                                               },
+                                               Transactions: []*types.Tx{
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+                                                               },
+                                                       }),
+                                                       types.NewTx(types.TxData{
+                                                               Inputs: []*types.TxInput{
+                                                                       types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+                                                               },
+                                                               Outputs: []*types.TxOutput{
+                                                                       types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+                                                               },
+                                                       }),
+                                               },
+                                       },
+                               },
+                               consensusResults: []*state.ConsensusResult{
+                                       {
+                                               Seq: 1,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000 + 250000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+                                               BlockHeight:    2,
+                                               CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+                                       },
+                                       {
+                                               Seq: 0,
+                                               NumOfVote: map[string]uint64{
+                                                       "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+                                                       "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+                                               },
+                                               BlockHash:      testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+                                               BlockHeight:    0,
+                                               CoinbaseReward: map[string]uint64{},
+                                       },
+                               },
+                               utxoViewPoint: &state.UtxoViewpoint{
+                                       Entries: map[bc.Hash]*storage.UtxoEntry{
+                                               testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+                                               testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+                                               testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for i, c := range cases {
+               consensus.ActiveNetParams.RoundVoteBlockNums = c.RoundVoteBlockNums
+
+               movDB := dbm.NewDB("mov_db", "leveldb", "mov_db")
+               movCore := mov.NewCoreWithDB(movDatabase.NewLevelDBMovStore(movDB), c.movStartHeight)
+
+               blockDB := dbm.NewDB("block_db", "leveldb", "block_db")
+               store := database.NewStore(blockDB)
+
+               mustSaveBlocks(c.beforeChainData.storedBlocks, store)
+
+               var mainChainBlockHeaders []*types.BlockHeader
+               for _, block := range c.beforeChainData.storedBlocks {
+                       mainChainBlockHeaders = append(mainChainBlockHeaders, &block.BlockHeader)
+               }
+               if err := store.SaveChainStatus(c.beforeChainData.bestBlockHeader, c.beforeChainData.lastIrrBlockHeader, mainChainBlockHeaders, c.beforeChainData.utxoViewPoint, c.beforeChainData.consensusResults); err != nil {
+                       t.Fatal(err)
+               }
+
+               chain, err := protocol.NewChain(store, nil, []protocol.Protocoler{movCore}, nil)
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               if err := chain.Rollback(c.rollbackToTargetHeight); err != nil {
+                       t.Fatal(err)
+               }
+
+               if !testutil.DeepEqual(chain.LastIrreversibleHeader(), c.wantChainData.lastIrrBlockHeader) {
+                       t.Errorf("lastIrrBlockHeader is not right!")
+               }
+
+               if !testutil.DeepEqual(chain.BestBlockHeader(), c.wantChainData.bestBlockHeader) {
+                       t.Errorf("wantBestBlockHeader is not right!")
+               }
+
+               gotConsensusResults := mustGetConsensusResultFromStore(store, chain)
+               if !testutil.DeepEqual(gotConsensusResults, c.wantChainData.consensusResults) {
+                       t.Errorf("cases#%d(%s) wantBestConsensusResult is not right!", i, c.desc)
+               }
+
+               gotBlocks := mustGetBlocksFromStore(chain)
+               if !blocksEquals(gotBlocks, c.wantChainData.storedBlocks) {
+                       t.Errorf("cases#%d(%s) the blocks is not same!", i, c.desc)
+               }
+
+               gotTransactions := getBcTransactions(gotBlocks)
+               gotUtxoViewPoint := state.NewUtxoViewpoint()
+               if err = store.GetTransactionsUtxo(gotUtxoViewPoint, gotTransactions); err != nil {
+                       t.Fatal(err)
+               }
+
+               if !testutil.DeepEqual(gotUtxoViewPoint, c.wantChainData.utxoViewPoint) {
+                       t.Fatal(err)
+               }
+
+               blockDB.Close()
+               os.RemoveAll("block_db")
+               movDB.Close()
+               os.RemoveAll("mov_db")
+
+       }
+}
+
+func blocksEquals(blocks1 []*types.Block, blocks2 []*types.Block) bool {
+       blockHashMap1 := make(map[string]interface{})
+       for _, block := range blocks1 {
+               hash := block.Hash()
+               blockHashMap1[hash.String()] = nil
+       }
+
+       blockHashMap2 := make(map[string]interface{})
+       for _, block := range blocks2 {
+               hash := block.Hash()
+               blockHashMap2[hash.String()] = nil
+       }
+       return testutil.DeepEqual(blockHashMap1, blockHashMap2)
+}
+
+func getBcTransactions(blocks []*types.Block) []*bc.Tx {
+       var txs []*bc.Tx
+       for _, block := range blocks {
+               for _, tx := range block.Transactions {
+                       txs = append(txs, tx.Tx)
+               }
+       }
+       return txs
+}
+
+func mustSaveBlocks(blocks []*types.Block, store *database.Store) {
+       for _, block := range blocks {
+               status := bc.NewTransactionStatus()
+               for index := range block.Transactions {
+                       if err := status.SetStatus(index, false); err != nil {
+                               panic(err)
+                       }
+               }
+               if err := store.SaveBlock(block, status); err != nil {
+                       panic(err)
+               }
+       }
+}
+
+func mustGetBlocksFromStore(chain *protocol.Chain) []*types.Block {
+       var blocks []*types.Block
+       for height := int64(chain.BestBlockHeight()); height >= 0; height-- {
+               block, err := chain.GetBlockByHeight(uint64(height))
+               if err != nil {
+                       panic(err)
+               }
+
+               blocks = append(blocks, block)
+       }
+       return blocks
+}
+
+func mustGetConsensusResultFromStore(store *database.Store, chain *protocol.Chain) []*state.ConsensusResult {
+       var consensusResults []*state.ConsensusResult
+       for seq := int64(state.CalcVoteSeq(chain.BestBlockHeight())); seq >= 0; seq-- {
+               consensusResult, err := store.GetConsensusResult(uint64(seq))
+               if err != nil {
+                       panic(err)
+               }
+
+               consensusResults = append(consensusResults, consensusResult)
+       }
+       return consensusResults
+}
index 4474893..bcce45f 100644 (file)
@@ -29,8 +29,8 @@ func MockChain(testDB dbm.DB) (*protocol.Chain, *database.Store, *protocol.TxPoo
        config.CommonConfig = config.DefaultConfig()
        store := database.NewStore(testDB)
        dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       txPool := protocol.NewTxPool(store, nil, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
        return chain, store, txPool, err
 }
 
index 8f34401..4a656d7 100644 (file)
@@ -41,9 +41,9 @@ func TestWalletUpdate(t *testing.T) {
        store := database.NewStore(testDB)
        walletStore := database.NewWalletStore(testDB)
        dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
+       txPool := protocol.NewTxPool(store, nil, dispatcher)
 
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
        if err != nil {
                t.Fatal(err)
        }
@@ -140,8 +140,8 @@ func TestRescanWallet(t *testing.T) {
 
        store := database.NewStore(testDB)
        dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       txPool := protocol.NewTxPool(store, nil, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
        if err != nil {
                t.Fatal(err)
        }
@@ -191,9 +191,9 @@ func TestMemPoolTxQueryLoop(t *testing.T) {
 
        store := database.NewStore(testDB)
        dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
+       txPool := protocol.NewTxPool(store, nil, dispatcher)
 
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
        if err != nil {
                t.Fatal(err)
        }
index f633f57..a3c1e19 100644 (file)
@@ -251,6 +251,11 @@ func (cfg *walletTestConfig) Run() error {
        if err != nil {
                return err
        }
+
+       if err = wallet.Run(); err != nil {
+               return err
+       }
+
        ctx := &walletTestContext{
                Wallet: wallet,
                Chain:  chain,
index 3888400..9702648 100644 (file)
@@ -34,7 +34,7 @@ func (s *Server) ListCrosschainTxs(c *gin.Context, listTxsReq *listCrosschainTxs
                txFilter.SourceTxHash = txHash
        }
        if txHash, err := listTxsReq.GetFilterString("dest_tx_hash"); err == nil && txHash != "" {
-               txFilter.DestTxHash = sql.NullString{txHash, true}
+               txFilter.DestTxHash = sql.NullString{String: txHash, Valid: true}
        }
 
        txQuery := s.db.Preload("Chain").Preload("Reqs").Preload("Reqs.Asset").Where(txFilter)
index 56c50b1..34917c6 100644 (file)
@@ -5,11 +5,12 @@ import (
 )
 
 type Asset struct {
-       ID              uint64           `gorm:"primary_key;foreignkey:ID" json:"-"`
-       AssetID         string           `json:"asset_id"`
-       IssuanceProgram string           `json:"-"`
-       VMVersion       uint64           `json:"-"`
-       Definition      string           `json:"-"`
-       CreatedAt       common.Timestamp `json:"-"`
-       UpdatedAt       common.Timestamp `json:"-"`
+       ID                    uint64           `gorm:"primary_key;foreignkey:ID" json:"-"`
+       AssetID               string           `json:"asset_id"`
+       IssuanceProgram       string           `json:"-"`
+       VMVersion             uint64           `json:"-"`
+       Definition            string           `json:"-"`
+       IsOpenFederationIssue bool             `json:"_"`
+       CreatedAt             common.Timestamp `json:"-"`
+       UpdatedAt             common.Timestamp `json:"-"`
 }
index 117717c..3606339 100644 (file)
@@ -14,6 +14,7 @@ import (
        "github.com/jinzhu/gorm"
        log "github.com/sirupsen/logrus"
 
+       vpCommon "github.com/bytom/vapor/common"
        "github.com/bytom/vapor/consensus"
        "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/protocol/bc"
@@ -53,6 +54,8 @@ func NewMainchainKeeper(db *gorm.DB, assetStore *database.AssetStore, cfg *confi
 
 func (m *mainchainKeeper) Run() {
        ticker := time.NewTicker(time.Duration(m.cfg.SyncSeconds) * time.Second)
+       defer ticker.Stop()
+
        for ; true; <-ticker.C {
                for {
                        isUpdate, err := m.syncBlock()
@@ -86,6 +89,10 @@ func (m *mainchainKeeper) createCrossChainReqs(db *gorm.DB, crossTransactionID u
                        return err
                }
 
+               if asset.IsOpenFederationIssue {
+                       continue
+               }
+
                req := &orm.CrossTransactionReq{
                        CrossTransactionID: crossTransactionID,
                        SourcePos:          uint64(i),
@@ -103,30 +110,42 @@ func (m *mainchainKeeper) createCrossChainReqs(db *gorm.DB, crossTransactionID u
        return nil
 }
 
-func (m *mainchainKeeper) isDepositTx(tx *types.Tx) bool {
+func (m *mainchainKeeper) isDepositTx(tx *types.Tx) (bool, error) {
        for _, input := range tx.Inputs {
                if bytes.Equal(input.ControlProgram(), m.federationProg) {
-                       return false
+                       return false, nil
                }
        }
 
        for _, output := range tx.Outputs {
-               if bytes.Equal(output.OutputCommitment.ControlProgram, m.federationProg) {
-                       return true
+               if !bytes.Equal(output.OutputCommitment.ControlProgram, m.federationProg) {
+                       continue
+               }
+
+               if isOFAsset, err := m.isOpenFederationAsset(output.AssetId); err != nil {
+                       return false, err
+               } else if !isOFAsset {
+                       return true, nil
                }
        }
-       return false
+       return false, nil
 }
 
-func (m *mainchainKeeper) isWithdrawalTx(tx *types.Tx) bool {
+func (m *mainchainKeeper) isWithdrawalTx(tx *types.Tx) (bool, error) {
        for _, input := range tx.Inputs {
                if !bytes.Equal(input.ControlProgram(), m.federationProg) {
-                       return false
+                       return false, nil
+               }
+
+               if isOFAsset, err := m.isOpenFederationAsset(input.AssetAmount().AssetId); err != nil {
+                       return false, err
+               } else if isOFAsset {
+                       return false, nil
                }
        }
 
        sourceTxHash := locateSideChainTx(tx.Outputs[len(tx.Outputs)-1])
-       return sourceTxHash != ""
+       return sourceTxHash != "", nil
 }
 
 func locateSideChainTx(output *types.TxOutput) string {
@@ -156,13 +175,17 @@ func (m *mainchainKeeper) processBlock(db *gorm.DB, block *types.Block, txStatus
                        return err
                }
 
-               if m.isDepositTx(tx) {
+               if isDeposit, err := m.isDepositTx(tx); err != nil {
+                       return err
+               } else if isDeposit {
                        if err := m.processDepositTx(db, block, txStatus, i); err != nil {
                                return err
                        }
                }
 
-               if m.isWithdrawalTx(tx) {
+               if isWithdrawal, err := m.isWithdrawalTx(tx); err != nil {
+                       return err
+               } else if isWithdrawal {
                        if err := m.processWithdrawalTx(db, block, i); err != nil {
                                return err
                        }
@@ -188,6 +211,15 @@ func (m *mainchainKeeper) processChainInfo(db *gorm.DB, block *types.Block) erro
        return nil
 }
 
+func (m *mainchainKeeper) isOpenFederationAsset(assetID *btmBc.AssetID) (bool, error) {
+       asset, err := m.assetStore.GetByAssetID(assetID.String())
+       if err != nil {
+               return false, err
+       }
+
+       return asset.IsOpenFederationIssue, nil
+}
+
 func (m *mainchainKeeper) processDepositTx(db *gorm.DB, block *types.Block, txStatus *bc.TransactionStatus, txIndex int) error {
        tx := block.Transactions[txIndex]
        var muxID btmBc.Hash
@@ -242,10 +274,11 @@ func (m *mainchainKeeper) processIssuance(tx *types.Tx) error {
                }
 
                asset := &orm.Asset{
-                       AssetID:         assetID.String(),
-                       IssuanceProgram: hex.EncodeToString(issuance.IssuanceProgram),
-                       VMVersion:       issuance.VMVersion,
-                       Definition:      string(issuance.AssetDefinition),
+                       AssetID:               assetID.String(),
+                       IssuanceProgram:       hex.EncodeToString(issuance.IssuanceProgram),
+                       VMVersion:             issuance.VMVersion,
+                       Definition:            string(issuance.AssetDefinition),
+                       IsOpenFederationIssue: vpCommon.IsOpenFederationIssueAsset(issuance.AssetDefinition),
                }
 
                if err := m.db.Create(asset).Error; err != nil {
index 0ec3d92..0e51314 100644 (file)
@@ -78,6 +78,10 @@ func (s *sidechainKeeper) createCrossChainReqs(db *gorm.DB, crossTransactionID u
                        return err
                }
 
+               if asset.IsOpenFederationIssue {
+                       continue
+               }
+
                prog := rawOutput.ControlProgram()
                req := &orm.CrossTransactionReq{
                        CrossTransactionID: crossTransactionID,
@@ -96,33 +100,49 @@ func (s *sidechainKeeper) createCrossChainReqs(db *gorm.DB, crossTransactionID u
        return nil
 }
 
-func (s *sidechainKeeper) isDepositTx(tx *types.Tx) bool {
+func (s *sidechainKeeper) isDepositTx(tx *types.Tx) (bool, error) {
        for _, input := range tx.Inputs {
-               if input.InputType() == types.CrossChainInputType {
-                       return true
+               if input.InputType() != types.CrossChainInputType {
+                       continue
+               }
+
+               if isOFAsset, err := s.isOpenFederationAsset(input.AssetAmount().AssetId); err != nil {
+                       return false, err
+               } else if !isOFAsset {
+                       return true, nil
                }
        }
-       return false
+       return false, nil
 }
 
-func (s *sidechainKeeper) isWithdrawalTx(tx *types.Tx) bool {
+func (s *sidechainKeeper) isWithdrawalTx(tx *types.Tx) (bool, error) {
        for _, output := range tx.Outputs {
-               if output.OutputType() == types.CrossChainOutputType {
-                       return true
+               if output.OutputType() != types.CrossChainOutputType {
+                       continue
+               }
+
+               if isOFAsset, err := s.isOpenFederationAsset(output.AssetAmount().AssetId); err != nil {
+                       return false, err
+               } else if !isOFAsset {
+                       return true, nil
                }
        }
-       return false
+       return false, nil
 }
 
 func (s *sidechainKeeper) processBlock(db *gorm.DB, block *types.Block, txStatus *bc.TransactionStatus) error {
        for i, tx := range block.Transactions {
-               if s.isDepositTx(tx) {
+               if isDeposit, err := s.isDepositTx(tx); err != nil {
+                       return err
+               } else if isDeposit {
                        if err := s.processDepositTx(db, block, i); err != nil {
                                return err
                        }
                }
 
-               if s.isWithdrawalTx(tx) {
+               if isWithdrawal, err := s.isWithdrawalTx(tx); err != nil {
+                       return err
+               } else if isWithdrawal {
                        if err := s.processWithdrawalTx(db, block, txStatus, i); err != nil {
                                return err
                        }
@@ -177,6 +197,15 @@ func (s *sidechainKeeper) processDepositTx(db *gorm.DB, block *types.Block, txIn
        return nil
 }
 
+func (s *sidechainKeeper) isOpenFederationAsset(assetID *bc.AssetID) (bool, error) {
+       asset, err := s.assetStore.GetByAssetID(assetID.String())
+       if err != nil {
+               return false, err
+       }
+
+       return asset.IsOpenFederationIssue, nil
+}
+
 func (s *sidechainKeeper) processWithdrawalTx(db *gorm.DB, block *types.Block, txStatus *bc.TransactionStatus, txIndex int) error {
        tx := block.Transactions[txIndex]
        var muxID bc.Hash
index 4f40794..6bb3396 100644 (file)
@@ -6,7 +6,6 @@ import (
 
        log "github.com/sirupsen/logrus"
        "github.com/bytom/vapor/crypto/ed25519/chainkd"
-
        "github.com/bytom/vapor/toolbar/common"
 )
 
index ba42732..d50356b 100644 (file)
@@ -47,7 +47,7 @@ const (
 
 var (
        // The full version string
-       Version = "1.0.4"
+       Version = "1.1.0"
        // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse HEAD)"
        GitCommit string
        Status    *UpdateStatus
index 739e949..e22c44e 100644 (file)
@@ -80,16 +80,22 @@ func NewWallet(store WalletStore, account *account.Manager, asset *asset.Registr
                return nil, err
        }
 
+       return w, nil
+}
+
+// Run go to run some wallet recorvery and clean tx thread
+func (w *Wallet) Run() error {
        var err error
        w.TxMsgSub, err = w.EventDispatcher.Subscribe(protocol.TxMsgEvent{})
        if err != nil {
-               return nil, err
+               return err
        }
 
        go w.walletUpdater()
        go w.delUnconfirmedTx()
        go w.MemPoolTxQueryLoop()
-       return w, nil
+
+       return nil
 }
 
 // MemPoolTxQueryLoop constantly pass a transaction accepted by mempool to the wallet.
@@ -307,6 +313,22 @@ func (w *Wallet) DeleteAccount(accountID string) (err error) {
        return nil
 }
 
+// Rollback wallet to target height
+func (w *Wallet) Rollback(targetHeight uint64) error {
+       for w.Status.WorkHeight > targetHeight {
+               block, err := w.Chain.GetBlockByHash(&w.Status.WorkHash)
+               if err != nil {
+                       return err
+               }
+
+               if err = w.DetachBlock(block); err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
 func (w *Wallet) UpdateAccountAlias(accountID string, newAlias string) (err error) {
        w.rw.Lock()
        defer w.rw.Unlock()