From 78ef45d4238457b2ad498d738db5a7a7a30df167 Mon Sep 17 00:00:00 2001 From: Paladz Date: Mon, 23 Mar 2020 11:02:52 +0800 Subject: [PATCH] Mov (#518) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * 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 * fix_open_federation_issue_asset (#478) * fix_open_federation_issue_asset * opt code * complement mov test (#477) Co-authored-by: Paladz * 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 * 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 * fix_add_order_of_order_book (#497) * fix_add_order_of_order_book * fix order book * opt code Co-authored-by: Paladz * 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 * rollback (#503) * rollback * add * rollback test * rollback xiugai * delete one line * s * rename chainData * reformat it * opt code Co-authored-by: shenao78 Co-authored-by: Paladz * 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 Co-authored-by: Poseidon 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> --- .travis.yml | 2 +- application/mov/common/type.go | 151 +- application/mov/common/type_test.go | 27 + application/mov/common/util.go | 30 + application/mov/contract/contract.go | 41 + application/mov/database/mov_iterator.go | 104 + application/mov/database/mov_iterator_test.go | 166 ++ application/mov/database/mov_store.go | 275 +- application/mov/database/mov_store_test.go | 3156 ++++++----------------- application/mov/match/engine.go | 263 ++ application/mov/match/engine_test.go | 388 +++ application/mov/match/fee_strategy.go | 108 + application/mov/match/order_book.go | 200 ++ application/mov/match/order_book_test.go | 376 +++ application/mov/match_collector.go | 146 ++ application/mov/mock/mock.go | 491 ++++ application/mov/mock/mock_mov_store.go | 108 + application/mov/mov_core.go | 513 ++++ application/mov/mov_core_test.go | 846 ++++++ blockchain/txbuilder/actions.go | 63 +- blockchain/txbuilder/signing_instruction.go | 1 + cmd/vapord/commands/rollback_node.go | 38 + cmd/vapord/commands/root.go | 1 + cmd/vapord/commands/run_node.go | 1 + common/bytes.go | 5 + common/crossin_asset.go | 25 + common/sort.go | 23 - config/config.go | 13 + config/federation_test.go | 1 - config/toml.go | 8 +- consensus/general.go | 30 +- consensus/segwit/segwit.go | 85 + database/account_store.go | 14 +- database/store.go | 50 + database/store_test.go | 226 ++ database/utxo_view.go | 3 +- database/wallet_store.go | 6 +- docs/federation/sql_dump/federation_shema.sql | 1 + math/algorithm.go | 9 + netsync/chainmgr/block_keeper.go | 13 +- netsync/chainmgr/block_keeper_test.go | 3 +- netsync/chainmgr/block_process.go | 8 +- netsync/chainmgr/fast_sync.go | 6 +- netsync/chainmgr/handle.go | 24 +- netsync/chainmgr/msg_fetcher.go | 7 +- netsync/chainmgr/storage.go | 8 +- netsync/chainmgr/tx_keeper_test.go | 4 + node/node.go | 98 +- proposal/blockproposer/blockproposer.go | 10 +- proposal/proposal.go | 334 ++- proposal/proposal_test.go | 2 +- protocol/asset_filter.go | 42 + protocol/bbft.go | 56 +- protocol/bc/bc.pb.go | 131 +- protocol/bc/bc.proto | 1 + protocol/bc/crosschain_input.go | 3 +- protocol/bc/entry_test.go | 1 + protocol/bc/types/block.go | 1 + protocol/bc/types/block_witness.go | 4 + protocol/bc/types/crosschain_output.go | 1 + protocol/bc/types/intrachain_output.go | 1 + protocol/bc/types/map.go | 2 +- protocol/bc/types/vote_output.go | 1 + protocol/block.go | 169 +- protocol/block_test.go | 4 +- protocol/consensus_node_manager_test.go | 10 +- protocol/protocol.go | 81 +- protocol/store.go | 3 + protocol/tx.go | 8 + protocol/txpool.go | 39 +- protocol/txpool_test.go | 4 + protocol/validation/tx.go | 15 +- protocol/validation/tx_test.go | 336 +++ protocol/validation/vmcontext.go | 13 +- protocol/vm/numeric.go | 34 + protocol/vm/numeric_test.go | 43 + protocol/vm/ops.go | 2 + protocol/vm/vmutil/script.go | 246 ++ test/accounts_test.go | 4 +- test/bench_blockchain_test.go | 6 +- test/mock/mempool.go | 4 + test/performance/mining_test.go | 4 +- test/rollback_test.go | 1571 +++++++++++ test/util.go | 4 +- test/wallet_test.go | 12 +- test/wallet_test_util.go | 5 + toolbar/federation/api/handler.go | 2 +- toolbar/federation/database/orm/asset.go | 15 +- toolbar/federation/synchron/mainchain_keeper.go | 61 +- toolbar/federation/synchron/sidechain_keeper.go | 49 +- toolbar/precognitive/config/config.go | 1 - version/version.go | 2 +- wallet/wallet.go | 26 +- 93 files changed, 8681 insertions(+), 2847 deletions(-) create mode 100644 application/mov/common/type_test.go create mode 100644 application/mov/common/util.go create mode 100644 application/mov/contract/contract.go create mode 100644 application/mov/database/mov_iterator.go create mode 100644 application/mov/database/mov_iterator_test.go create mode 100644 application/mov/match/engine.go create mode 100644 application/mov/match/engine_test.go create mode 100644 application/mov/match/fee_strategy.go create mode 100644 application/mov/match/order_book.go create mode 100644 application/mov/match/order_book_test.go create mode 100644 application/mov/match_collector.go create mode 100644 application/mov/mock/mock.go create mode 100644 application/mov/mock/mock_mov_store.go create mode 100644 application/mov/mov_core.go create mode 100644 application/mov/mov_core_test.go create mode 100644 cmd/vapord/commands/rollback_node.go create mode 100644 common/crossin_asset.go delete mode 100644 common/sort.go create mode 100644 math/algorithm.go create mode 100644 protocol/asset_filter.go create mode 100644 test/rollback_test.go diff --git a/.travis.yml b/.travis.yml index f32dc512..436a5c61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ branches: only: - master - dev - - v0.1 + - mov script: - make ci diff --git a/application/mov/common/type.go b/application/mov/common/type.go index d56dac08..11d89bb1 100644 --- a/application/mov/common/type.go +++ b/application/mov/common/type.go @@ -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 index 00000000..8fe9e810 --- /dev/null +++ b/application/mov/common/type_test.go @@ -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 index 00000000..0077eab5 --- /dev/null +++ b/application/mov/common/util.go @@ -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 index 00000000..75cd00d7 --- /dev/null +++ b/application/mov/contract/contract.go @@ -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 index 00000000..b06debf4 --- /dev/null +++ b/application/mov/database/mov_iterator.go @@ -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 index 00000000..5df84e1e --- /dev/null +++ b/application/mov/database/mov_iterator_test.go @@ -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) + } + } +} diff --git a/application/mov/database/mov_store.go b/application/mov/database/mov_store.go index 252886a4..20d3101e 100644 --- a/application/mov/database/mov_store.go +++ b/application/mov/database/mov_store.go @@ -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 -} diff --git a/application/mov/database/mov_store_test.go b/application/mov/database/mov_store_test.go index f212ae07..aeb9c3f3 100644 --- a/application/mov/database/mov_store_test.go +++ b/application/mov/database/mov_store_test.go @@ -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 index 00000000..77de1113 --- /dev/null +++ b/application/mov/match/engine.go @@ -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 index 00000000..1ac993ac --- /dev/null +++ b/application/mov/match/engine_test.go @@ -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 index 00000000..f3d1dfc3 --- /dev/null +++ b/application/mov/match/fee_strategy.go @@ -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 index 00000000..426fd7da --- /dev/null +++ b/application/mov/match/order_book.go @@ -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 index 00000000..daf330ff --- /dev/null +++ b/application/mov/match/order_book_test.go @@ -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 index 00000000..1081be31 --- /dev/null +++ b/application/mov/match_collector.go @@ -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 index 00000000..46f0bccf --- /dev/null +++ b/application/mov/mock/mock.go @@ -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: Ð, + 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: Ð, + 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: Ð, + 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: Ð, + 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: Ð, + 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: Ð, + 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: Ð, + 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: Ð, + 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: Ð, + 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 index 00000000..34a1bd02 --- /dev/null +++ b/application/mov/mock/mock_mov_store.go @@ -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 index 00000000..a8386c29 --- /dev/null +++ b/application/mov/mov_core.go @@ -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 index 00000000..9b4e037e --- /dev/null +++ b/application/mov/mov_core_test.go @@ -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 +} diff --git a/blockchain/txbuilder/actions.go b/blockchain/txbuilder/actions.go index 907d1af7..c41d7dc9 100644 --- a/blockchain/txbuilder/actions.go +++ b/blockchain/txbuilder/actions.go @@ -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" } diff --git a/blockchain/txbuilder/signing_instruction.go b/blockchain/txbuilder/signing_instruction.go index 6a111980..ba448d6f 100644 --- a/blockchain/txbuilder/signing_instruction.go +++ b/blockchain/txbuilder/signing_instruction.go @@ -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 index 00000000..ff1f8784 --- /dev/null +++ b/cmd/vapord/commands/rollback_node.go @@ -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) +} diff --git a/cmd/vapord/commands/root.go b/cmd/vapord/commands/root.go index ed4060d7..d534b2d5 100644 --- a/cmd/vapord/commands/root.go +++ b/cmd/vapord/commands/root.go @@ -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.", diff --git a/cmd/vapord/commands/run_node.go b/cmd/vapord/commands/run_node.go index 2cc8bc7b..af75e323 100644 --- a/cmd/vapord/commands/run_node.go +++ b/cmd/vapord/commands/run_node.go @@ -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") diff --git a/common/bytes.go b/common/bytes.go index db0cdc99..e899a1e4 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -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 index 00000000..9dbd729a --- /dev/null +++ b/common/crossin_asset.go @@ -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 index a476c922..00000000 --- a/common/sort.go +++ /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] -} diff --git a/config/config.go b/config/config.go index fa19b304..e9490f91 100644 --- a/config/config.go +++ b/config/config.go @@ -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") diff --git a/config/federation_test.go b/config/federation_test.go index 1c36d863..34e28973 100644 --- a/config/federation_test.go +++ b/config/federation_test.go @@ -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) diff --git a/config/toml.go b/config/toml.go index 8c1d570d..83091b5f 100644 --- a/config/toml.go +++ b/config/toml.go @@ -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. diff --git a/consensus/general.go b/consensus/general.go index 07c32e8a..799185e9 100644 --- a/consensus/general.go +++ b/consensus/general.go @@ -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 { diff --git a/consensus/segwit/segwit.go b/consensus/segwit/segwit.go index 75151417..8f303207 100644 --- a/consensus/segwit/segwit.go +++ b/consensus/segwit/segwit.go @@ -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 { diff --git a/database/account_store.go b/database/account_store.go index 8d15bcf7..f392f1da 100644 --- a/database/account_store.go +++ b/database/account_store.go @@ -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)) } } diff --git a/database/store.go b/database/store.go index 1970543b..4ffc65dc 100644 --- a/database/store.go +++ b/database/store.go @@ -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) { diff --git a/database/store_test.go b/database/store_test.go index 548f6b24..cf8e86a9 100644 --- a/database/store_test.go +++ b/database/store_test.go @@ -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") + } +} diff --git a/database/utxo_view.go b/database/utxo_view.go index 81a7f28b..5f4de1ff 100644 --- a/database/utxo_view.go +++ b/database/utxo_view.go @@ -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) } diff --git a/database/wallet_store.go b/database/wallet_store.go index d0bfa5ab..d50464b1 100644 --- a/database/wallet_store.go +++ b/database/wallet_store.go @@ -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 diff --git a/docs/federation/sql_dump/federation_shema.sql b/docs/federation/sql_dump/federation_shema.sql index 36e00611..97b48c0f 100644 --- a/docs/federation/sql_dump/federation_shema.sql +++ b/docs/federation/sql_dump/federation_shema.sql @@ -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 index 00000000..791a70cd --- /dev/null +++ b/math/algorithm.go @@ -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 +} diff --git a/netsync/chainmgr/block_keeper.go b/netsync/chainmgr/block_keeper.go index 5d0522d9..9cdfe1b0 100644 --- a/netsync/chainmgr/block_keeper.go +++ b/netsync/chainmgr/block_keeper.go @@ -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 } diff --git a/netsync/chainmgr/block_keeper_test.go b/netsync/chainmgr/block_keeper_test.go index e34e48bd..52b206c7 100644 --- a/netsync/chainmgr/block_keeper_test.go +++ b/netsync/chainmgr/block_keeper_test.go @@ -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) } diff --git a/netsync/chainmgr/block_process.go b/netsync/chainmgr/block_process.go index d1e45a65..50a110b6 100644 --- a/netsync/chainmgr/block_process.go +++ b/netsync/chainmgr/block_process.go @@ -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, diff --git a/netsync/chainmgr/fast_sync.go b/netsync/chainmgr/fast_sync.go index 6b8ea72c..a8022279 100644 --- a/netsync/chainmgr/fast_sync.go +++ b/netsync/chainmgr/fast_sync.go @@ -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 } diff --git a/netsync/chainmgr/handle.go b/netsync/chainmgr/handle.go index e57e60a7..35d1f9a5 100644 --- a/netsync/chainmgr/handle.go +++ b/netsync/chainmgr/handle.go @@ -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{}) diff --git a/netsync/chainmgr/msg_fetcher.go b/netsync/chainmgr/msg_fetcher.go index 457574bf..aa8d657b 100644 --- a/netsync/chainmgr/msg_fetcher.go +++ b/netsync/chainmgr/msg_fetcher.go @@ -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(), diff --git a/netsync/chainmgr/storage.go b/netsync/chainmgr/storage.go index ff6b758c..8cc8b12e 100644 --- a/netsync/chainmgr/storage.go +++ b/netsync/chainmgr/storage.go @@ -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) diff --git a/netsync/chainmgr/tx_keeper_test.go b/netsync/chainmgr/tx_keeper_test.go index cfe85600..00e59e1e 100644 --- a/netsync/chainmgr/tx_keeper_test.go +++ b/netsync/chainmgr/tx_keeper_test.go @@ -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 { diff --git a/node/node.go b/node/node.go index 1b00877f..313f55b2 100644 --- a/node/node.go +++ b/node/node.go @@ -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() { diff --git a/proposal/blockproposer/blockproposer.go b/proposal/blockproposer/blockproposer.go index 70c9018c..f7d08b05 100644 --- a/proposal/blockproposer/blockproposer.go +++ b/proposal/blockproposer/blockproposer.go @@ -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 diff --git a/proposal/proposal.go b/proposal/proposal.go index 0cdb5ad6..1a4d1c8f 100644 --- a/proposal/proposal.go +++ b/proposal/proposal.go @@ -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 } diff --git a/proposal/proposal_test.go b/proposal/proposal_test.go index 73de939f..026c6272 100644 --- a/proposal/proposal_test.go +++ b/proposal/proposal_test.go @@ -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 index 00000000..cd9fccd6 --- /dev/null +++ b/protocol/asset_filter.go @@ -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 +} diff --git a/protocol/bbft.go b/protocol/bbft.go index d62b3ed6..06b48591 100644 --- a/protocol/bbft.go +++ b/protocol/bbft.go @@ -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 } diff --git a/protocol/bc/bc.pb.go b/protocol/bc/bc.pb.go index b65b3b7f..d302d9bc 100644 --- a/protocol/bc/bc.pb.go +++ b/protocol/bc/bc.pb.go @@ -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, } diff --git a/protocol/bc/bc.proto b/protocol/bc/bc.proto index 59190d4c..ddc90bdd 100644 --- a/protocol/bc/bc.proto +++ b/protocol/bc/bc.proto @@ -132,4 +132,5 @@ message CrossChainInput { AssetDefinition asset_definition = 4; repeated bytes witness_arguments = 5; uint64 ordinal = 6; + bytes rawDefinitionByte = 7; } diff --git a/protocol/bc/crosschain_input.go b/protocol/bc/crosschain_input.go index e9014c27..08a7cc76 100644 --- a/protocol/bc/crosschain_input.go +++ b/protocol/bc/crosschain_input.go @@ -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, } } diff --git a/protocol/bc/entry_test.go b/protocol/bc/entry_test.go index 34520be8..8ce9fbcb 100644 --- a/protocol/bc/entry_test.go +++ b/protocol/bc/entry_test.go @@ -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", }, diff --git a/protocol/bc/types/block.go b/protocol/bc/types/block.go index 654da54b..621935ad 100644 --- a/protocol/bc/types/block.go +++ b/protocol/bc/types/block.go @@ -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 { diff --git a/protocol/bc/types/block_witness.go b/protocol/bc/types/block_witness.go index a756cd3e..f05ddc3e 100644 --- a/protocol/bc/types/block_witness.go +++ b/protocol/bc/types/block_witness.go @@ -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] diff --git a/protocol/bc/types/crosschain_output.go b/protocol/bc/types/crosschain_output.go index 6c3222bf..017cbcbf 100644 --- a/protocol/bc/types/crosschain_output.go +++ b/protocol/bc/types/crosschain_output.go @@ -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 } diff --git a/protocol/bc/types/intrachain_output.go b/protocol/bc/types/intrachain_output.go index 55e27736..efc74b35 100644 --- a/protocol/bc/types/intrachain_output.go +++ b/protocol/bc/types/intrachain_output.go @@ -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 } diff --git a/protocol/bc/types/map.go b/protocol/bc/types/map.go index 5a866898..65533e2a 100644 --- a/protocol/bc/types/map.go +++ b/protocol/bc/types/map.go @@ -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{ diff --git a/protocol/bc/types/vote_output.go b/protocol/bc/types/vote_output.go index ffeb5957..673b2b50 100644 --- a/protocol/bc/types/vote_output.go +++ b/protocol/bc/types/vote_output.go @@ -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 } diff --git a/protocol/block.go b/protocol/block.go index 937ba5a1..52eab1bd 100644 --- a/protocol/block.go +++ b/protocol/block.go @@ -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 } diff --git a/protocol/block_test.go b/protocol/block_test.go index e32826f9..ba13dbe6 100644 --- a/protocol/block_test.go +++ b/protocol/block_test.go @@ -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 diff --git a/protocol/consensus_node_manager_test.go b/protocol/consensus_node_manager_test.go index 76e3428f..56471265 100644 --- a/protocol/consensus_node_manager_test.go +++ b/protocol/consensus_node_manager_test.go @@ -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 diff --git a/protocol/protocol.go b/protocol/protocol.go index 56c85fb4..a1cbb709 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -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 { diff --git a/protocol/store.go b/protocol/store.go index 808b289f..b108ab8c 100644 --- a/protocol/store.go +++ b/protocol/store.go @@ -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 diff --git a/protocol/tx.go b/protocol/tx.go index b5b4ec5e..1206916d 100644 --- a/protocol/tx.go +++ b/protocol/tx.go @@ -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") } diff --git a/protocol/txpool.go b/protocol/txpool.go index 841e549b..9413fe4e 100644 --- a/protocol/txpool.go +++ b/protocol/txpool.go @@ -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) } diff --git a/protocol/txpool_test.go b/protocol/txpool_test.go index 343eb073..50e448e5 100644 --- a/protocol/txpool_test.go +++ b/protocol/txpool_test.go @@ -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 diff --git a/protocol/validation/tx.go b/protocol/validation/tx.go index bb824796..44287e9c 100644 --- a/protocol/validation/tx.go +++ b/protocol/validation/tx.go @@ -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) diff --git a/protocol/validation/tx_test.go b/protocol/validation/tx_test.go index 619a02ed..6f5c9252 100644 --- a/protocol/validation/tx_test.go +++ b/protocol/validation/tx_test.go @@ -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 diff --git a/protocol/validation/vmcontext.go b/protocol/validation/vmcontext.go index 50c000d5..16dd185d 100644 --- a/protocol/validation/vmcontext.go +++ b/protocol/validation/vmcontext.go @@ -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 } } diff --git a/protocol/vm/numeric.go b/protocol/vm/numeric.go index 0845c9cd..5a388ecd 100644 --- a/protocol/vm/numeric.go +++ b/protocol/vm/numeric.go @@ -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) +} diff --git a/protocol/vm/numeric_test.go b/protocol/vm/numeric_test.go index 52759983..d15d119e 100644 --- a/protocol/vm/numeric_test.go +++ b/protocol/vm/numeric_test.go @@ -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 { diff --git a/protocol/vm/ops.go b/protocol/vm/ops.go index 0659fb7e..88dfe1e2 100644 --- a/protocol/vm/ops.go +++ b/protocol/vm/ops.go @@ -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}, diff --git a/protocol/vm/vmutil/script.go b/protocol/vm/vmutil/script.go index 849ec69c..21cc0d4b 100644 --- a/protocol/vm/vmutil/script.go +++ b/protocol/vm/vmutil/script.go @@ -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 [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 7] +// ROLL [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset ] +// TOALTSTACK [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset] +// 6 [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 6] +// ROLL [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset ] +// DUP [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset ] +// 2 [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 2] +// NUMEQUAL [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset ( == 2)] +// JUMPIF:$cancel [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset ] +// 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 ] +// DUP [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount ] +// TOALTSTACK [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount ] +// 6 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount 6] +// ROLL [... sellerKey standardProgram sellerProgram requestedAsset actualAmount exchangeAmount] +// 999 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount exchangeAmount 999] +// 1000 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount exchangeAmount 1000] +// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset actualAmount receiveAmount] +// 3 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount receiveAmount 3] +// ROLL [... sellerKey standardProgram sellerProgram actualAmount receiveAmount requestedAsset] +// 1 [... sellerKey standardProgram sellerProgram actualAmount receiveAmount requestedAsset 1] +// 5 [... sellerKey standardProgram sellerProgram actualAmount receiveAmount requestedAsset 1 5] +// ROLL [... sellerKey standardProgram actualAmount receiveAmount requestedAsset 1 sellerProgram] +// CHECKOUTPUT [... sellerKey standardProgram actualAmount checkOutput(receiveAmount, requestedAsset, sellerProgram)] +// VERIFY [... sellerKey standardProgram actualAmount] +// FROMALTSTACK [... sellerKey standardProgram actualAmount ] +// 1 [... sellerKey standardProgram actualAmount 1] +// ADD [... sellerKey standardProgram actualAmount ( + 1)] +// AMOUNT [... sellerKey standardProgram actualAmount ( + 1) valueAmount] +// 2 [... sellerKey standardProgram actualAmount ( + 1) valueAmount 2] +// ROLL [... sellerKey standardProgram ( + 1) valueAmount actualAmount] +// SUB [... sellerKey standardProgram ( + 1) (valueAmount - actualAmount)] +// ASSET [... sellerKey standardProgram ( + 1) (valueAmount - actualAmount) valueAsset] +// 1 [... sellerKey standardProgram ( + 1) (valueAmount - actualAmount) valueAsset 1] +// 4 [... sellerKey standardProgram ( + 1) (valueAmount - actualAmount) valueAsset 1 4] +// ROLL [... sellerKey ( + 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 ] +// SWAP [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount] +// 2 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 2] +// ROLL [... sellerKey standardProgram sellerProgram requestedAmount requestedAsset] +// 1 [... sellerKey standardProgram sellerProgram requestedAmount requestedAsset 1] +// 4 [... sellerKey standardProgram sellerProgram requestedAmount requestedAsset 1 4] +// ROLL [... sellerKey standardProgram 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 ] +// 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() +} diff --git a/test/accounts_test.go b/test/accounts_test.go index 4fed7821..d312889a 100644 --- a/test/accounts_test.go +++ b/test/accounts_test.go @@ -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) } diff --git a/test/bench_blockchain_test.go b/test/bench_blockchain_test.go index 77cf9c17..9da5e3d6 100644 --- a/test/bench_blockchain_test.go +++ b/test/bench_blockchain_test.go @@ -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 } diff --git a/test/mock/mempool.go b/test/mock/mempool.go index a2bec3cf..18be6f98 100644 --- a/test/mock/mempool.go +++ b/test/mock/mempool.go @@ -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 +} diff --git a/test/performance/mining_test.go b/test/performance/mining_test.go index 22a8720f..4fc72b60 100644 --- a/test/performance/mining_test.go +++ b/test/performance/mining_test.go @@ -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 index 00000000..d40f9ba7 --- /dev/null +++ b/test/rollback_test.go @@ -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 +} diff --git a/test/util.go b/test/util.go index 44748931..bcce45fd 100644 --- a/test/util.go +++ b/test/util.go @@ -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 } diff --git a/test/wallet_test.go b/test/wallet_test.go index 8f344015..4a656d70 100644 --- a/test/wallet_test.go +++ b/test/wallet_test.go @@ -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) } diff --git a/test/wallet_test_util.go b/test/wallet_test_util.go index f633f572..a3c1e19b 100644 --- a/test/wallet_test_util.go +++ b/test/wallet_test_util.go @@ -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, diff --git a/toolbar/federation/api/handler.go b/toolbar/federation/api/handler.go index 3888400f..97026488 100644 --- a/toolbar/federation/api/handler.go +++ b/toolbar/federation/api/handler.go @@ -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) diff --git a/toolbar/federation/database/orm/asset.go b/toolbar/federation/database/orm/asset.go index 56c50b18..34917c62 100644 --- a/toolbar/federation/database/orm/asset.go +++ b/toolbar/federation/database/orm/asset.go @@ -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:"-"` } diff --git a/toolbar/federation/synchron/mainchain_keeper.go b/toolbar/federation/synchron/mainchain_keeper.go index 117717c8..36063395 100644 --- a/toolbar/federation/synchron/mainchain_keeper.go +++ b/toolbar/federation/synchron/mainchain_keeper.go @@ -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 { diff --git a/toolbar/federation/synchron/sidechain_keeper.go b/toolbar/federation/synchron/sidechain_keeper.go index 0ec3d925..0e513146 100644 --- a/toolbar/federation/synchron/sidechain_keeper.go +++ b/toolbar/federation/synchron/sidechain_keeper.go @@ -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 diff --git a/toolbar/precognitive/config/config.go b/toolbar/precognitive/config/config.go index 4f407945..6bb33964 100644 --- a/toolbar/precognitive/config/config.go +++ b/toolbar/precognitive/config/config.go @@ -6,7 +6,6 @@ import ( log "github.com/sirupsen/logrus" "github.com/bytom/vapor/crypto/ed25519/chainkd" - "github.com/bytom/vapor/toolbar/common" ) diff --git a/version/version.go b/version/version.go index ba427320..d50356b0 100644 --- a/version/version.go +++ b/version/version.go @@ -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 diff --git a/wallet/wallet.go b/wallet/wallet.go index 739e949f..e22c44e0 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -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() -- 2.11.0