• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: コミット

Golang implemented sidechain for Bytom


コミットメタ情報

リビジョン8c4ce00e959a22bb2a0803d82e1a3597e96abd61 (tree)
日時2020-03-23 21:05:58
作者oys <oys@oysd...>
コミッターoys

ログメッセージ

merge mov

変更サマリ

差分

--- a/application/mov/match/match.go
+++ b/application/mov/match/engine.go
@@ -1,8 +1,6 @@
11 package match
22
33 import (
4- "encoding/hex"
5- "math"
64 "math/big"
75
86 "github.com/bytom/vapor/application/mov/common"
@@ -13,19 +11,18 @@ import (
1311 "github.com/bytom/vapor/protocol/bc"
1412 "github.com/bytom/vapor/protocol/bc/types"
1513 "github.com/bytom/vapor/protocol/vm"
16- "github.com/bytom/vapor/protocol/vm/vmutil"
1714 )
1815
1916 // Engine is used to generate math transactions
2017 type Engine struct {
21- orderBook *OrderBook
22- maxFeeRate float64
23- nodeProgram []byte
18+ orderBook *OrderBook
19+ feeStrategy FeeStrategy
20+ rewardProgram []byte
2421 }
2522
2623 // NewEngine return a new Engine
27-func NewEngine(orderBook *OrderBook, maxFeeRate float64, nodeProgram []byte) *Engine {
28- return &Engine{orderBook: orderBook, maxFeeRate: maxFeeRate, nodeProgram: nodeProgram}
24+func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
25+ return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
2926 }
3027
3128 // HasMatchedTx check does the input trade pair can generate a match deal
@@ -65,38 +62,20 @@ func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, erro
6562 return tx, nil
6663 }
6764
68-func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
69- txFee, err := CalcMatchedTxFee(txData, e.maxFeeRate)
70- if err != nil {
71- return err
65+func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, refunds []RefundAssets, fees []*bc.AssetAmount) error {
66+ for _, feeAmount := range fees {
67+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
7268 }
7369
74- for assetID, matchTxFee := range txFee {
75- feeAmount, reminder := matchTxFee.FeeAmount, int64(0)
76- if matchTxFee.FeeAmount > matchTxFee.MaxFeeAmount {
77- feeAmount = matchTxFee.MaxFeeAmount
78- reminder = matchTxFee.FeeAmount - matchTxFee.MaxFeeAmount
79- }
80- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(feeAmount), e.nodeProgram))
81-
82- // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
83- averageAmount := reminder / int64(len(txData.Inputs))
84- if averageAmount == 0 {
85- averageAmount = 1
86- }
87-
88- for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
70+ for i, refund := range refunds {
71+ // each trading participant may be refunded multiple assets
72+ for _, assetAmount := range refund {
8973 contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
9074 if err != nil {
9175 return err
9276 }
9377
94- if i == len(txData.Inputs)-1 {
95- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(reminder), contractArgs.SellerProgram))
96- } else {
97- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(averageAmount), contractArgs.SellerProgram))
98- }
99- reminder -= averageAmount
78+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, contractArgs.SellerProgram))
10079 }
10180 }
10281 return nil
@@ -104,7 +83,7 @@ func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
10483
10584 func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
10685 for i, output := range tx.Outputs {
107- if !segwit.IsP2WMCScript(output.ControlProgram()) {
86+ if !segwit.IsP2WMCScript(output.ControlProgram()) || output.AssetAmount().Amount == 0 {
10887 continue
10988 }
11089
@@ -120,17 +99,18 @@ func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
12099
121100 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
122101 txData := &types.TxData{Version: 1}
123- for i, order := range orders {
102+ for _, order := range orders {
124103 input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
125104 txData.Inputs = append(txData.Inputs, input)
105+ }
126106
127- oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
128- if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil {
129- return nil, err
130- }
107+ receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
108+ allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
109+ if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets.Receives); err != nil {
110+ return nil, err
131111 }
132112
133- if err := e.addMatchTxFeeOutput(txData); err != nil {
113+ if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Refunds, allocatedAssets.Fees); err != nil {
134114 return nil, err
135115 }
136116
@@ -143,94 +123,71 @@ func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
143123 return types.NewTx(*txData), nil
144124 }
145125
146-// MatchedTxFee is object to record the mov tx's fee information
147-type MatchedTxFee struct {
148- MaxFeeAmount int64
149- FeeAmount int64
150-}
151-
152-// CalcMatchedTxFee is used to calculate tx's MatchedTxFees
153-func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) {
154- assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
155- dealProgMaps := make(map[string]bool)
156-
157- for _, input := range txData.Inputs {
158- assetFeeMap[input.AssetID()] = &MatchedTxFee{FeeAmount: int64(input.AssetAmount().Amount)}
159- contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
160- if err != nil {
161- return nil, err
162- }
163-
164- dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
165- }
166-
167- for _, input := range txData.Inputs {
168- contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
126+func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts, deductFeeReceives []*bc.AssetAmount) error {
127+ for i, order := range orders {
128+ contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
169129 if err != nil {
170- return nil, err
130+ return err
171131 }
172132
173- oppositeAmount := uint64(assetFeeMap[contractArgs.RequestedAsset].FeeAmount)
174- receiveAmount := vprMath.MinUint64(CalcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
175- assetFeeMap[input.AssetID()].MaxFeeAmount = calcMaxFeeAmount(calcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate)
176- }
133+ requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
134+ receivedAmount := receivedAmounts[i].Amount
135+ shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
136+ isPartialTrade := requestAmount > receivedAmount
177137
178- for _, output := range txData.Outputs {
179- assetAmount := output.AssetAmount()
180- if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
181- assetFeeMap[*assetAmount.AssetId].FeeAmount -= int64(assetAmount.Amount)
182- if assetFeeMap[*assetAmount.AssetId].FeeAmount <= 0 {
183- delete(assetFeeMap, *assetAmount.AssetId)
184- }
138+ setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmounts[i].Amount)
139+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, deductFeeReceives[i].Amount, contractArgs.SellerProgram))
140+ if isPartialTrade {
141+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
185142 }
186143 }
187- return assetFeeMap, nil
144+ return nil
188145 }
189146
190-func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error {
191- contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
192- if err != nil {
193- return err
194- }
195-
196- requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs)
197- receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
198- shouldPayAmount := calcShouldPayAmount(receiveAmount, contractArgs)
199- isPartialTrade := requestAmount > receiveAmount
200-
201- setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount)
202- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
203- if isPartialTrade {
204- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
205- }
206- return nil
147+func calcOppositeIndex(size int, selfIdx int) int {
148+ return (selfIdx + 1) % size
207149 }
208150
209151 // CalcRequestAmount is from amount * numerator / ratioDenominator
210-func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
152+func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
211153 res := big.NewInt(0).SetUint64(fromAmount)
212- res.Mul(res, big.NewInt(contractArg.RatioNumerator)).Quo(res, big.NewInt(contractArg.RatioDenominator))
154+ res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
213155 if !res.IsUint64() {
214156 return 0
215157 }
216158 return res.Uint64()
217159 }
218160
219-func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
161+func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
220162 res := big.NewInt(0).SetUint64(receiveAmount)
221- res.Mul(res, big.NewInt(contractArg.RatioDenominator)).Quo(res, big.NewInt(contractArg.RatioNumerator))
163+ res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
222164 if !res.IsUint64() {
223165 return 0
224166 }
225167 return res.Uint64()
226168 }
227169
228-func calcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
229- return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
230-}
170+// CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
171+func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, []*bc.AssetAmount) {
172+ var receivedAmounts, priceDiffs, shouldPayAmounts []*bc.AssetAmount
173+ for i, order := range orders {
174+ requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
175+ oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
176+ receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
177+ shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
178+ receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
179+ shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
180+ }
231181
232-func calcOppositeIndex(size int, selfIdx int) int {
233- return (selfIdx + 1) % size
182+ for i, receivedAmount := range receivedAmounts {
183+ oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
184+ if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
185+ assetID := oppositeShouldPayAmount.AssetId
186+ amount := oppositeShouldPayAmount.Amount - receivedAmount.Amount
187+ priceDiffs = append(priceDiffs, &bc.AssetAmount{AssetId: assetID, Amount: amount})
188+ }
189+ }
190+ return receivedAmounts, priceDiffs
234191 }
235192
236193 // IsMatched check does the orders can be exchange
@@ -240,13 +197,12 @@ func IsMatched(orders []*common.Order) bool {
240197 return false
241198 }
242199
243- rate := big.NewRat(sortedOrders[0].RatioDenominator, sortedOrders[0].RatioNumerator)
244- oppositeRate := big.NewRat(1, 1)
245- for i := 1; i < len(sortedOrders); i++ {
246- oppositeRate.Mul(oppositeRate, big.NewRat(sortedOrders[i].RatioNumerator, sortedOrders[i].RatioDenominator))
200+ product := big.NewRat(1, 1)
201+ for _, order := range orders {
202+ product.Mul(product, big.NewRat(order.RatioNumerator, order.RatioDenominator))
247203 }
248-
249- return rate.Cmp(oppositeRate) >= 0
204+ one := big.NewRat(1, 1)
205+ return product.Cmp(one) <= 0
250206 }
251207
252208 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
@@ -259,29 +215,6 @@ func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position i
259215 txInput.SetArguments(arguments)
260216 }
261217
262-func validateTradePairs(tradePairs []*common.TradePair) error {
263- if len(tradePairs) < 2 {
264- return errors.New("size of trade pairs at least 2")
265- }
266-
267- assetMap := make(map[string]bool)
268- for _, tradePair := range tradePairs {
269- assetMap[tradePair.FromAssetID.String()] = true
270- if *tradePair.FromAssetID == *tradePair.ToAssetID {
271- return errors.New("from asset id can't equal to asset id")
272- }
273- }
274-
275- for _, tradePair := range tradePairs {
276- key := tradePair.ToAssetID.String()
277- if _, ok := assetMap[key]; !ok {
278- return errors.New("invalid trade pairs")
279- }
280- delete(assetMap, key)
281- }
282- return nil
283-}
284-
285218 func sortOrders(orders []*common.Order) []*common.Order {
286219 if len(orders) == 0 {
287220 return nil
@@ -305,3 +238,26 @@ func sortOrders(orders []*common.Order) []*common.Order {
305238 }
306239 return sortedOrders
307240 }
241+
242+func validateTradePairs(tradePairs []*common.TradePair) error {
243+ if len(tradePairs) < 2 {
244+ return errors.New("size of trade pairs at least 2")
245+ }
246+
247+ assetMap := make(map[string]bool)
248+ for _, tradePair := range tradePairs {
249+ assetMap[tradePair.FromAssetID.String()] = true
250+ if *tradePair.FromAssetID == *tradePair.ToAssetID {
251+ return errors.New("from asset id can't equal to asset id")
252+ }
253+ }
254+
255+ for _, tradePair := range tradePairs {
256+ key := tradePair.ToAssetID.String()
257+ if _, ok := assetMap[key]; !ok {
258+ return errors.New("invalid trade pairs")
259+ }
260+ delete(assetMap, key)
261+ }
262+ return nil
263+}
--- a/application/mov/match/match_test.go
+++ b/application/mov/match/engine_test.go
@@ -8,7 +8,6 @@ import (
88 "github.com/bytom/vapor/protocol/bc"
99 "github.com/bytom/vapor/protocol/bc/types"
1010 "github.com/bytom/vapor/protocol/validation"
11- "github.com/bytom/vapor/testutil"
1211 )
1312
1413 func TestGenerateMatchedTxs(t *testing.T) {
@@ -76,11 +75,21 @@ func TestGenerateMatchedTxs(t *testing.T) {
7675 mock.MatchedTxs[6],
7776 },
7877 },
78+ {
79+ desc: "multiple assets as a fee",
80+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
81+ initStoreOrders: []*common.Order{
82+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[3],
83+ },
84+ wantMatchedTxs: []*types.Tx{
85+ mock.MatchedTxs[11],
86+ },
87+ },
7988 }
8089
8190 for i, c := range cases {
8291 movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
83- matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), 0.05, mock.NodeProgram)
92+ matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), NewDefaultFeeStrategy(), mock.RewardProgram)
8493 var gotMatchedTxs []*types.Tx
8594 for matchEngine.HasMatchedTx(c.tradePairs...) {
8695 matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
@@ -96,19 +105,19 @@ func TestGenerateMatchedTxs(t *testing.T) {
96105 continue
97106 }
98107
99- for i, gotMatchedTx := range gotMatchedTxs {
108+ for j, gotMatchedTx := range gotMatchedTxs {
100109 if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
101110 t.Fatal(err)
102111 }
103112
104- c.wantMatchedTxs[i].Version = 1
105- byteData, err := c.wantMatchedTxs[i].MarshalText()
113+ c.wantMatchedTxs[j].Version = 1
114+ byteData, err := c.wantMatchedTxs[j].MarshalText()
106115 if err != nil {
107116 t.Fatal(err)
108117 }
109118
110- c.wantMatchedTxs[i].SerializedSize = uint64(len(byteData))
111- wantMatchedTx := types.NewTx(c.wantMatchedTxs[i].TxData)
119+ c.wantMatchedTxs[j].SerializedSize = uint64(len(byteData))
120+ wantMatchedTx := types.NewTx(c.wantMatchedTxs[j].TxData)
112121 if gotMatchedTx.ID != wantMatchedTx.ID {
113122 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())
114123 }
@@ -116,45 +125,6 @@ func TestGenerateMatchedTxs(t *testing.T) {
116125 }
117126 }
118127
119-func TestCalcMatchedTxFee(t *testing.T) {
120- cases := []struct {
121- desc string
122- tx *types.TxData
123- maxFeeRate float64
124- wantMatchedTxFee map[bc.AssetID]*MatchedTxFee
125- }{
126- {
127- desc: "fee less than max fee",
128- maxFeeRate: 0.05,
129- wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 10, MaxFeeAmount: 26}},
130- tx: &mock.MatchedTxs[1].TxData,
131- },
132- {
133- desc: "fee refund in tx",
134- maxFeeRate: 0.05,
135- wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 27, MaxFeeAmount: 27}},
136- tx: &mock.MatchedTxs[2].TxData,
137- },
138- {
139- desc: "fee is zero",
140- maxFeeRate: 0.05,
141- wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{},
142- tx: &mock.MatchedTxs[0].TxData,
143- },
144- }
145-
146- for i, c := range cases {
147- gotMatchedTxFee, err := CalcMatchedTxFee(c.tx, c.maxFeeRate)
148- if err != nil {
149- t.Fatal(err)
150- }
151-
152- if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
153- t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
154- }
155- }
156-}
157-
158128 func TestValidateTradePairs(t *testing.T) {
159129 cases := []struct {
160130 desc string
--- /dev/null
+++ b/application/mov/match/fee_strategy.go
@@ -0,0 +1,108 @@
1+package match
2+
3+import (
4+ "math"
5+
6+ "github.com/bytom/vapor/errors"
7+ "github.com/bytom/vapor/protocol/bc"
8+)
9+
10+var (
11+ // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
12+ ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
13+)
14+
15+// AllocatedAssets represent reallocated assets after calculating fees
16+type AllocatedAssets struct {
17+ Receives []*bc.AssetAmount
18+ Refunds []RefundAssets
19+ Fees []*bc.AssetAmount
20+}
21+
22+// RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
23+type RefundAssets []*bc.AssetAmount
24+
25+// FeeStrategy used to indicate how to charge a matching fee
26+type FeeStrategy interface {
27+ // Allocate will allocate the price differential in matching transaction to the participants and the fee
28+ // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
29+ // @param priceDiffs price differential of matching transaction
30+ // @return reallocated assets after calculating fees
31+ Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
32+
33+ // Validate verify that the fee charged for a matching transaction is correct
34+ Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
35+}
36+
37+// DefaultFeeStrategy represent the default fee charge strategy
38+type DefaultFeeStrategy struct {}
39+
40+// NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
41+func NewDefaultFeeStrategy() *DefaultFeeStrategy {
42+ return &DefaultFeeStrategy{}
43+}
44+
45+// Allocate will allocate the price differential in matching transaction to the participants and the fee
46+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
47+ feeMap := make(map[bc.AssetID]uint64)
48+ for _, priceDiff := range priceDiffs {
49+ feeMap[*priceDiff.AssetId] = priceDiff.Amount
50+ }
51+
52+ var fees []*bc.AssetAmount
53+ refunds := make([]RefundAssets, len(receiveAmounts))
54+ receives := make([]*bc.AssetAmount, len(receiveAmounts))
55+
56+ for i, receiveAmount := range receiveAmounts {
57+ amount := receiveAmount.Amount
58+ minFeeAmount := d.calcMinFeeAmount(amount)
59+ receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: amount - minFeeAmount}
60+ feeMap[*receiveAmount.AssetId] += minFeeAmount
61+
62+ maxFeeAmount := d.calcMaxFeeAmount(amount)
63+ feeAmount, reminder := feeMap[*receiveAmount.AssetId], uint64(0)
64+ if feeAmount > maxFeeAmount {
65+ reminder = feeAmount - maxFeeAmount
66+ feeAmount = maxFeeAmount
67+ }
68+
69+ fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
70+
71+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
72+ averageAmount := reminder / uint64(len(receiveAmounts))
73+ if averageAmount == 0 {
74+ averageAmount = 1
75+ }
76+
77+ for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
78+ refundAmount := averageAmount
79+ if j == len(receiveAmounts)-1 {
80+ refundAmount = reminder
81+ }
82+ refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
83+ reminder -= averageAmount
84+ }
85+ }
86+ return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
87+}
88+
89+// Validate verify that the fee charged for a matching transaction is correct
90+func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
91+ for _, receiveAmount := range receiveAmounts {
92+ feeAmount := feeAmounts[*receiveAmount.AssetId]
93+ maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
94+ minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
95+ if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
96+ return ErrAmountOfFeeOutOfRange
97+ }
98+ }
99+ return nil
100+}
101+
102+func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
103+ return uint64(math.Ceil(float64(amount) / 1000))
104+}
105+
106+func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
107+ return uint64(math.Ceil(float64(amount) * 0.05))
108+}
--- a/application/mov/mock/mock.go
+++ b/application/mov/mock/mock.go
@@ -10,11 +10,11 @@ import (
1010 )
1111
1212 var (
13- BTC = bc.NewAssetID([32]byte{1})
14- ETH = bc.NewAssetID([32]byte{2})
15- EOS = bc.NewAssetID([32]byte{3})
16- ETC = bc.NewAssetID([32]byte{4})
17- NodeProgram = []byte{0x58}
13+ BTC = bc.NewAssetID([32]byte{1})
14+ ETH = bc.NewAssetID([32]byte{2})
15+ EOS = bc.NewAssetID([32]byte{3})
16+ ETC = bc.NewAssetID([32]byte{4})
17+ RewardProgram = []byte{0x58}
1818
1919 Btc2EthOrders = []*common.Order{
2020 {
@@ -104,6 +104,18 @@ var (
104104 ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
105105 },
106106 },
107+ {
108+ FromAssetID: &ETH,
109+ ToAssetID: &BTC,
110+ RatioNumerator: 1,
111+ RatioDenominator: 150,
112+ Utxo: &common.MovUtxo{
113+ SourceID: hashPtr(testutil.MustDecodeHash("82752cda63c877a8529d7a7461da6096673e45b3e0b019ce44aa18687ad20445")),
114+ SourcePos: 0,
115+ Amount: 600,
116+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"), 1, 150.0),
117+ },
118+ },
107119 }
108120
109121 Eos2EtcOrders = []*common.Order{
@@ -253,10 +265,13 @@ var (
253265 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),
254266 },
255267 Outputs: []*types.TxOutput{
256- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
268+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
257269 // re-order
258270 types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
259- types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
271+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
272+ // fee
273+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
274+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
260275 },
261276 }),
262277
@@ -267,9 +282,11 @@ var (
267282 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),
268283 },
269284 Outputs: []*types.TxOutput{
270- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
271- types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
272- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, NodeProgram),
285+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
286+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
287+ // fee
288+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 11, RewardProgram),
289+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
273290 },
274291 }),
275292
@@ -280,15 +297,16 @@ var (
280297 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),
281298 },
282299 Outputs: []*types.TxOutput{
283- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
284- types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
300+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
301+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
285302 // re-order
286303 types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
287304 // fee
288- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 27, NodeProgram),
305+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram),
306+ types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 1, RewardProgram),
289307 // refund
290- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 6, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
291- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
308+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
309+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
292310 },
293311 }),
294312 types.NewTx(types.TxData{
@@ -297,10 +315,13 @@ var (
297315 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("39bdb7058a0c31fb740af8e3c382bf608efff1b041cd4dd461332722ad24552a"), *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram),
298316 },
299317 Outputs: []*types.TxOutput{
300- types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 270, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
318+ types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 269, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
301319 // re-order
302320 types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, 15, Btc2EthOrders[1].Utxo.ControlProgram),
303- types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 5, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
321+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 4, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
322+ // fee
323+ types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 1, RewardProgram),
324+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
304325 },
305326 }),
306327
@@ -311,10 +332,13 @@ var (
311332 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),
312333 },
313334 Outputs: []*types.TxOutput{
314- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
335+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
315336 // re-order
316337 types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
317- types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
338+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
339+ // fee
340+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
341+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
318342 },
319343 }),
320344
@@ -325,8 +349,11 @@ var (
325349 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),
326350 },
327351 Outputs: []*types.TxOutput{
328- types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 50, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
329- types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
352+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
353+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
354+ // fee
355+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
356+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
330357 },
331358 }),
332359
@@ -338,9 +365,13 @@ var (
338365 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),
339366 },
340367 Outputs: []*types.TxOutput{
341- types.NewIntraChainOutput(ETH, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
342- types.NewIntraChainOutput(EOS, 1000, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
343- types.NewIntraChainOutput(BTC, 10, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
368+ types.NewIntraChainOutput(ETH, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
369+ types.NewIntraChainOutput(EOS, 999, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
370+ types.NewIntraChainOutput(BTC, 9, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
371+ // fee
372+ types.NewIntraChainOutput(ETH, 1, RewardProgram),
373+ types.NewIntraChainOutput(EOS, 1, RewardProgram),
374+ types.NewIntraChainOutput(BTC, 1, RewardProgram),
344375 },
345376 }),
346377
@@ -351,10 +382,13 @@ var (
351382 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),
352383 },
353384 Outputs: []*types.TxOutput{
354- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
355- types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 2, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
385+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
386+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
356387 // re-order
357388 types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, 404, Eth2BtcOrders[0].Utxo.ControlProgram),
389+ // fee
390+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
391+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
358392 },
359393 }),
360394
@@ -365,12 +399,13 @@ var (
365399 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),
366400 },
367401 Outputs: []*types.TxOutput{
368- types.NewIntraChainOutput(*Btc2EthOrders[3].ToAssetID, 810, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
402+ types.NewIntraChainOutput(*Btc2EthOrders[3].ToAssetID, 809, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
369403 // re-order
370404 types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 1, Btc2EthOrders[3].Utxo.ControlProgram),
371- types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 15, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
405+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 14, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
372406 // fee
373- types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 1, NodeProgram),
407+ types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 2, RewardProgram),
408+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
374409 },
375410 }),
376411
@@ -381,8 +416,11 @@ var (
381416 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),
382417 },
383418 Outputs: []*types.TxOutput{
384- types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 50, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
385- types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
419+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
420+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
421+ // fee
422+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
423+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
386424 },
387425 }),
388426
@@ -393,9 +431,32 @@ var (
393431 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),
394432 },
395433 Outputs: []*types.TxOutput{
396- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
397- types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
398- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, NodeProgram),
434+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
435+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
436+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, RewardProgram),
437+ // fee
438+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
439+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
440+ },
441+ }),
442+
443+ // full matched transaction from Btc2EthOrders[0] Eth2BtcOrders[3]
444+ types.NewTx(types.TxData{
445+ Inputs: []*types.TxInput{
446+ 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),
447+ 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),
448+ },
449+ Outputs: []*types.TxOutput{
450+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
451+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
452+ // fee
453+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 25, RewardProgram),
454+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 1, RewardProgram),
455+ // refund
456+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
457+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
458+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
459+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
399460 },
400461 }),
401462 }
--- a/application/mov/mov_core.go
+++ b/application/mov/mov_core.go
@@ -1,10 +1,13 @@
11 package mov
22
33 import (
4+ "encoding/hex"
5+
46 "github.com/bytom/vapor/application/mov/common"
57 "github.com/bytom/vapor/application/mov/contract"
68 "github.com/bytom/vapor/application/mov/database"
79 "github.com/bytom/vapor/application/mov/match"
10+ "github.com/bytom/vapor/consensus"
811 "github.com/bytom/vapor/consensus/segwit"
912 dbm "github.com/bytom/vapor/database/leveldb"
1013 "github.com/bytom/vapor/errors"
@@ -12,50 +15,49 @@ import (
1215 "github.com/bytom/vapor/protocol/bc/types"
1316 )
1417
15-const maxFeeRate = 0.05
16-
1718 var (
18- errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
19- errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
20- errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
21- errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
22- errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
23- errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
24- errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
25- errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
26- errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
27- errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
28- errNotMatchedOrder = errors.New("order in matched tx is not matched")
19+ errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
20+ errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
21+ errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
22+ errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
23+ errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
24+ errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
25+ errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
26+ errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
27+ errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
28+ errNotMatchedOrder = errors.New("order in matched tx is not matched")
29+ errNotConfiguredRewardProgram = errors.New("reward program is not configured properly")
30+ errRewardProgramIsWrong = errors.New("the reward program is not correct")
2931 )
3032
31-// MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
33+// Core represent the core logic of the match module, which include generate match transactions before packing the block,
3234 // verify the match transaction in block is correct, and update the order table according to the transaction.
33-type MovCore struct {
35+type Core struct {
3436 movStore database.MovStore
3537 startBlockHeight uint64
3638 }
3739
38-// NewMovCore return a instance of MovCore by path of mov db
39-func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
40+// NewCore return a instance of Core by path of mov db
41+func NewCore(dbBackend, dbDir string, startBlockHeight uint64) *Core {
4042 movDB := dbm.NewDB("mov", dbBackend, dbDir)
41- return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
43+ return &Core{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
44+}
45+
46+// NewCoreWithDB return a instance of Core by movStore
47+func NewCoreWithDB(store *database.LevelDBMovStore, startBlockHeight uint64) *Core {
48+ return &Core{movStore: store, startBlockHeight: startBlockHeight}
4249 }
4350
4451 // ApplyBlock parse pending order and cancel from the the transactions of block
4552 // and add pending order to the dex db, remove cancel order from dex db.
46-func (m *MovCore) ApplyBlock(block *types.Block) error {
53+func (m *Core) ApplyBlock(block *types.Block) error {
4754 if block.Height < m.startBlockHeight {
4855 return nil
4956 }
5057
5158 if block.Height == m.startBlockHeight {
5259 blockHash := block.Hash()
53- if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
54- return err
55- }
56-
57- // the next block can send orders
58- return nil
60+ return m.movStore.InitDBState(block.Height, &blockHash)
5961 }
6062
6163 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
@@ -71,7 +73,7 @@ func (m *MovCore) ApplyBlock(block *types.Block) error {
7173 }
7274
7375 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
74-func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
76+func (m *Core) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
7577 if blockHeight <= m.startBlockHeight {
7678 return nil, nil
7779 }
@@ -81,14 +83,20 @@ func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, block
8183 return nil, err
8284 }
8385
84- matchEngine := match.NewEngine(orderBook, maxFeeRate, nodeProgram)
86+ program, _ := getRewardProgram(blockHeight)
87+ rewardProgram, err := hex.DecodeString(program)
88+ if err != nil {
89+ return nil, errNotConfiguredRewardProgram
90+ }
91+
92+ matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
8593 tradePairIterator := database.NewTradePairIterator(m.movStore)
8694 matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
8795 return matchCollector.result()
8896 }
8997
9098 // ChainStatus return the current block height and block hash in dex core
91-func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
99+func (m *Core) ChainStatus() (uint64, *bc.Hash, error) {
92100 state, err := m.movStore.GetMovDatabaseState()
93101 if err != nil {
94102 return 0, nil, err
@@ -99,7 +107,7 @@ func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
99107
100108 // DetachBlock parse pending order and cancel from the the transactions of block
101109 // and add cancel order to the dex db, remove pending order from dex db.
102-func (m *MovCore) DetachBlock(block *types.Block) error {
110+func (m *Core) DetachBlock(block *types.Block) error {
103111 if block.Height < m.startBlockHeight {
104112 return nil
105113 }
@@ -113,7 +121,7 @@ func (m *MovCore) DetachBlock(block *types.Block) error {
113121 }
114122
115123 // IsDust block the transaction that are not generated by the match engine
116-func (m *MovCore) IsDust(tx *types.Tx) bool {
124+func (m *Core) IsDust(tx *types.Tx) bool {
117125 for _, input := range tx.Inputs {
118126 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
119127 return true
@@ -123,25 +131,20 @@ func (m *MovCore) IsDust(tx *types.Tx) bool {
123131 }
124132
125133 // Name return the name of current module
126-func (m *MovCore) Name() string {
134+func (m *Core) Name() string {
127135 return "MOV"
128136 }
129137
130138 // StartHeight return the start block height of current module
131-func (m *MovCore) StartHeight() uint64 {
139+func (m *Core) StartHeight() uint64 {
132140 return m.startBlockHeight
133141 }
134142
135143 // ValidateBlock no need to verify the block header, because the first module has been verified.
136144 // just need to verify the transactions in the block.
137-func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
138- return m.ValidateTxs(block.Transactions, verifyResults)
139-}
140-
141-// ValidateTxs validate the trade transaction.
142-func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
143- for i, tx := range txs {
144- if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
145+func (m *Core) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
146+ for i, tx := range block.Transactions {
147+ if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
145148 return err
146149 }
147150 }
@@ -149,9 +152,17 @@ func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResul
149152 }
150153
151154 // ValidateTx validate one transaction.
152-func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
155+func (m *Core) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
156+ if blockHeight <= m.startBlockHeight {
157+ return nil
158+ }
159+
160+ if verifyResult.StatusFail {
161+ return errStatusFailMustFalse
162+ }
163+
153164 if common.IsMatchedTx(tx) {
154- if err := validateMatchedTx(tx, verifyResult); err != nil {
165+ if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil {
155166 return err
156167 }
157168 } else if common.IsCancelOrderTx(tx) {
@@ -164,9 +175,6 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro
164175 if !segwit.IsP2WMCScript(output.ControlProgram()) {
165176 continue
166177 }
167- if verifyResult.StatusFail {
168- return errStatusFailMustFalse
169- }
170178
171179 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
172180 return err
@@ -175,11 +183,44 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro
175183 return nil
176184 }
177185
178-func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
179- if verifyResult.StatusFail {
180- return errStatusFailMustFalse
186+// matchedTxFee is object to record the mov tx's fee information
187+type matchedTxFee struct {
188+ rewardProgram []byte
189+ amount uint64
190+}
191+
192+// calcFeeAmount return the amount of fee in the matching transaction
193+func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
194+ assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
195+ dealProgMaps := make(map[string]bool)
196+
197+ for _, input := range matchedTx.Inputs {
198+ assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
199+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
200+ if err != nil {
201+ return nil, err
202+ }
203+
204+ dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
181205 }
182206
207+ for _, output := range matchedTx.Outputs {
208+ assetAmount := output.AssetAmount()
209+ if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
210+ assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
211+ if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
212+ delete(assetFeeMap, *assetAmount.AssetId)
213+ }
214+ } else if assetFeeMap[*assetAmount.AssetId].rewardProgram == nil {
215+ assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
216+ } else {
217+ return nil, errors.Wrap(errRewardProgramIsWrong, "double reward program")
218+ }
219+ }
220+ return assetFeeMap, nil
221+}
222+
223+func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
183224 for _, input := range tx.Inputs {
184225 if !segwit.IsP2WMCScript(input.ControlProgram()) {
185226 return errInputProgramMustP2WMCScript
@@ -206,17 +247,13 @@ func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte
206247 return errRatioOfTradeLessThanZero
207248 }
208249
209- if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
250+ if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
210251 return errRequestAmountMath
211252 }
212253 return nil
213254 }
214255
215-func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
216- if verifyResult.StatusFail {
217- return errStatusFailMustFalse
218- }
219-
256+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
220257 fromAssetIDMap := make(map[string]bool)
221258 toAssetIDMap := make(map[string]bool)
222259 for i, input := range tx.Inputs {
@@ -241,24 +278,37 @@ func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
241278 return errAssetIDMustUniqueInMatchedTx
242279 }
243280
244- return validateMatchedTxFeeAmount(tx)
281+ return validateMatchedTxFee(tx, blockHeight)
245282 }
246283
247-func validateMatchedTxFeeAmount(tx *types.Tx) error {
248- txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
284+func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
285+ matchedTxFees, err := calcFeeAmount(tx)
249286 if err != nil {
250287 return err
251288 }
252289
253- for _, amount := range txFee {
254- if amount.FeeAmount > amount.MaxFeeAmount {
255- return errAmountOfFeeGreaterThanMaximum
290+ for _, fee := range matchedTxFees {
291+ if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
292+ return err
256293 }
257294 }
258- return nil
295+
296+ orders, err := getDeleteOrdersFromTx(tx)
297+ if err != nil {
298+ return err
299+ }
300+
301+ receivedAmount, _ := match.CalcReceivedAmount(orders)
302+ feeAmounts := make(map[bc.AssetID]uint64)
303+ for assetID, fee := range matchedTxFees {
304+ feeAmounts[assetID] = fee.amount
305+ }
306+
307+ feeStrategy := match.NewDefaultFeeStrategy()
308+ return feeStrategy.Validate(receivedAmount, feeAmounts)
259309 }
260310
261-func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
311+func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
262312 orderBook := match.NewOrderBook(m.movStore, nil, nil)
263313 for _, tx := range txs {
264314 if common.IsMatchedTx(tx) {
@@ -348,15 +398,8 @@ func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error)
348398 }
349399
350400 func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
351- var nonMatchedTxs []*types.Tx
352- for _, tx := range txs {
353- if !common.IsMatchedTx(tx) {
354- nonMatchedTxs = append(nonMatchedTxs, tx)
355- }
356- }
357-
358401 var arrivalAddOrders, arrivalDelOrders []*common.Order
359- for _, tx := range nonMatchedTxs {
402+ for _, tx := range txs {
360403 addOrders, err := getAddOrdersFromTx(tx)
361404 if err != nil {
362405 return nil, err
@@ -381,6 +424,10 @@ func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
381424 continue
382425 }
383426
427+ if output.AssetAmount().Amount == 0 {
428+ continue
429+ }
430+
384431 order, err := common.NewOrderFromOutput(tx, i)
385432 if err != nil {
386433 return nil, err
@@ -436,3 +483,31 @@ func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*commo
436483 }
437484 return addOrders, deleteOrders
438485 }
486+
487+// getRewardProgram return the reward program by specified block height
488+// if no reward program configured, then will return empty string
489+// if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
490+// the second return value represent whether to find exactly
491+func getRewardProgram(height uint64) (string, bool) {
492+ rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
493+ if len(rewardPrograms) == 0 {
494+ return "", false
495+ }
496+
497+ var program string
498+ for _, rewardProgram := range rewardPrograms {
499+ program = rewardProgram.Program
500+ if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
501+ return program, true
502+ }
503+ }
504+ return program, false
505+}
506+
507+func validateRewardProgram(height uint64, program string) error {
508+ rewardProgram, exact := getRewardProgram(height)
509+ if exact && rewardProgram != program {
510+ return errRewardProgramIsWrong
511+ }
512+ return nil
513+}
--- a/application/mov/mov_core_test.go
+++ b/application/mov/mov_core_test.go
@@ -1,12 +1,14 @@
11 package mov
22
33 import (
4+ "encoding/hex"
45 "math"
56 "os"
67 "testing"
78
89 "github.com/bytom/vapor/application/mov/common"
910 "github.com/bytom/vapor/application/mov/database"
11+ "github.com/bytom/vapor/application/mov/match"
1012 "github.com/bytom/vapor/application/mov/mock"
1113 "github.com/bytom/vapor/consensus"
1214 dbm "github.com/bytom/vapor/database/leveldb"
@@ -262,7 +264,7 @@ func TestApplyBlock(t *testing.T) {
262264 t.Fatal(err)
263265 }
264266
265- movCore := &MovCore{movStore: store}
267+ movCore := &Core{movStore: store}
266268 if err := c.blockFunc(movCore, c.block); err != c.wantError {
267269 t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
268270 }
@@ -287,6 +289,14 @@ func TestApplyBlock(t *testing.T) {
287289 }
288290
289291 func TestValidateBlock(t *testing.T) {
292+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
293+ {
294+ BeginBlock: 0,
295+ EndBlock: 100,
296+ Program: hex.EncodeToString(mock.RewardProgram),
297+ },
298+ }
299+
290300 cases := []struct {
291301 desc string
292302 block *types.Block
@@ -362,9 +372,11 @@ func TestValidateBlock(t *testing.T) {
362372 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),
363373 },
364374 Outputs: []*types.TxOutput{
365- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
366- types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
367- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
375+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
376+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
377+ // fee
378+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
379+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
368380 },
369381 }),
370382 },
@@ -383,9 +395,10 @@ func TestValidateBlock(t *testing.T) {
383395 types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
384396 },
385397 Outputs: []*types.TxOutput{
386- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
387- types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
388- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
398+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
399+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
400+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
401+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
389402 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
390403 },
391404 }),
@@ -405,10 +418,11 @@ func TestValidateBlock(t *testing.T) {
405418 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),
406419 },
407420 Outputs: []*types.TxOutput{
408- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
409- types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
410- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
411- types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
421+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
422+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
423+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
424+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
425+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
412426 },
413427 }),
414428 },
@@ -427,7 +441,7 @@ func TestValidateBlock(t *testing.T) {
427441 },
428442 Outputs: []*types.TxOutput{
429443 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
430- types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
444+ types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
431445 },
432446 }),
433447 },
@@ -445,18 +459,19 @@ func TestValidateBlock(t *testing.T) {
445459 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),
446460 },
447461 Outputs: []*types.TxOutput{
448- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
449- types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
462+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
463+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
450464 // re-order
451465 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
452466 // fee
453- types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
467+ types.NewIntraChainOutput(*mock.Btc2EthOrders[2].ToAssetID, 41, mock.RewardProgram),
468+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 1, mock.RewardProgram),
454469 },
455470 }),
456471 },
457472 },
458473 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
459- wantError: errAmountOfFeeGreaterThanMaximum,
474+ wantError: match.ErrAmountOfFeeOutOfRange,
460475 },
461476 {
462477 desc: "ratio numerator is zero",
@@ -500,14 +515,71 @@ func TestValidateBlock(t *testing.T) {
500515 }
501516
502517 for i, c := range cases {
503- movCore := &MovCore{}
518+ movCore := &Core{}
519+ c.block.Height = 3456786543
504520 if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
505521 t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
506522 }
507523 }
508524 }
509525
526+func TestCalcMatchedTxFee(t *testing.T) {
527+ cases := []struct {
528+ desc string
529+ tx types.TxData
530+ maxFeeRate float64
531+ wantMatchedTxFee map[bc.AssetID]*matchedTxFee
532+ }{
533+ {
534+ desc: "fee less than max fee",
535+ maxFeeRate: 0.05,
536+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
537+ mock.ETH: {amount: 11, rewardProgram: mock.RewardProgram},
538+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
539+ },
540+ tx: mock.MatchedTxs[1].TxData,
541+ },
542+ {
543+ desc: "fee refund in tx",
544+ maxFeeRate: 0.05,
545+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
546+ mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram},
547+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
548+ },
549+ tx: mock.MatchedTxs[2].TxData,
550+ },
551+ {
552+ desc: "no price diff",
553+ maxFeeRate: 0.05,
554+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
555+ mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
556+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
557+ },
558+ tx: mock.MatchedTxs[0].TxData,
559+ },
560+ }
561+
562+ for i, c := range cases {
563+ gotMatchedTxFee, err := calcFeeAmount(types.NewTx(c.tx))
564+ if err != nil {
565+ t.Fatal(err)
566+ }
567+
568+ if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
569+ t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
570+ }
571+ }
572+}
573+
510574 func TestBeforeProposalBlock(t *testing.T) {
575+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
576+ {
577+ BeginBlock: 0,
578+ EndBlock: 100,
579+ Program: hex.EncodeToString(mock.RewardProgram),
580+ },
581+ }
582+
511583 cases := []struct {
512584 desc string
513585 initOrders []*common.Order
@@ -571,8 +643,8 @@ func TestBeforeProposalBlock(t *testing.T) {
571643 t.Fatal(err)
572644 }
573645
574- movCore := &MovCore{movStore: store}
575- gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, []byte{0x51}, 2, c.gasLeft, func() bool { return false })
646+ movCore := &Core{movStore: store}
647+ gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, 2, c.gasLeft, func() bool { return false })
576648 if err != nil {
577649 t.Fatal(err)
578650 }
@@ -724,7 +796,7 @@ func TestValidateMatchedTxSequence(t *testing.T) {
724796 t.Fatal(err)
725797 }
726798
727- movCore := &MovCore{movStore: store}
799+ movCore := &Core{movStore: store}
728800 if err := movCore.validateMatchedTxSequence(c.transactions); err != c.wantError {
729801 t.Errorf("#%d(%s):wanet error(%v), got error(%v)", i, c.desc, c.wantError, err)
730802 }
@@ -734,13 +806,13 @@ func TestValidateMatchedTxSequence(t *testing.T) {
734806 }
735807 }
736808
737-type testFun func(movCore *MovCore, block *types.Block) error
809+type testFun func(movCore *Core, block *types.Block) error
738810
739-func applyBlock(movCore *MovCore, block *types.Block) error {
811+func applyBlock(movCore *Core, block *types.Block) error {
740812 return movCore.ApplyBlock(block)
741813 }
742814
743-func detachBlock(movCore *MovCore, block *types.Block) error {
815+func detachBlock(movCore *Core, block *types.Block) error {
744816 return movCore.DetachBlock(block)
745817 }
746818
--- a/blockchain/txbuilder/signing_instruction.go
+++ b/blockchain/txbuilder/signing_instruction.go
@@ -8,6 +8,7 @@ import (
88 "github.com/bytom/vapor/errors"
99 )
1010
11+// AddDataWitness append data to the witness array
1112 func (si *SigningInstruction) AddDataWitness(data chainjson.HexBytes) {
1213 dw := DataWitness(data)
1314 si.WitnessComponents = append(si.WitnessComponents, &dw)
--- a/cmd/vapord/commands/root.go
+++ b/cmd/vapord/commands/root.go
@@ -15,6 +15,7 @@ var (
1515 config = cfg.DefaultConfig()
1616 )
1717
18+// RootCmd is the command for run node
1819 var RootCmd = &cobra.Command{
1920 Use: "vapord",
2021 Short: "Multiple asset management.",
--- a/common/bytes.go
+++ b/common/bytes.go
@@ -6,6 +6,7 @@ import (
66 "encoding/hex"
77 )
88
9+// FromHex convert hex byte string to []byte
910 func FromHex(s string) []byte {
1011 if len(s) > 1 {
1112 if s[0:2] == "0x" {
@@ -19,21 +20,25 @@ func FromHex(s string) []byte {
1920 return nil
2021 }
2122
23+// Bytes2Hex convert byte array to string
2224 func Bytes2Hex(d []byte) string {
2325 return hex.EncodeToString(d)
2426 }
2527
28+// Hex2Bytes convert hex string to byte array
2629 func Hex2Bytes(str string) []byte {
2730 h, _ := hex.DecodeString(str)
2831 return h
2932 }
3033
34+// Unit64ToBytes convert uint64 to bytes
3135 func Unit64ToBytes(n uint64) []byte {
3236 buf := make([]byte, 8)
3337 binary.LittleEndian.PutUint64(buf, n)
3438 return buf
3539 }
3640
41+// BytesToUnit64 convert bytes to uint64
3742 func BytesToUnit64(b []byte) uint64 {
3843 return binary.LittleEndian.Uint64(b)
3944 }
--- a/common/crossin_asset.go
+++ b/common/crossin_asset.go
@@ -4,6 +4,7 @@ import (
44 "encoding/json"
55 )
66
7+// IsOpenFederationIssueAsset check if the asset definition satisfy ofmf asset
78 func IsOpenFederationIssueAsset(rawDefinitionByte []byte) bool {
89 var defMap map[string]interface{}
910 if err := json.Unmarshal(rawDefinitionByte, &defMap); err != nil {
@@ -20,8 +21,5 @@ func IsOpenFederationIssueAsset(rawDefinitionByte []byte) bool {
2021 return false
2122 }
2223
23- if issueAssetAction != "cross_chain" {
24- return false
25- }
26- return true
24+ return issueAssetAction == "open_federation_cross_chain"
2725 }
--- a/common/sort.go
+++ /dev/null
@@ -1,23 +0,0 @@
1-package common
2-
3-// timeSorter implements sort.Interface to allow a slice of timestamps to
4-// be sorted.
5-type TimeSorter []uint64
6-
7-// Len returns the number of timestamps in the slice. It is part of the
8-// sort.Interface implementation.
9-func (s TimeSorter) Len() int {
10- return len(s)
11-}
12-
13-// Swap swaps the timestamps at the passed indices. It is part of the
14-// sort.Interface implementation.
15-func (s TimeSorter) Swap(i, j int) {
16- s[i], s[j] = s[j], s[i]
17-}
18-
19-// Less returns whether the timstamp with index i should sort before the
20-// timestamp with index j. It is part of the sort.Interface implementation.
21-func (s TimeSorter) Less(i, j int) bool {
22- return s[i] < s[j]
23-}
--- a/consensus/general.go
+++ b/consensus/general.go
@@ -75,6 +75,13 @@ type ProducerSubsidy struct {
7575 Subsidy uint64
7676 }
7777
78+// MovRewardProgram is a reward address corresponding to the range of the specified block height when matching transactions
79+type MovRewardProgram struct {
80+ BeginBlock uint64
81+ EndBlock uint64
82+ Program string
83+}
84+
7885 // Params store the config for different network
7986 type Params struct {
8087 // Name defines a human-readable identifier for the network.
@@ -103,7 +110,12 @@ type Params struct {
103110 ProducerSubsidys []ProducerSubsidy
104111
105112 SoftForkPoint map[uint64]uint64
113+
114+ // Mov will only start when the block height is greater than this value
106115 MovStartHeight uint64
116+
117+ // Used to receive rewards for matching transactions
118+ MovRewardPrograms []MovRewardProgram
107119 }
108120
109121 // ActiveNetParams is the active NetParams
@@ -145,7 +157,15 @@ var MainNetParams = Params{
145157 ProducerSubsidys: []ProducerSubsidy{
146158 {BeginBlock: 1, EndBlock: 63072000, Subsidy: 9512938},
147159 },
148- SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600},
160+ SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600},
161+ MovStartHeight: 43354800,
162+ MovRewardPrograms: []MovRewardProgram{
163+ {
164+ BeginBlock: 1,
165+ EndBlock: 126144000,
166+ Program: "00141d00f85e220e35a23282cfc7f91fe7b34bf6dc18",
167+ },
168+ },
149169 }
150170
151171 // TestNetParams is the config for vapor-testnet
@@ -234,6 +254,7 @@ func BytomMainNetParams(vaporParam *Params) *Params {
234254 return &Params{Bech32HRPSegwit: bech32HRPSegwit}
235255 }
236256
257+// InitActiveNetParams load the config by chain ID
237258 func InitActiveNetParams(chainID string) error {
238259 var exist bool
239260 if ActiveNetParams, exist = NetParams[chainID]; !exist {
--- a/database/account_store.go
+++ b/database/account_store.go
@@ -45,7 +45,7 @@ func accountIndexKey(xpubs []chainkd.XPub) []byte {
4545 return append(AccountIndexPrefix, hash[:]...)
4646 }
4747
48-func Bip44ContractIndexKey(accountID string, change bool) []byte {
48+func bip44ContractIndexKey(accountID string, change bool) []byte {
4949 key := append(ContractIndexPrefix, []byte(accountID)...)
5050 if change {
5151 return append(key, 0x01)
@@ -96,7 +96,7 @@ func (store *AccountStore) InitBatch() acc.AccountStore {
9696 // CommitBatch commit batch
9797 func (store *AccountStore) CommitBatch() error {
9898 if store.batch == nil {
99- return errors.New("AccountStore commit fail, store batch is nil.")
99+ return errors.New("accountStore commit fail, store batch is nil")
100100 }
101101 store.batch.Write()
102102 store.batch = nil
@@ -119,8 +119,8 @@ func (store *AccountStore) DeleteAccount(account *acc.Account) error {
119119 }
120120
121121 // delete bip44 contract index
122- batch.Delete(Bip44ContractIndexKey(account.ID, false))
123- batch.Delete(Bip44ContractIndexKey(account.ID, true))
122+ batch.Delete(bip44ContractIndexKey(account.ID, false))
123+ batch.Delete(bip44ContractIndexKey(account.ID, true))
124124
125125 // delete contract index
126126 batch.Delete(contractIndexKey(account.ID))
@@ -216,7 +216,7 @@ func (store *AccountStore) GetAccountIndex(xpubs []chainkd.XPub) uint64 {
216216 // GetBip44ContractIndex get bip44 contract index
217217 func (store *AccountStore) GetBip44ContractIndex(accountID string, change bool) uint64 {
218218 index := uint64(0)
219- if rawIndexBytes := store.db.Get(Bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
219+ if rawIndexBytes := store.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
220220 index = common.BytesToUnit64(rawIndexBytes)
221221 }
222222 return index
@@ -367,9 +367,9 @@ func (store *AccountStore) SetAccountIndex(account *acc.Account) {
367367 // SetBip44ContractIndex set contract index
368368 func (store *AccountStore) SetBip44ContractIndex(accountID string, change bool, index uint64) {
369369 if store.batch == nil {
370- store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
370+ store.db.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
371371 } else {
372- store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
372+ store.batch.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
373373 }
374374 }
375375
--- a/database/utxo_view.go
+++ b/database/utxo_view.go
@@ -1,12 +1,12 @@
11 package database
22
33 import (
4- "github.com/golang/protobuf/proto"
54 dbm "github.com/bytom/vapor/database/leveldb"
65 "github.com/bytom/vapor/database/storage"
76 "github.com/bytom/vapor/errors"
87 "github.com/bytom/vapor/protocol/bc"
98 "github.com/bytom/vapor/protocol/state"
9+ "github.com/golang/protobuf/proto"
1010 )
1111
1212 const utxoPreFix = "UT:"
@@ -91,6 +91,7 @@ func saveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
9191 return nil
9292 }
9393
94+// SaveUtxoView is export for intergation test
9495 func SaveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
9596 return saveUtxoView(batch, view)
9697 }
--- a/database/wallet_store.go
+++ b/database/wallet_store.go
@@ -31,6 +31,7 @@ const (
3131 recoveryKey //recoveryKey key for db store recovery info.
3232 )
3333
34+// pre-define variables
3435 var (
3536 walletStore = []byte("WS:")
3637 SUTXOPrefix = append(walletStore, sutxoPrefix, colon)
@@ -66,10 +67,12 @@ func calcUnconfirmedTxKey(formatKey string) []byte {
6667 return append(UnconfirmedTxPrefix, []byte(formatKey)...)
6768 }
6869
70+// CalcGlobalTxIndexKey calculate tx hash index key
6971 func CalcGlobalTxIndexKey(txID string) []byte {
7072 return append(GlobalTxIndexPrefix, []byte(txID)...)
7173 }
7274
75+// CalcGlobalTxIndex calcuate the block index + position index key
7376 func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
7477 txIdx := make([]byte, 40)
7578 copy(txIdx[:32], blockHash.Bytes())
@@ -109,7 +112,7 @@ func (store *WalletStore) InitBatch() wallet.WalletStore {
109112 // CommitBatch commit batch
110113 func (store *WalletStore) CommitBatch() error {
111114 if store.batch == nil {
112- return errors.New("WalletStore commit fail, store batch is nil.")
115+ return errors.New("walletStore commit fail, store batch is nil")
113116 }
114117
115118 store.batch.Write()
@@ -347,6 +350,7 @@ func (store *WalletStore) ListAccountUTXOs(id string, isSmartContract bool) ([]*
347350 return confirmedUTXOs, nil
348351 }
349352
353+// ListTransactions list tx by filter args
350354 func (store *WalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
351355 annotatedTxs := []*query.AnnotatedTx{}
352356 var startKey []byte
--- a/math/algorithm.go
+++ b/math/algorithm.go
@@ -1,5 +1,6 @@
11 package math
22
3+// MinUint64 return the min of x and y
34 func MinUint64(x, y uint64) uint64 {
45 if x < y {
56 return x
--- a/netsync/chainmgr/block_keeper.go
+++ b/netsync/chainmgr/block_keeper.go
@@ -27,11 +27,7 @@ var (
2727 maxNumOfBlocksRegularSync = uint64(128)
2828 )
2929
30-type FastSync interface {
31- process() error
32- setSyncPeer(peer *peers.Peer)
33-}
34-
30+// Fetcher is the interface for fetch struct
3531 type Fetcher interface {
3632 processBlock(peerID string, block *types.Block)
3733 processBlocks(peerID string, blocks []*types.Block)
@@ -56,7 +52,7 @@ type headersMsg struct {
5652
5753 type blockKeeper struct {
5854 chain Chain
59- fastSync FastSync
55+ fastSync *fastSync
6056 msgFetcher Fetcher
6157 peers *peers.PeerSet
6258 syncPeer *peers.Peer
@@ -76,7 +72,7 @@ func newBlockKeeper(chain Chain, peers *peers.PeerSet, fastSyncDB dbm.DB) *block
7672 }
7773 }
7874
79-func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash) ([]*types.Block, error) {
75+func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash, isTimeout func() bool) ([]*types.Block, error) {
8076 headers, err := bk.locateHeaders(locator, stopHash, 0, maxNumOfBlocksPerMsg)
8177 if err != nil {
8278 return nil, err
@@ -91,6 +87,9 @@ func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash) ([]*t
9187 }
9288
9389 blocks = append(blocks, block)
90+ if isTimeout() {
91+ break
92+ }
9493 }
9594 return blocks, nil
9695 }
--- a/netsync/chainmgr/block_keeper_test.go
+++ b/netsync/chainmgr/block_keeper_test.go
@@ -448,7 +448,8 @@ func TestLocateBlocks(t *testing.T) {
448448 want = append(want, blocks[i])
449449 }
450450
451- got, err := bk.locateBlocks(locator, &c.stopHash)
451+ mockTimeout := func() bool { return false }
452+ got, err := bk.locateBlocks(locator, &c.stopHash, mockTimeout)
452453 if err != c.wantErr {
453454 t.Errorf("case %d: got %v want err = %v", i, err, c.wantErr)
454455 }
--- a/netsync/chainmgr/block_process.go
+++ b/netsync/chainmgr/block_process.go
@@ -12,17 +12,13 @@ import (
1212
1313 var errOrphanBlock = errors.New("fast sync inserting orphan block")
1414
15-type BlockProcessor interface {
16- process(chan struct{}, chan struct{}, uint64, *sync.WaitGroup)
17-}
18-
1915 type blockProcessor struct {
2016 chain Chain
21- storage Storage
17+ storage *storage
2218 peers *peers.PeerSet
2319 }
2420
25-func newBlockProcessor(chain Chain, storage Storage, peers *peers.PeerSet) *blockProcessor {
21+func newBlockProcessor(chain Chain, storage *storage, peers *peers.PeerSet) *blockProcessor {
2622 return &blockProcessor{
2723 chain: chain,
2824 peers: peers,
--- a/netsync/chainmgr/fast_sync.go
+++ b/netsync/chainmgr/fast_sync.go
@@ -29,12 +29,12 @@ var (
2929 type fastSync struct {
3030 chain Chain
3131 msgFetcher MsgFetcher
32- blockProcessor BlockProcessor
32+ blockProcessor *blockProcessor
3333 peers *peers.PeerSet
3434 mainSyncPeer *peers.Peer
3535 }
3636
37-func newFastSync(chain Chain, msgFetcher MsgFetcher, storage Storage, peers *peers.PeerSet) *fastSync {
37+func newFastSync(chain Chain, msgFetcher MsgFetcher, storage *storage, peers *peers.PeerSet) *fastSync {
3838 return &fastSync{
3939 chain: chain,
4040 msgFetcher: msgFetcher,
--- a/netsync/chainmgr/handle.go
+++ b/netsync/chainmgr/handle.go
@@ -3,6 +3,7 @@ package chainmgr
33 import (
44 "errors"
55 "reflect"
6+ "time"
67
78 log "github.com/sirupsen/logrus"
89
@@ -38,6 +39,7 @@ type Chain interface {
3839 ValidateTx(*types.Tx) (bool, error)
3940 }
4041
42+// Switch is the interface for network layer
4143 type Switch interface {
4244 AddReactor(name string, reactor p2p.Reactor) p2p.Reactor
4345 Start() (bool, error)
@@ -90,6 +92,7 @@ func NewManager(config *cfg.Config, sw Switch, chain Chain, mempool Mempool, dis
9092 return manager, nil
9193 }
9294
95+// AddPeer add the network layer peer to logic layer
9396 func (m *Manager) AddPeer(peer peers.BasePeer) {
9497 m.peers.AddPeer(peer)
9598 }
@@ -154,7 +157,12 @@ func (m *Manager) handleGetBlockMsg(peer *peers.Peer, msg *msgs.GetBlockMessage)
154157 }
155158
156159 func (m *Manager) handleGetBlocksMsg(peer *peers.Peer, msg *msgs.GetBlocksMessage) {
157- blocks, err := m.blockKeeper.locateBlocks(msg.GetBlockLocator(), msg.GetStopHash())
160+ endTime := time.Now().Add(requireBlocksTimeout / 2)
161+ isTimeout := func() bool {
162+ return time.Now().After(endTime)
163+ }
164+
165+ blocks, err := m.blockKeeper.locateBlocks(msg.GetBlockLocator(), msg.GetStopHash(), isTimeout)
158166 if err != nil || len(blocks) == 0 {
159167 return
160168 }
@@ -354,10 +362,12 @@ func (m *Manager) processMsg(basePeer peers.BasePeer, msgType byte, msg msgs.Blo
354362 }
355363 }
356364
365+// RemovePeer delete peer for peer set
357366 func (m *Manager) RemovePeer(peerID string) {
358367 m.peers.RemovePeer(peerID)
359368 }
360369
370+// SendStatus sent the current self status to remote peer
361371 func (m *Manager) SendStatus(peer peers.BasePeer) error {
362372 p := m.peers.GetPeer(peer.ID())
363373 if p == nil {
@@ -371,6 +381,7 @@ func (m *Manager) SendStatus(peer peers.BasePeer) error {
371381 return nil
372382 }
373383
384+// Start the network logic layer
374385 func (m *Manager) Start() error {
375386 var err error
376387 m.txMsgSub, err = m.eventDispatcher.Subscribe(core.TxMsgEvent{})
--- a/netsync/chainmgr/msg_fetcher.go
+++ b/netsync/chainmgr/msg_fetcher.go
@@ -24,7 +24,7 @@ const (
2424 var (
2525 requireBlockTimeout = 20 * time.Second
2626 requireHeadersTimeout = 30 * time.Second
27- requireBlocksTimeout = 50 * time.Second
27+ requireBlocksTimeout = 90 * time.Second
2828 checkSyncPeerNumInterval = 5 * time.Second
2929
3030 errRequestBlocksTimeout = errors.New("request blocks timeout")
@@ -33,6 +33,7 @@ var (
3333 errSendMsg = errors.New("send message error")
3434 )
3535
36+// MsgFetcher is the interface for msg fetch struct
3637 type MsgFetcher interface {
3738 resetParameter()
3839 addSyncPeer(peerID string)
@@ -51,7 +52,7 @@ type fetchBlocksResult struct {
5152 }
5253
5354 type msgFetcher struct {
54- storage Storage
55+ storage *storage
5556 syncPeers *fastSyncPeers
5657 peers *peers.PeerSet
5758 blockProcessCh chan *blockMsg
@@ -61,7 +62,7 @@ type msgFetcher struct {
6162 mux sync.RWMutex
6263 }
6364
64-func newMsgFetcher(storage Storage, peers *peers.PeerSet) *msgFetcher {
65+func newMsgFetcher(storage *storage, peers *peers.PeerSet) *msgFetcher {
6566 return &msgFetcher{
6667 storage: storage,
6768 syncPeers: newFastSyncPeers(),
--- a/netsync/chainmgr/storage.go
+++ b/netsync/chainmgr/storage.go
@@ -15,13 +15,7 @@ var (
1515 errDBFindBlock = errors.New("can't find block from DB")
1616 )
1717
18-type Storage interface {
19- resetParameter()
20- writeBlocks(peerID string, blocks []*types.Block) error
21- readBlock(height uint64) (*blockStorage, error)
22- deleteBlock(height uint64)
23-}
24-
18+// LocalStore is the interface for persistent storage
2519 type LocalStore interface {
2620 writeBlock(block *types.Block) error
2721 readBlock(height uint64) (*types.Block, error)
--- a/node/node.go
+++ b/node/node.go
@@ -5,6 +5,7 @@ import (
55 "errors"
66 "net"
77 "net/http"
8+ // debug tool
89 _ "net/http/pprof"
910 "path/filepath"
1011 "reflect"
@@ -84,7 +85,7 @@ func NewNode(config *cfg.Config) *Node {
8485 accessTokens := accesstoken.NewStore(tokenDB)
8586
8687 dispatcher := event.NewDispatcher()
87- movCore := mov.NewMovCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
88+ movCore := mov.NewCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
8889 assetFilter := protocol.NewAssetFilter(config.CrossChain.AssetWhitelist)
8990 txPool := protocol.NewTxPool(store, []protocol.DustFilterer{movCore, assetFilter}, dispatcher)
9091 chain, err := protocol.NewChain(store, txPool, []protocol.Protocoler{movCore}, dispatcher)
@@ -177,7 +178,7 @@ func Rollback(config *cfg.Config, targetHeight uint64) error {
177178 store := database.NewStore(coreDB)
178179
179180 dispatcher := event.NewDispatcher()
180- movCore := mov.NewMovCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
181+ movCore := mov.NewCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
181182 txPool := protocol.NewTxPool(store, []protocol.DustFilterer{movCore}, dispatcher)
182183 chain, err := protocol.NewChain(store, txPool, []protocol.Protocoler{movCore}, dispatcher)
183184 if err != nil {
@@ -235,7 +236,7 @@ func checkConfig(chain *protocol.Chain, config *cfg.Config) error {
235236 typedInput := genesisBlock.Transactions[0].Inputs[0].TypedInput
236237 if v, ok := typedInput.(*types.CoinbaseInput); ok {
237238 if !reflect.DeepEqual(fedpegScript, v.Arbitrary) {
238- return errors.New("config xpubs don't equal genesis block xpubs.")
239+ return errors.New("config xpubs don't equal genesis block xpubs")
239240 }
240241 }
241242 return nil
@@ -272,6 +273,7 @@ func (n *Node) initAndstartAPIServer() {
272273 n.api.StartServer(*listenAddr)
273274 }
274275
276+// OnStart implements BaseService
275277 func (n *Node) OnStart() error {
276278 if n.miningEnable {
277279 if _, err := n.wallet.AccountMgr.GetMiningAddress(); err != nil {
@@ -303,6 +305,7 @@ func (n *Node) OnStart() error {
303305 return nil
304306 }
305307
308+// OnStop implements BaseService
306309 func (n *Node) OnStop() {
307310 n.notificationMgr.Shutdown()
308311 n.notificationMgr.WaitForShutdown()
@@ -316,6 +319,7 @@ func (n *Node) OnStop() {
316319 n.eventDispatcher.Stop()
317320 }
318321
322+// RunForever listen to the stop signal
319323 func (n *Node) RunForever() {
320324 // Sleep forever and then...
321325 cmn.TrapSignal(func() {
--- a/proposal/proposal.go
+++ b/proposal/proposal.go
@@ -102,7 +102,7 @@ func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) e
102102 continue
103103 }
104104
105- results, gasLeft := preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
105+ results, gasLeft := b.preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
106106 for _, result := range results {
107107 if result.err != nil && !result.gasOnly {
108108 log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("mining block generation: skip tx due to")
@@ -139,11 +139,6 @@ func (b *blockBuilder) applyTransactionFromPool() error {
139139 }
140140
141141 func (b *blockBuilder) applyTransactionFromSubProtocol() error {
142- cp, err := b.accountManager.GetCoinbaseControlProgram()
143- if err != nil {
144- return err
145- }
146-
147142 isTimeout := func() bool {
148143 return b.getTimeoutStatus() > timeoutOk
149144 }
@@ -153,7 +148,7 @@ func (b *blockBuilder) applyTransactionFromSubProtocol() error {
153148 break
154149 }
155150
156- subTxs, err := p.BeforeProposalBlock(b.block.Transactions, cp, b.block.Height, b.gasLeft, isTimeout)
151+ subTxs, err := p.BeforeProposalBlock(b.block.Transactions, b.block.Height, b.gasLeft, isTimeout)
157152 if err != nil {
158153 log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
159154 continue
@@ -294,7 +289,7 @@ type validateTxResult struct {
294289 err error
295290 }
296291
297-func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
292+func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
298293 var results []*validateTxResult
299294 bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
300295 bcTxs := make([]*bc.Tx, len(txs))
@@ -328,7 +323,7 @@ func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoView
328323 continue
329324 }
330325
331- if err := validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
326+ if err := b.validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
332327 results = append(results, &validateTxResult{tx: txs[i], err: err})
333328 continue
334329 }
@@ -339,10 +334,10 @@ func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoView
339334 return results, gasLeft
340335 }
341336
342-func validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
337+func (b *blockBuilder) validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
343338 for _, subProtocol := range subProtocols {
344339 verifyResult := &bc.TxVerifyResult{StatusFail: statusFail}
345- if err := subProtocol.ValidateTx(tx, verifyResult); err != nil {
340+ if err := subProtocol.ValidateTx(tx, verifyResult, b.block.Height); err != nil {
346341 return err
347342 }
348343 }
--- a/protocol/asset_filter.go
+++ b/protocol/asset_filter.go
@@ -7,14 +7,15 @@ import (
77 "github.com/bytom/vapor/protocol/bc/types"
88 )
99
10-type assetFilter struct {
10+// AssetFilter is struct for allow open federation asset cross chain
11+type AssetFilter struct {
1112 whitelist map[string]struct{}
1213 }
1314
1415 // NewAssetFilter returns a assetFilter according a whitelist,
1516 // which is a strings list cancated via comma
16-func NewAssetFilter(whitelist string) *assetFilter {
17- af := &assetFilter{whitelist: make(map[string]struct{})}
17+func NewAssetFilter(whitelist string) *AssetFilter {
18+ af := &AssetFilter{whitelist: make(map[string]struct{})}
1819 af.whitelist[consensus.BTMAssetID.String()] = struct{}{}
1920 for _, assetID := range strings.Split(whitelist, ",") {
2021 af.whitelist[strings.ToLower(assetID)] = struct{}{}
@@ -25,7 +26,7 @@ func NewAssetFilter(whitelist string) *assetFilter {
2526 // IsDust implements the DustFilterer interface.
2627 // It filters a transaction as long as there is one asset neither BTM or in the whitelist
2728 // No need to check the output assets types becauese they must have been cover in input assets types
28-func (af *assetFilter) IsDust(tx *types.Tx) bool {
29+func (af *AssetFilter) IsDust(tx *types.Tx) bool {
2930 for _, input := range tx.Inputs {
3031 if _, ok := input.TypedInput.(*types.CrossChainInput); !ok {
3132 continue
--- a/protocol/bbft.go
+++ b/protocol/bbft.go
@@ -142,8 +142,13 @@ func (c *Chain) validateSign(block *types.Block) error {
142142
143143 if err := c.checkNodeSign(&block.BlockHeader, node, block.Get(node.Order)); err == errDoubleSignBlock {
144144 log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String(), "pubKey": pubKey}).Warn("the consensus node double sign the same height of different block")
145- block.BlockWitness.Delete(node.Order)
146- continue
145+ // if the blocker double sign & become the mainchain, that means
146+ // all the side chain will reject the main chain make the chain
147+ // fork. All the node will ban each other & can't roll back
148+ if blocker != pubKey {
149+ block.BlockWitness.Delete(node.Order)
150+ continue
151+ }
147152 } else if err != nil {
148153 return err
149154 }
@@ -228,23 +233,22 @@ func (c *Chain) signBlockHeader(blockHeader *types.BlockHeader) ([]byte, error)
228233 xpub := xprv.XPub()
229234 node, err := c.getConsensusNode(&blockHeader.PreviousBlockHash, xpub.String())
230235 blockHash := blockHeader.Hash()
231- blockHashStr := blockHash.String()
232236 if err == errNotFoundConsensusNode {
233- log.WithFields(log.Fields{"module": logModule, "blockHash": blockHashStr}).Warn("can't find consensus node of current node")
237+ log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String()}).Debug("can't find consensus node of current node")
234238 return nil, nil
235239 } else if err != nil {
236240 return nil, err
237241 }
238242
239- if err := c.checkDoubleSign(blockHeader, node.XPub.String()); err == errDoubleSignBlock {
240- log.WithFields(log.Fields{"module": logModule, "blockHash": blockHashStr}).Warn("current node has double sign the block")
243+ if len(blockHeader.Get(node.Order)) != 0 {
241244 return nil, nil
242- } else if err != nil {
243- return nil, err
244245 }
245246
246- if signature := blockHeader.Get(node.Order); len(signature) != 0 {
247+ if err := c.checkDoubleSign(blockHeader, node.XPub.String()); err == errDoubleSignBlock {
248+ log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String()}).Warn("current node has double sign the block")
247249 return nil, nil
250+ } else if err != nil {
251+ return nil, err
248252 }
249253
250254 signature := xprv.Sign(blockHeader.Hash().Bytes())
--- a/protocol/bc/types/block.go
+++ b/protocol/bc/types/block.go
@@ -102,6 +102,7 @@ func (b *Block) readFrom(r *blockchain.Reader) error {
102102 return nil
103103 }
104104
105+// WriteTo write block to io.Writer
105106 func (b *Block) WriteTo(w io.Writer) (int64, error) {
106107 ew := errors.NewWriter(w)
107108 if err := b.writeTo(ew, SerBlockFull); err != nil {
--- a/protocol/bc/types/block_witness.go
+++ b/protocol/bc/types/block_witness.go
@@ -6,6 +6,7 @@ import (
66 "github.com/bytom/vapor/encoding/blockchain"
77 )
88
9+// BlockWitness save the consensus node sign
910 type BlockWitness struct {
1011 // Witness is a vector of arguments for validating this block.
1112 Witness [][]byte
@@ -21,6 +22,7 @@ func (bw *BlockWitness) writeTo(w io.Writer) error {
2122 return err
2223 }
2324
25+// Set save data to index position
2426 func (bw *BlockWitness) Set(index uint64, data []byte) {
2527 if uint64(len(bw.Witness)) <= index {
2628 newWitness := make([][]byte, index+1, index+1)
@@ -30,12 +32,14 @@ func (bw *BlockWitness) Set(index uint64, data []byte) {
3032 bw.Witness[index] = data
3133 }
3234
35+// Delete remove data from index position
3336 func (bw *BlockWitness) Delete(index uint64) {
3437 if uint64(len(bw.Witness)) > index {
3538 bw.Witness[index] = nil
3639 }
3740 }
3841
42+// Get return data from index position
3943 func (bw *BlockWitness) Get(index uint64) []byte {
4044 if uint64(len(bw.Witness)) > index {
4145 return bw.Witness[index]
--- 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
2828 }
2929 }
3030
31+// OutputType implement the txout interface
3132 func (it *CrossChainOutput) OutputType() uint8 { return CrossChainOutputType }
--- 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
2828 }
2929 }
3030
31+// OutputType implement the txout interface
3132 func (it *IntraChainOutput) OutputType() uint8 { return IntraChainOutputType }
--- 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
3030 }
3131 }
3232
33+// OutputType implement the txout interface
3334 func (it *VoteOutput) OutputType() uint8 { return VoteOutputType }
--- a/protocol/block.go
+++ b/protocol/block.go
@@ -20,7 +20,7 @@ var (
2020
2121 // BlockExist check is a block in chain or orphan
2222 func (c *Chain) BlockExist(hash *bc.Hash) bool {
23- if bh, err := c.store.GetBlockHeader(hash); err == nil && bh.Height <= c.BestBlockHeight() {
23+ if _, err := c.store.GetBlockHeader(hash); err == nil {
2424 return true
2525 }
2626 return c.orphanManage.BlockExist(hash)
@@ -104,6 +104,7 @@ func (c *Chain) connectBlock(block *types.Block) (err error) {
104104 if err != nil {
105105 return err
106106 }
107+
107108 if err := consensusResult.ApplyBlock(block); err != nil {
108109 return err
109110 }
--- a/protocol/protocol.go
+++ b/protocol/protocol.go
@@ -19,14 +19,14 @@ const (
1919 maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
2020 )
2121
22+// Protocoler is interface for layer 2 consensus protocol
2223 type Protocoler interface {
2324 Name() string
2425 StartHeight() uint64
25- BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
26+ BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
2627 ChainStatus() (uint64, *bc.Hash, error)
2728 ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error
28- ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error
29- ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error
29+ ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error
3030 ApplyBlock(block *types.Block) error
3131 DetachBlock(block *types.Block) error
3232 }
@@ -174,6 +174,7 @@ func (c *Chain) InMainChain(hash bc.Hash) bool {
174174 return *blockHash == hash
175175 }
176176
177+// SubProtocols return list of layer 2 consensus protocol
177178 func (c *Chain) SubProtocols() []Protocoler {
178179 return c.subProtocols
179180 }
--- a/protocol/store.go
+++ b/protocol/store.go
@@ -9,6 +9,7 @@ import (
99 "github.com/bytom/vapor/protocol/state"
1010 )
1111
12+// predefine errors
1213 var (
1314 ErrNotFoundConsensusResult = errors.New("can't find the vote result by given sequence")
1415 )
--- a/protocol/tx.go
+++ b/protocol/tx.go
@@ -54,7 +54,7 @@ func (c *Chain) validateTx(tx *types.Tx, bh *types.BlockHeader) (bool, error) {
5454
5555 txVerifyResult := &bc.TxVerifyResult{StatusFail: err != nil}
5656 for _, p := range c.subProtocols {
57- if err := p.ValidateTx(tx, txVerifyResult); err != nil {
57+ if err := p.ValidateTx(tx, txVerifyResult, bh.Height); err != nil {
5858 c.txPool.AddErrCache(&tx.ID, err)
5959 return false, err
6060 }
--- a/protocol/txpool.go
+++ b/protocol/txpool.go
@@ -43,10 +43,12 @@ var (
4343 ErrDustTx = errors.New("transaction is dust tx")
4444 )
4545
46+// DustFilterer is inerface for dust transaction filter rule
4647 type DustFilterer interface {
4748 IsDust(tx *types.Tx) bool
4849 }
4950
51+// TxMsgEvent is message wrap for subscribe event
5052 type TxMsgEvent struct{ TxMsg *TxPoolMsg }
5153
5254 // TxDesc store tx and related info for mining strategy
--- a/protocol/validation/tx_test.go
+++ b/protocol/validation/tx_test.go
@@ -922,8 +922,10 @@ func TestMagneticContractTx(t *testing.T) {
922922 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
923923 },
924924 Outputs: []*types.TxOutput{
925- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
926- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
925+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
926+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
927+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
928+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
927929 },
928930 }),
929931 },
@@ -942,9 +944,11 @@ func TestMagneticContractTx(t *testing.T) {
942944 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
943945 },
944946 Outputs: []*types.TxOutput{
945- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
947+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
946948 types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, programSeller),
947- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
949+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
950+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
951+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
948952 },
949953 }),
950954 },
@@ -963,9 +967,11 @@ func TestMagneticContractTx(t *testing.T) {
963967 types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer),
964968 },
965969 Outputs: []*types.TxOutput{
966- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
967- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
970+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
971+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
968972 types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer),
973+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
974+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
969975 },
970976 }),
971977 },
@@ -1063,9 +1069,11 @@ func TestMagneticContractTx(t *testing.T) {
10631069 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
10641070 },
10651071 Outputs: []*types.TxOutput{
1066- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
1072+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
10671073 types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, []byte{0x55}),
1068- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
1074+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
1075+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
1076+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
10691077 },
10701078 }),
10711079 },
@@ -1139,9 +1147,12 @@ func TestRingMagneticContractTx(t *testing.T) {
11391147 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram),
11401148 },
11411149 Outputs: []*types.TxOutput{
1142- types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000000, aliceArgs.SellerProgram),
1143- types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000000, bobArgs.SellerProgram),
1144- types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000000, jackArgs.SellerProgram),
1150+ types.NewIntraChainOutput(aliceArgs.RequestedAsset, 199800000, aliceArgs.SellerProgram),
1151+ types.NewIntraChainOutput(bobArgs.RequestedAsset, 399600000, bobArgs.SellerProgram),
1152+ types.NewIntraChainOutput(jackArgs.RequestedAsset, 99900000, jackArgs.SellerProgram),
1153+ types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000, []byte{0x51}),
1154+ types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000, []byte{0x51}),
1155+ types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000, []byte{0x51}),
11451156 },
11461157 }),
11471158 },
@@ -1161,16 +1172,16 @@ func TestValidateOpenFederationIssueAsset(t *testing.T) {
11611172 tx := &types.Tx{TxData: types.TxData{Version: 1}}
11621173 tx.Inputs = append(tx.Inputs, types.NewCrossChainInput(nil,
11631174 testutil.MustDecodeHash("449143cb95389d19a1939879681168f78cc62614f4e0fb41f17b3232528a709d"),
1164- testutil.MustDecodeAsset("ed9d55880b4c88d8579e6c542b0e714eb8c05226ae84b6794a18aad2e1124f70"),
1175+ testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
11651176 100000000,
11661177 0,
11671178 1,
1168- testutil.MustDecodeHexString("7b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b0a202020202269737375655f61737365745f616374696f6e223a202263726f73735f636861696e220a20207d2c0a2020226e616d65223a2022454f53222c0a20202271756f72756d223a20312c0a20202272656973737565223a202274727565222c0a20202273796d626f6c223a2022454f53220a7d"),
1179+ testutil.MustDecodeHexString("7b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b0a202020202269737375655f61737365745f616374696f6e223a20226f70656e5f66656465726174696f6e5f63726f73735f636861696e220a20207d2c0a2020226e616d65223a2022454f53222c0a20202271756f72756d223a20312c0a20202272656973737565223a202274727565222c0a20202273796d626f6c223a2022454f53220a7d"),
11691180 testutil.MustDecodeHexString("ae20d827c281d47f5de93f98544b20468feaac046bf8b89bd51102f6e971f09d215920be43bb856fe337b37f5f09040c2b6cdbe23eaf5aa4770b16ea51fdfc45514c295152ad"),
11701181 ))
11711182
11721183 tx.Outputs = append(tx.Outputs, types.NewIntraChainOutput(
1173- testutil.MustDecodeAsset("ed9d55880b4c88d8579e6c542b0e714eb8c05226ae84b6794a18aad2e1124f70"),
1184+ testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
11741185 100000000,
11751186 testutil.MustDecodeHexString("0014d8dd58f374f58cffb1b1a7cc1e18a712b4fe67b5"),
11761187 ))
@@ -1193,7 +1204,7 @@ func TestValidateOpenFederationIssueAsset(t *testing.T) {
11931204 }
11941205 }
11951206
1196-func toByte64(str string) [64]byte{
1207+func toByte64(str string) [64]byte {
11971208 var result [64]byte
11981209 bytes := testutil.MustDecodeHexString(str)
11991210 for i := range bytes {
@@ -1201,6 +1212,7 @@ func toByte64(str string) [64]byte{
12011212 }
12021213 return result
12031214 }
1215+
12041216 // A txFixture is returned by sample (below) to produce a sample
12051217 // transaction, which takes a separate, optional _input_ txFixture to
12061218 // affect the transaction that's built. The components of the
--- a/protocol/vm/vmutil/script.go
+++ b/protocol/vm/vmutil/script.go
@@ -166,14 +166,16 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
166166 // standardProgram: Program,
167167 // sellerKey: PublicKey) locks valueAmount of valueAsset {
168168 // clause partialTrade(exchangeAmount: Amount) {
169-// define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
170-// verify actualAmount > 0 && actualAmount < valueAmount
171-// lock exchangeAmount of requestedAsset with sellerProgram
169+// define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
170+// verify actualAmount > 0 && actualAmount < valueAmount
171+// define receiveAmount: Integer = exchangeAmount * 999 / 1000
172+// lock receiveAmount of requestedAsset with sellerProgram
172173 // lock valueAmount-actualAmount of valueAsset with standardProgram
173174 // unlock actualAmount of valueAsset
174175 // }
175176 // clause fullTrade() {
176177 // define requestedAmount: Integer = valueAmount * ratioNumerator / ratioDenominator
178+// define requestedAmount: Integer = requestedAmount * 999 / 1000
177179 // verify requestedAmount > 0
178180 // lock requestedAmount of requestedAsset with sellerProgram
179181 // unlock valueAmount of valueAsset
@@ -219,12 +221,15 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
219221 // TOALTSTACK [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
220222 // 6 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> 6]
221223 // ROLL [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount]
222-// 3 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 3]
223-// ROLL [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset]
224-// 1 [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset 1]
225-// 5 [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset 1 5]
226-// ROLL [... sellerKey standardProgram actualAmount <position> exchangeAmount requestedAsset 1 sellerProgram]
227-// CHECKOUTPUT [... sellerKey standardProgram actualAmount checkOutput(exchangeAmount, requestedAsset, sellerProgram)]
224+// 999 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 999]
225+// 1000 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 1000]
226+// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount]
227+// 3 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount 3]
228+// ROLL [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset]
229+// 1 [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1]
230+// 5 [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1 5]
231+// ROLL [... sellerKey standardProgram actualAmount <position> receiveAmount requestedAsset 1 sellerProgram]
232+// CHECKOUTPUT [... sellerKey standardProgram actualAmount checkOutput(receiveAmount, requestedAsset, sellerProgram)]
228233 // VERIFY [... sellerKey standardProgram actualAmount]
229234 // FROMALTSTACK [... sellerKey standardProgram actualAmount <position>]
230235 // 1 [... sellerKey standardProgram actualAmount <position> 1]
@@ -246,6 +251,9 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
246251 // 3 [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioNumerator 3]
247252 // ROLL [... sellerKey standardProgram sellerProgram requestedAsset valueAmount ratioNumerator ratioDenominator]
248253 // MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
254+// 999 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999]
255+// 1000 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999 1000]
256+// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
249257 // DUP [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount]
250258 // 0 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount 0]
251259 // GREATERTHAN [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)]
@@ -315,6 +323,9 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
315323 builder.AddOp(vm.OP_TOALTSTACK)
316324 builder.AddOp(vm.OP_6)
317325 builder.AddOp(vm.OP_ROLL)
326+ builder.AddInt64(999)
327+ builder.AddInt64(1000)
328+ builder.AddOp(vm.OP_MULFRACTION)
318329 builder.AddOp(vm.OP_3)
319330 builder.AddOp(vm.OP_ROLL)
320331 builder.AddOp(vm.OP_1)
@@ -340,6 +351,9 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
340351 builder.AddOp(vm.OP_3)
341352 builder.AddOp(vm.OP_ROLL)
342353 builder.AddOp(vm.OP_MULFRACTION)
354+ builder.AddInt64(999)
355+ builder.AddInt64(1000)
356+ builder.AddOp(vm.OP_MULFRACTION)
343357 builder.AddOp(vm.OP_DUP)
344358 builder.AddOp(vm.OP_0)
345359 builder.AddOp(vm.OP_GREATERTHAN)
--- /dev/null
+++ b/test/rollback_test.go
@@ -0,0 +1,1571 @@
1+package test
2+
3+import (
4+ "os"
5+ "testing"
6+
7+ "github.com/bytom/vapor/application/mov"
8+ movDatabase "github.com/bytom/vapor/application/mov/database"
9+ "github.com/bytom/vapor/consensus"
10+ "github.com/bytom/vapor/database"
11+ dbm "github.com/bytom/vapor/database/leveldb"
12+ "github.com/bytom/vapor/database/storage"
13+ "github.com/bytom/vapor/protocol"
14+ "github.com/bytom/vapor/protocol/bc"
15+ "github.com/bytom/vapor/protocol/bc/types"
16+ "github.com/bytom/vapor/protocol/state"
17+ "github.com/bytom/vapor/testutil"
18+)
19+
20+type chainData struct {
21+ bestBlockHeader *types.BlockHeader
22+ lastIrrBlockHeader *types.BlockHeader
23+ utxoViewPoint *state.UtxoViewpoint
24+ storedBlocks []*types.Block
25+ consensusResults []*state.ConsensusResult
26+}
27+
28+func TestRollback(t *testing.T) {
29+ cases := []struct {
30+ desc string
31+ movStartHeight uint64
32+ RoundVoteBlockNums uint64
33+ beforeChainData *chainData
34+ wantChainData *chainData
35+ rollbackToTargetHeight uint64
36+ }{
37+ {
38+ desc: "rollback from height 1 to 0",
39+ movStartHeight: 10,
40+ RoundVoteBlockNums: 1200,
41+ rollbackToTargetHeight: 0,
42+ beforeChainData: &chainData{
43+ bestBlockHeader: &types.BlockHeader{
44+ Height: 1,
45+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
46+ },
47+ lastIrrBlockHeader: &types.BlockHeader{
48+ Height: 1,
49+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
50+ },
51+ utxoViewPoint: &state.UtxoViewpoint{
52+ Entries: map[bc.Hash]*storage.UtxoEntry{
53+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
54+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
55+ },
56+ },
57+ storedBlocks: []*types.Block{
58+ {
59+ BlockHeader: types.BlockHeader{
60+ Height: 0,
61+ },
62+ Transactions: []*types.Tx{
63+ types.NewTx(types.TxData{
64+ Inputs: []*types.TxInput{
65+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
66+ },
67+ Outputs: []*types.TxOutput{
68+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
69+ },
70+ }),
71+ },
72+ },
73+ {
74+ BlockHeader: types.BlockHeader{
75+ Height: 1,
76+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
77+ },
78+ Transactions: []*types.Tx{
79+ types.NewTx(types.TxData{
80+ Inputs: []*types.TxInput{
81+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
82+ },
83+ Outputs: []*types.TxOutput{
84+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
85+ },
86+ }),
87+ },
88+ },
89+ },
90+ consensusResults: []*state.ConsensusResult{
91+ {
92+ Seq: 1,
93+ NumOfVote: map[string]uint64{
94+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
95+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
96+ },
97+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
98+ BlockHeight: 1,
99+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + 10000000000},
100+ },
101+ {
102+ Seq: 0,
103+ NumOfVote: map[string]uint64{
104+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
105+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
106+ },
107+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
108+ BlockHeight: 0,
109+ CoinbaseReward: map[string]uint64{},
110+ },
111+ },
112+ },
113+ wantChainData: &chainData{
114+ bestBlockHeader: &types.BlockHeader{
115+ Height: 0,
116+ },
117+ lastIrrBlockHeader: &types.BlockHeader{
118+ Height: 0,
119+ },
120+ storedBlocks: []*types.Block{
121+ {
122+ BlockHeader: types.BlockHeader{
123+ Height: 0,
124+ },
125+ Transactions: []*types.Tx{
126+ types.NewTx(types.TxData{
127+ Inputs: []*types.TxInput{
128+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
129+ },
130+ Outputs: []*types.TxOutput{
131+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
132+ },
133+ }),
134+ },
135+ },
136+ },
137+ utxoViewPoint: &state.UtxoViewpoint{
138+ Entries: map[bc.Hash]*storage.UtxoEntry{
139+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
140+ },
141+ },
142+ consensusResults: []*state.ConsensusResult{
143+ {
144+ Seq: 0,
145+ NumOfVote: map[string]uint64{
146+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
147+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
148+ },
149+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
150+ BlockHeight: 0,
151+ CoinbaseReward: map[string]uint64{},
152+ },
153+ },
154+ },
155+ },
156+ {
157+ desc: "rollback from height 2 to 0",
158+ movStartHeight: 10,
159+ RoundVoteBlockNums: 1200,
160+ rollbackToTargetHeight: 0,
161+ beforeChainData: &chainData{
162+ bestBlockHeader: &types.BlockHeader{
163+ Height: 2,
164+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
165+ },
166+ lastIrrBlockHeader: &types.BlockHeader{
167+ Height: 2,
168+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
169+ },
170+ utxoViewPoint: &state.UtxoViewpoint{
171+ Entries: map[bc.Hash]*storage.UtxoEntry{
172+ testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
173+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
174+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
175+ },
176+ },
177+ storedBlocks: []*types.Block{
178+ {
179+ BlockHeader: types.BlockHeader{
180+ Height: 0,
181+ },
182+ Transactions: []*types.Tx{
183+ types.NewTx(types.TxData{
184+ Inputs: []*types.TxInput{
185+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
186+ },
187+ Outputs: []*types.TxOutput{
188+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
189+ },
190+ }),
191+ },
192+ },
193+ {
194+ BlockHeader: types.BlockHeader{
195+ Height: 1,
196+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
197+ },
198+ Transactions: []*types.Tx{
199+ types.NewTx(types.TxData{
200+ Inputs: []*types.TxInput{
201+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
202+ },
203+ Outputs: []*types.TxOutput{
204+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
205+ },
206+ }),
207+ },
208+ },
209+ {
210+ BlockHeader: types.BlockHeader{
211+ Height: 2,
212+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
213+ },
214+ Transactions: []*types.Tx{
215+ types.NewTx(types.TxData{
216+ Inputs: []*types.TxInput{
217+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
218+ },
219+ Outputs: []*types.TxOutput{
220+ types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
221+ },
222+ }),
223+ },
224+ },
225+ },
226+ consensusResults: []*state.ConsensusResult{
227+ {
228+ Seq: 1,
229+ NumOfVote: map[string]uint64{
230+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
231+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
232+ },
233+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
234+ BlockHeight: 2,
235+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(2) + 10000000000},
236+ },
237+ {
238+ Seq: 0,
239+ NumOfVote: map[string]uint64{
240+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
241+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
242+ },
243+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
244+ BlockHeight: 0,
245+ CoinbaseReward: map[string]uint64{},
246+ },
247+ },
248+ },
249+ wantChainData: &chainData{
250+ bestBlockHeader: &types.BlockHeader{
251+ Height: 0,
252+ },
253+ lastIrrBlockHeader: &types.BlockHeader{
254+ Height: 0,
255+ },
256+ storedBlocks: []*types.Block{
257+ {
258+ BlockHeader: types.BlockHeader{
259+ Height: 0,
260+ },
261+ Transactions: []*types.Tx{
262+ types.NewTx(types.TxData{
263+ Inputs: []*types.TxInput{
264+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
265+ },
266+ Outputs: []*types.TxOutput{
267+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
268+ },
269+ }),
270+ },
271+ },
272+ },
273+ utxoViewPoint: &state.UtxoViewpoint{
274+ Entries: map[bc.Hash]*storage.UtxoEntry{
275+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
276+ },
277+ },
278+ consensusResults: []*state.ConsensusResult{
279+ {
280+ Seq: 0,
281+ NumOfVote: map[string]uint64{
282+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
283+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
284+ },
285+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
286+ BlockHeight: 0,
287+ CoinbaseReward: map[string]uint64{},
288+ },
289+ },
290+ },
291+ },
292+ {
293+ desc: "rollback from height 2 to 1",
294+ movStartHeight: 10,
295+ RoundVoteBlockNums: 1200,
296+ rollbackToTargetHeight: 1,
297+ beforeChainData: &chainData{
298+ bestBlockHeader: &types.BlockHeader{
299+ Height: 2,
300+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
301+ },
302+ lastIrrBlockHeader: &types.BlockHeader{
303+ Height: 2,
304+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
305+ },
306+ utxoViewPoint: &state.UtxoViewpoint{
307+ Entries: map[bc.Hash]*storage.UtxoEntry{
308+ testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
309+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
310+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
311+ },
312+ },
313+ storedBlocks: []*types.Block{
314+ {
315+ BlockHeader: types.BlockHeader{
316+ Height: 0,
317+ },
318+ Transactions: []*types.Tx{
319+ types.NewTx(types.TxData{
320+ Inputs: []*types.TxInput{
321+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
322+ },
323+ Outputs: []*types.TxOutput{
324+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
325+ },
326+ }),
327+ },
328+ },
329+ {
330+ BlockHeader: types.BlockHeader{
331+ Height: 1,
332+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
333+ },
334+ Transactions: []*types.Tx{
335+ types.NewTx(types.TxData{
336+ Inputs: []*types.TxInput{
337+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
338+ },
339+ Outputs: []*types.TxOutput{
340+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
341+ },
342+ }),
343+ },
344+ },
345+ {
346+ BlockHeader: types.BlockHeader{
347+ Height: 2,
348+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
349+ },
350+ Transactions: []*types.Tx{
351+ types.NewTx(types.TxData{
352+ Inputs: []*types.TxInput{
353+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
354+ },
355+ Outputs: []*types.TxOutput{
356+ types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
357+ },
358+ }),
359+ },
360+ },
361+ },
362+ consensusResults: []*state.ConsensusResult{
363+ {
364+ Seq: 1,
365+ NumOfVote: map[string]uint64{
366+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
367+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
368+ },
369+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
370+ BlockHeight: 2,
371+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 500},
372+ },
373+ {
374+ Seq: 0,
375+ NumOfVote: map[string]uint64{
376+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
377+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
378+ },
379+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
380+ BlockHeight: 0,
381+ CoinbaseReward: map[string]uint64{},
382+ },
383+ },
384+ },
385+ wantChainData: &chainData{
386+ bestBlockHeader: &types.BlockHeader{
387+ Height: 1,
388+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
389+ },
390+ lastIrrBlockHeader: &types.BlockHeader{
391+ Height: 1,
392+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
393+ },
394+ storedBlocks: []*types.Block{
395+ {
396+ BlockHeader: types.BlockHeader{
397+ Height: 0,
398+ },
399+ Transactions: []*types.Tx{
400+ types.NewTx(types.TxData{
401+ Inputs: []*types.TxInput{
402+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
403+ },
404+ Outputs: []*types.TxOutput{
405+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
406+ },
407+ }),
408+ },
409+ },
410+ {
411+ BlockHeader: types.BlockHeader{
412+ Height: 1,
413+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
414+ },
415+ Transactions: []*types.Tx{
416+ types.NewTx(types.TxData{
417+ Inputs: []*types.TxInput{
418+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
419+ },
420+ Outputs: []*types.TxOutput{
421+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
422+ },
423+ }),
424+ },
425+ },
426+ },
427+ utxoViewPoint: &state.UtxoViewpoint{
428+ Entries: map[bc.Hash]*storage.UtxoEntry{
429+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
430+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
431+ },
432+ },
433+ consensusResults: []*state.ConsensusResult{
434+ {
435+ Seq: 1,
436+ NumOfVote: map[string]uint64{
437+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
438+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
439+ },
440+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
441+ BlockHeight: 1,
442+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1)},
443+ },
444+ {
445+ Seq: 0,
446+ NumOfVote: map[string]uint64{
447+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
448+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
449+ },
450+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
451+ BlockHeight: 0,
452+ CoinbaseReward: map[string]uint64{},
453+ },
454+ },
455+ },
456+ },
457+ {
458+ desc: "rollback from height 2 to 1, RoundVoteBlockNums is 2",
459+ movStartHeight: 10,
460+ RoundVoteBlockNums: 2,
461+ rollbackToTargetHeight: 1,
462+ beforeChainData: &chainData{
463+ bestBlockHeader: &types.BlockHeader{
464+ Height: 2,
465+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
466+ },
467+ lastIrrBlockHeader: &types.BlockHeader{
468+ Height: 2,
469+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
470+ },
471+ utxoViewPoint: &state.UtxoViewpoint{
472+ Entries: map[bc.Hash]*storage.UtxoEntry{
473+ testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
474+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
475+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
476+ },
477+ },
478+ storedBlocks: []*types.Block{
479+ {
480+ BlockHeader: types.BlockHeader{
481+ Height: 0,
482+ },
483+ Transactions: []*types.Tx{
484+ types.NewTx(types.TxData{
485+ Inputs: []*types.TxInput{
486+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
487+ },
488+ Outputs: []*types.TxOutput{
489+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
490+ },
491+ }),
492+ },
493+ },
494+ {
495+ BlockHeader: types.BlockHeader{
496+ Height: 1,
497+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
498+ },
499+ Transactions: []*types.Tx{
500+ types.NewTx(types.TxData{
501+ Inputs: []*types.TxInput{
502+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
503+ },
504+ Outputs: []*types.TxOutput{
505+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
506+ },
507+ }),
508+ },
509+ },
510+ {
511+ BlockHeader: types.BlockHeader{
512+ Height: 2,
513+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
514+ },
515+ Transactions: []*types.Tx{
516+ types.NewTx(types.TxData{
517+ Inputs: []*types.TxInput{
518+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
519+ },
520+ Outputs: []*types.TxOutput{
521+ types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
522+ },
523+ }),
524+ },
525+ },
526+ },
527+ consensusResults: []*state.ConsensusResult{
528+ {
529+ Seq: 1,
530+ NumOfVote: map[string]uint64{
531+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
532+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
533+ },
534+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
535+ BlockHeight: 2,
536+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 500},
537+ },
538+ {
539+ Seq: 0,
540+ NumOfVote: map[string]uint64{
541+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
542+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
543+ },
544+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
545+ BlockHeight: 0,
546+ CoinbaseReward: map[string]uint64{},
547+ },
548+ },
549+ },
550+ wantChainData: &chainData{
551+ bestBlockHeader: &types.BlockHeader{
552+ Height: 1,
553+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
554+ },
555+ lastIrrBlockHeader: &types.BlockHeader{
556+ Height: 1,
557+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
558+ },
559+ storedBlocks: []*types.Block{
560+ {
561+ BlockHeader: types.BlockHeader{
562+ Height: 0,
563+ },
564+ Transactions: []*types.Tx{
565+ types.NewTx(types.TxData{
566+ Inputs: []*types.TxInput{
567+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
568+ },
569+ Outputs: []*types.TxOutput{
570+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
571+ },
572+ }),
573+ },
574+ },
575+ {
576+ BlockHeader: types.BlockHeader{
577+ Height: 1,
578+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
579+ },
580+ Transactions: []*types.Tx{
581+ types.NewTx(types.TxData{
582+ Inputs: []*types.TxInput{
583+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
584+ },
585+ Outputs: []*types.TxOutput{
586+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
587+ },
588+ }),
589+ },
590+ },
591+ },
592+ utxoViewPoint: &state.UtxoViewpoint{
593+ Entries: map[bc.Hash]*storage.UtxoEntry{
594+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
595+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
596+ },
597+ },
598+ consensusResults: []*state.ConsensusResult{
599+ {
600+ Seq: 1,
601+ NumOfVote: map[string]uint64{
602+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
603+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
604+ },
605+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
606+ BlockHeight: 1,
607+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1)},
608+ },
609+ {
610+ Seq: 0,
611+ NumOfVote: map[string]uint64{
612+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
613+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
614+ },
615+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
616+ BlockHeight: 0,
617+ CoinbaseReward: map[string]uint64{},
618+ },
619+ },
620+ },
621+ },
622+ {
623+ desc: "rollback from height 3 to 1, RoundVoteBlockNums is 2",
624+ movStartHeight: 10,
625+ RoundVoteBlockNums: 2,
626+ rollbackToTargetHeight: 1,
627+ beforeChainData: &chainData{
628+ bestBlockHeader: &types.BlockHeader{
629+ Height: 3,
630+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
631+ },
632+ lastIrrBlockHeader: &types.BlockHeader{
633+ Height: 3,
634+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
635+ },
636+ utxoViewPoint: &state.UtxoViewpoint{
637+ Entries: map[bc.Hash]*storage.UtxoEntry{
638+ testutil.MustDecodeHash("5b4d53fbc2a489847f34dd0e0c085797fe7cf0a3a9a2f3231d11bdad16dea2be"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
639+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
640+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
641+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
642+ },
643+ },
644+ storedBlocks: []*types.Block{
645+ {
646+ BlockHeader: types.BlockHeader{
647+ Height: 0,
648+ },
649+ Transactions: []*types.Tx{
650+ types.NewTx(types.TxData{
651+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
652+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
653+ }),
654+ types.NewTx(types.TxData{
655+ Inputs: []*types.TxInput{
656+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
657+ },
658+ Outputs: []*types.TxOutput{
659+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
660+ },
661+ }),
662+ },
663+ },
664+ {
665+ BlockHeader: types.BlockHeader{
666+ Height: 1,
667+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
668+ },
669+ Transactions: []*types.Tx{
670+ types.NewTx(types.TxData{
671+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
672+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
673+ }),
674+ types.NewTx(types.TxData{
675+ Inputs: []*types.TxInput{
676+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
677+ },
678+ Outputs: []*types.TxOutput{
679+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
680+ },
681+ }),
682+ },
683+ },
684+ {
685+ BlockHeader: types.BlockHeader{
686+ Height: 2,
687+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
688+ },
689+ Transactions: []*types.Tx{
690+ types.NewTx(types.TxData{
691+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
692+ Outputs: []*types.TxOutput{
693+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
694+ },
695+ }),
696+ types.NewTx(types.TxData{
697+ Inputs: []*types.TxInput{
698+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
699+ },
700+ Outputs: []*types.TxOutput{
701+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
702+ },
703+ }),
704+ },
705+ },
706+ {
707+ BlockHeader: types.BlockHeader{
708+ Height: 3,
709+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
710+ },
711+ Transactions: []*types.Tx{
712+ types.NewTx(types.TxData{
713+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
714+ Outputs: []*types.TxOutput{
715+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
716+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
717+ },
718+ }),
719+ types.NewTx(types.TxData{
720+ Inputs: []*types.TxInput{
721+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400000000, 0, []byte{0, 1}),
722+ },
723+ Outputs: []*types.TxOutput{
724+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
725+ },
726+ }),
727+ },
728+ },
729+ },
730+ consensusResults: []*state.ConsensusResult{
731+ {
732+ Seq: 2,
733+ NumOfVote: map[string]uint64{
734+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 609998000,
735+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
736+ },
737+ BlockHash: testutil.MustDecodeHash("0c1cd1c0a6e6161f437c382cca21ce28921234ed7c4f252f7e4bbc9a523b74ac"),
738+ BlockHeight: 3,
739+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + 240000000},
740+ },
741+ {
742+ Seq: 1,
743+ NumOfVote: map[string]uint64{
744+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
745+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
746+ },
747+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
748+ BlockHeight: 2,
749+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
750+ },
751+ {
752+ Seq: 0,
753+ NumOfVote: map[string]uint64{
754+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
755+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
756+ },
757+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
758+ BlockHeight: 0,
759+ CoinbaseReward: map[string]uint64{},
760+ },
761+ },
762+ },
763+ wantChainData: &chainData{
764+ bestBlockHeader: &types.BlockHeader{
765+ Height: 1,
766+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
767+ },
768+ lastIrrBlockHeader: &types.BlockHeader{
769+ Height: 1,
770+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
771+ },
772+ storedBlocks: []*types.Block{
773+ {
774+ BlockHeader: types.BlockHeader{
775+ Height: 0,
776+ },
777+ Transactions: []*types.Tx{
778+ types.NewTx(types.TxData{
779+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
780+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
781+ }),
782+ types.NewTx(types.TxData{
783+ Inputs: []*types.TxInput{
784+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
785+ },
786+ Outputs: []*types.TxOutput{
787+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
788+ },
789+ }),
790+ },
791+ },
792+ {
793+ BlockHeader: types.BlockHeader{
794+ Height: 1,
795+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
796+ },
797+ Transactions: []*types.Tx{
798+ types.NewTx(types.TxData{
799+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
800+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
801+ }),
802+ types.NewTx(types.TxData{
803+ Inputs: []*types.TxInput{
804+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
805+ },
806+ Outputs: []*types.TxOutput{
807+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
808+ },
809+ }),
810+ },
811+ },
812+ },
813+ utxoViewPoint: &state.UtxoViewpoint{
814+ Entries: map[bc.Hash]*storage.UtxoEntry{
815+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
816+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
817+ },
818+ },
819+ consensusResults: []*state.ConsensusResult{
820+ {
821+ Seq: 1,
822+ NumOfVote: map[string]uint64{
823+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000,
824+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
825+ },
826+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
827+ BlockHeight: 1,
828+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + 2000},
829+ },
830+ {
831+ Seq: 0,
832+ NumOfVote: map[string]uint64{
833+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
834+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
835+ },
836+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
837+ BlockHeight: 0,
838+ CoinbaseReward: map[string]uint64{},
839+ },
840+ },
841+ },
842+ },
843+ {
844+ desc: "rollback from height 3 to 2, RoundVoteBlockNums is 2",
845+ movStartHeight: 10,
846+ RoundVoteBlockNums: 2,
847+ rollbackToTargetHeight: 2,
848+ beforeChainData: &chainData{
849+ bestBlockHeader: &types.BlockHeader{
850+ Height: 3,
851+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
852+ },
853+ lastIrrBlockHeader: &types.BlockHeader{
854+ Height: 3,
855+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
856+ },
857+ utxoViewPoint: &state.UtxoViewpoint{
858+ Entries: map[bc.Hash]*storage.UtxoEntry{
859+ testutil.MustDecodeHash("5b4d53fbc2a489847f34dd0e0c085797fe7cf0a3a9a2f3231d11bdad16dea2be"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
860+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
861+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
862+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
863+ },
864+ },
865+ storedBlocks: []*types.Block{
866+ {
867+ BlockHeader: types.BlockHeader{
868+ Height: 0,
869+ },
870+ Transactions: []*types.Tx{
871+ types.NewTx(types.TxData{
872+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
873+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
874+ }),
875+ types.NewTx(types.TxData{
876+ Inputs: []*types.TxInput{
877+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
878+ },
879+ Outputs: []*types.TxOutput{
880+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
881+ },
882+ }),
883+ },
884+ },
885+ {
886+ BlockHeader: types.BlockHeader{
887+ Height: 1,
888+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
889+ },
890+ Transactions: []*types.Tx{
891+ types.NewTx(types.TxData{
892+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
893+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
894+ }),
895+ types.NewTx(types.TxData{
896+ Inputs: []*types.TxInput{
897+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
898+ },
899+ Outputs: []*types.TxOutput{
900+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
901+ },
902+ }),
903+ },
904+ },
905+ {
906+ BlockHeader: types.BlockHeader{
907+ Height: 2,
908+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
909+ },
910+ Transactions: []*types.Tx{
911+ types.NewTx(types.TxData{
912+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
913+ Outputs: []*types.TxOutput{
914+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
915+ },
916+ }),
917+ types.NewTx(types.TxData{
918+ Inputs: []*types.TxInput{
919+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
920+ },
921+ Outputs: []*types.TxOutput{
922+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
923+ },
924+ }),
925+ },
926+ },
927+ {
928+ BlockHeader: types.BlockHeader{
929+ Height: 3,
930+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
931+ },
932+ Transactions: []*types.Tx{
933+ types.NewTx(types.TxData{
934+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
935+ Outputs: []*types.TxOutput{
936+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
937+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
938+ },
939+ }),
940+ types.NewTx(types.TxData{
941+ Inputs: []*types.TxInput{
942+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400000000, 0, []byte{0, 1}),
943+ },
944+ Outputs: []*types.TxOutput{
945+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
946+ },
947+ }),
948+ },
949+ },
950+ },
951+ consensusResults: []*state.ConsensusResult{
952+ {
953+ Seq: 2,
954+ NumOfVote: map[string]uint64{
955+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 609998000,
956+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
957+ },
958+ BlockHash: testutil.MustDecodeHash("0c1cd1c0a6e6161f437c382cca21ce28921234ed7c4f252f7e4bbc9a523b74ac"),
959+ BlockHeight: 3,
960+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + 240000000},
961+ },
962+ {
963+ Seq: 1,
964+ NumOfVote: map[string]uint64{
965+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
966+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
967+ },
968+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
969+ BlockHeight: 2,
970+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
971+ },
972+ {
973+ Seq: 0,
974+ NumOfVote: map[string]uint64{
975+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
976+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
977+ },
978+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
979+ BlockHeight: 0,
980+ CoinbaseReward: map[string]uint64{},
981+ },
982+ },
983+ },
984+ wantChainData: &chainData{
985+ bestBlockHeader: &types.BlockHeader{
986+ Height: 2,
987+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
988+ },
989+ lastIrrBlockHeader: &types.BlockHeader{
990+ Height: 2,
991+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
992+ },
993+ storedBlocks: []*types.Block{
994+ {
995+ BlockHeader: types.BlockHeader{
996+ Height: 0,
997+ },
998+ Transactions: []*types.Tx{
999+ types.NewTx(types.TxData{
1000+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1001+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
1002+ }),
1003+ types.NewTx(types.TxData{
1004+ Inputs: []*types.TxInput{
1005+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
1006+ },
1007+ Outputs: []*types.TxOutput{
1008+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
1009+ },
1010+ }),
1011+ },
1012+ },
1013+ {
1014+ BlockHeader: types.BlockHeader{
1015+ Height: 1,
1016+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
1017+ },
1018+ Transactions: []*types.Tx{
1019+ types.NewTx(types.TxData{
1020+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1021+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
1022+ }),
1023+ types.NewTx(types.TxData{
1024+ Inputs: []*types.TxInput{
1025+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
1026+ },
1027+ Outputs: []*types.TxOutput{
1028+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1029+ },
1030+ }),
1031+ },
1032+ },
1033+ {
1034+ BlockHeader: types.BlockHeader{
1035+ Height: 2,
1036+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
1037+ },
1038+ Transactions: []*types.Tx{
1039+ types.NewTx(types.TxData{
1040+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1041+ Outputs: []*types.TxOutput{
1042+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1043+ },
1044+ }),
1045+ types.NewTx(types.TxData{
1046+ Inputs: []*types.TxInput{
1047+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
1048+ },
1049+ Outputs: []*types.TxOutput{
1050+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1051+ },
1052+ }),
1053+ },
1054+ },
1055+ },
1056+ utxoViewPoint: &state.UtxoViewpoint{
1057+ Entries: map[bc.Hash]*storage.UtxoEntry{
1058+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
1059+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
1060+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
1061+ },
1062+ },
1063+ consensusResults: []*state.ConsensusResult{
1064+ {
1065+ Seq: 1,
1066+ NumOfVote: map[string]uint64{
1067+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000 + 250000000,
1068+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1069+ },
1070+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
1071+ BlockHeight: 2,
1072+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
1073+ },
1074+ {
1075+ Seq: 0,
1076+ NumOfVote: map[string]uint64{
1077+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
1078+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1079+ },
1080+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
1081+ BlockHeight: 0,
1082+ CoinbaseReward: map[string]uint64{},
1083+ },
1084+ },
1085+ },
1086+ },
1087+ {
1088+ desc: "rollback from height 4 to 2, there is two chain , and round vote block nums is 2",
1089+ movStartHeight: 10,
1090+ RoundVoteBlockNums: 2,
1091+ rollbackToTargetHeight: 2,
1092+ beforeChainData: &chainData{
1093+ bestBlockHeader: &types.BlockHeader{
1094+ Height: 5,
1095+ Timestamp: uint64(1528945008),
1096+ PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
1097+ },
1098+ lastIrrBlockHeader: &types.BlockHeader{
1099+ Height: 5,
1100+ Timestamp: uint64(1528945008),
1101+ PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
1102+ },
1103+ utxoViewPoint: &state.UtxoViewpoint{
1104+ Entries: map[bc.Hash]*storage.UtxoEntry{
1105+ testutil.MustDecodeHash("3c07f3159d4e2a0527129d644a8fcd09ce26555e94c9c7f348464120ef463275"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 5, Spent: true},
1106+ testutil.MustDecodeHash("927144d2a391e17dc12184f5ae163b994984132ad72c34d854bb9009b68cd4cc"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 4, Spent: true},
1107+ testutil.MustDecodeHash("fa43f4ca43bcb0e94d43b52c56d1740dea1329b59a44f6ee045d70446881c514"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
1108+ testutil.MustDecodeHash("f081ccd0c97ae34bc5580a0405d9b1ed0b0ed9e1410f1786b7112b348a412e3d"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 4, Spent: true},
1109+ testutil.MustDecodeHash("2704fa67c76e020b08ffa3f93a500acebcaf68b45ba43d8b3b08b68c5bb1eff1"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
1110+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
1111+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
1112+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
1113+ },
1114+ },
1115+ storedBlocks: []*types.Block{
1116+ {
1117+ BlockHeader: types.BlockHeader{
1118+ Height: 0,
1119+ },
1120+ Transactions: []*types.Tx{
1121+ types.NewTx(types.TxData{
1122+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1123+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
1124+ }),
1125+ types.NewTx(types.TxData{
1126+ Inputs: []*types.TxInput{
1127+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
1128+ },
1129+ Outputs: []*types.TxOutput{
1130+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
1131+ },
1132+ }),
1133+ },
1134+ },
1135+ {
1136+ BlockHeader: types.BlockHeader{
1137+ Height: 1,
1138+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
1139+ },
1140+ Transactions: []*types.Tx{
1141+ types.NewTx(types.TxData{
1142+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1143+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
1144+ }),
1145+ types.NewTx(types.TxData{
1146+ Inputs: []*types.TxInput{
1147+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
1148+ },
1149+ Outputs: []*types.TxOutput{
1150+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1151+ },
1152+ }),
1153+ },
1154+ },
1155+ {
1156+ BlockHeader: types.BlockHeader{
1157+ Height: 2,
1158+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
1159+ },
1160+ Transactions: []*types.Tx{
1161+ types.NewTx(types.TxData{
1162+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1163+ Outputs: []*types.TxOutput{
1164+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1165+ },
1166+ }),
1167+ types.NewTx(types.TxData{
1168+ Inputs: []*types.TxInput{
1169+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
1170+ },
1171+ Outputs: []*types.TxOutput{
1172+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1173+ },
1174+ }),
1175+ },
1176+ },
1177+ {
1178+ BlockHeader: types.BlockHeader{
1179+ Height: 3,
1180+ Timestamp: uint64(1528945000),
1181+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
1182+ },
1183+ Transactions: []*types.Tx{
1184+ types.NewTx(types.TxData{
1185+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1186+ Outputs: []*types.TxOutput{
1187+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1188+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
1189+ },
1190+ }),
1191+ types.NewTx(types.TxData{
1192+ Inputs: []*types.TxInput{
1193+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 440000000, 0, []byte{0, 1}),
1194+ },
1195+ Outputs: []*types.TxOutput{
1196+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1197+ },
1198+ }),
1199+ },
1200+ },
1201+ {
1202+ BlockHeader: types.BlockHeader{
1203+ Height: 4,
1204+ Timestamp: uint64(1528945005),
1205+ PreviousBlockHash: testutil.MustDecodeHash("bec3dd0d6fecb80a6f3a0373ec2ae676cc1ce72af83546f3d4672231c9b080e6"),
1206+ },
1207+ Transactions: []*types.Tx{
1208+ types.NewTx(types.TxData{
1209+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1210+ Outputs: []*types.TxOutput{
1211+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1212+ },
1213+ }),
1214+ types.NewTx(types.TxData{
1215+ Inputs: []*types.TxInput{
1216+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 500000000, 0, []byte{0, 1}),
1217+ },
1218+ Outputs: []*types.TxOutput{
1219+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1220+ },
1221+ }),
1222+ },
1223+ },
1224+ {
1225+ BlockHeader: types.BlockHeader{
1226+ Height: 3,
1227+ Timestamp: uint64(1528945001),
1228+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
1229+ },
1230+ Transactions: []*types.Tx{
1231+ types.NewTx(types.TxData{
1232+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1233+ Outputs: []*types.TxOutput{
1234+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1235+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
1236+ },
1237+ }),
1238+ types.NewTx(types.TxData{
1239+ Inputs: []*types.TxInput{
1240+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 402000000, 0, []byte{0, 1}),
1241+ },
1242+ Outputs: []*types.TxOutput{
1243+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1244+ },
1245+ }),
1246+ },
1247+ },
1248+ {
1249+ BlockHeader: types.BlockHeader{
1250+ Height: 4,
1251+ Timestamp: uint64(1528945006),
1252+ PreviousBlockHash: testutil.MustDecodeHash("1d2d01a97d1239de51b4e7d0fb522f71771d2d4f9a0a559154519859cc44a230"),
1253+ },
1254+ Transactions: []*types.Tx{
1255+ types.NewTx(types.TxData{
1256+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1257+ Outputs: []*types.TxOutput{
1258+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1259+ },
1260+ }),
1261+ types.NewTx(types.TxData{
1262+ Inputs: []*types.TxInput{
1263+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 410000000, 0, []byte{0, 1}),
1264+ },
1265+ Outputs: []*types.TxOutput{
1266+ types.NewVoteOutput(*consensus.BTMAssetID, 170000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1267+ },
1268+ }),
1269+ },
1270+ },
1271+ {
1272+ BlockHeader: types.BlockHeader{
1273+ Height: 5,
1274+ Timestamp: uint64(1528945008),
1275+ PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
1276+ },
1277+ Transactions: []*types.Tx{
1278+ types.NewTx(types.TxData{
1279+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1280+ Outputs: []*types.TxOutput{
1281+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1282+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(3)+consensus.BlockSubsidy(4)+520000000, []byte{0x51}),
1283+ },
1284+ }),
1285+ types.NewTx(types.TxData{
1286+ Inputs: []*types.TxInput{
1287+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400004000, 0, []byte{0, 1}),
1288+ },
1289+ Outputs: []*types.TxOutput{
1290+ types.NewVoteOutput(*consensus.BTMAssetID, 160004000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1291+ },
1292+ }),
1293+ },
1294+ },
1295+ },
1296+ consensusResults: []*state.ConsensusResult{
1297+ {
1298+ Seq: 3,
1299+ NumOfVote: map[string]uint64{
1300+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 980002000,
1301+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1302+ },
1303+ BlockHash: testutil.MustDecodeHash("075ce54f7d4c1b524474265219be52238beec98138f0c0a4d21f1a6b0047914a"),
1304+ BlockHeight: 5,
1305+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(5) + 240000000},
1306+ },
1307+ {
1308+ Seq: 2,
1309+ NumOfVote: map[string]uint64{
1310+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 819998000,
1311+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1312+ },
1313+ BlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
1314+ BlockHeight: 4,
1315+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + consensus.BlockSubsidy(4) + 442000000},
1316+ },
1317+ {
1318+ Seq: 1,
1319+ NumOfVote: map[string]uint64{
1320+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
1321+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1322+ },
1323+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
1324+ BlockHeight: 2,
1325+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
1326+ },
1327+ {
1328+ Seq: 0,
1329+ NumOfVote: map[string]uint64{
1330+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
1331+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1332+ },
1333+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
1334+ BlockHeight: 0,
1335+ CoinbaseReward: map[string]uint64{},
1336+ },
1337+ },
1338+ },
1339+ wantChainData: &chainData{
1340+ bestBlockHeader: &types.BlockHeader{
1341+ Height: 2,
1342+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
1343+ },
1344+ lastIrrBlockHeader: &types.BlockHeader{
1345+ Height: 2,
1346+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
1347+ },
1348+ storedBlocks: []*types.Block{
1349+ {
1350+ BlockHeader: types.BlockHeader{
1351+ Height: 0,
1352+ },
1353+ Transactions: []*types.Tx{
1354+ types.NewTx(types.TxData{
1355+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1356+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
1357+ }),
1358+ types.NewTx(types.TxData{
1359+ Inputs: []*types.TxInput{
1360+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
1361+ },
1362+ Outputs: []*types.TxOutput{
1363+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
1364+ },
1365+ }),
1366+ },
1367+ },
1368+ {
1369+ BlockHeader: types.BlockHeader{
1370+ Height: 1,
1371+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
1372+ },
1373+ Transactions: []*types.Tx{
1374+ types.NewTx(types.TxData{
1375+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1376+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
1377+ }),
1378+ types.NewTx(types.TxData{
1379+ Inputs: []*types.TxInput{
1380+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
1381+ },
1382+ Outputs: []*types.TxOutput{
1383+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1384+ },
1385+ }),
1386+ },
1387+ },
1388+ {
1389+ BlockHeader: types.BlockHeader{
1390+ Height: 2,
1391+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
1392+ },
1393+ Transactions: []*types.Tx{
1394+ types.NewTx(types.TxData{
1395+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
1396+ Outputs: []*types.TxOutput{
1397+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
1398+ },
1399+ }),
1400+ types.NewTx(types.TxData{
1401+ Inputs: []*types.TxInput{
1402+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
1403+ },
1404+ Outputs: []*types.TxOutput{
1405+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
1406+ },
1407+ }),
1408+ },
1409+ },
1410+ },
1411+ consensusResults: []*state.ConsensusResult{
1412+ {
1413+ Seq: 1,
1414+ NumOfVote: map[string]uint64{
1415+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000 + 250000000,
1416+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1417+ },
1418+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
1419+ BlockHeight: 2,
1420+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
1421+ },
1422+ {
1423+ Seq: 0,
1424+ NumOfVote: map[string]uint64{
1425+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
1426+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
1427+ },
1428+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
1429+ BlockHeight: 0,
1430+ CoinbaseReward: map[string]uint64{},
1431+ },
1432+ },
1433+ utxoViewPoint: &state.UtxoViewpoint{
1434+ Entries: map[bc.Hash]*storage.UtxoEntry{
1435+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
1436+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
1437+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
1438+ },
1439+ },
1440+ },
1441+ },
1442+ }
1443+
1444+ for i, c := range cases {
1445+ consensus.ActiveNetParams.RoundVoteBlockNums = c.RoundVoteBlockNums
1446+
1447+ movDB := dbm.NewDB("mov_db", "leveldb", "mov_db")
1448+ movCore := mov.NewCoreWithDB(movDatabase.NewLevelDBMovStore(movDB), c.movStartHeight)
1449+
1450+ blockDB := dbm.NewDB("block_db", "leveldb", "block_db")
1451+ store := database.NewStore(blockDB)
1452+
1453+ mustSaveBlocks(c.beforeChainData.storedBlocks, store)
1454+
1455+ var mainChainBlockHeaders []*types.BlockHeader
1456+ for _, block := range c.beforeChainData.storedBlocks {
1457+ mainChainBlockHeaders = append(mainChainBlockHeaders, &block.BlockHeader)
1458+ }
1459+ if err := store.SaveChainStatus(c.beforeChainData.bestBlockHeader, c.beforeChainData.lastIrrBlockHeader, mainChainBlockHeaders, c.beforeChainData.utxoViewPoint, c.beforeChainData.consensusResults); err != nil {
1460+ t.Fatal(err)
1461+ }
1462+
1463+ chain, err := protocol.NewChain(store, nil, []protocol.Protocoler{movCore}, nil)
1464+ if err != nil {
1465+ t.Fatal(err)
1466+ }
1467+
1468+ if err := chain.Rollback(c.rollbackToTargetHeight); err != nil {
1469+ t.Fatal(err)
1470+ }
1471+
1472+ if !testutil.DeepEqual(chain.LastIrreversibleHeader(), c.wantChainData.lastIrrBlockHeader) {
1473+ t.Errorf("lastIrrBlockHeader is not right!")
1474+ }
1475+
1476+ if !testutil.DeepEqual(chain.BestBlockHeader(), c.wantChainData.bestBlockHeader) {
1477+ t.Errorf("wantBestBlockHeader is not right!")
1478+ }
1479+
1480+ gotConsensusResults := mustGetConsensusResultFromStore(store, chain)
1481+ if !testutil.DeepEqual(gotConsensusResults, c.wantChainData.consensusResults) {
1482+ t.Errorf("cases#%d(%s) wantBestConsensusResult is not right!", i, c.desc)
1483+ }
1484+
1485+ gotBlocks := mustGetBlocksFromStore(chain)
1486+ if !blocksEquals(gotBlocks, c.wantChainData.storedBlocks) {
1487+ t.Errorf("cases#%d(%s) the blocks is not same!", i, c.desc)
1488+ }
1489+
1490+ gotTransactions := getBcTransactions(gotBlocks)
1491+ gotUtxoViewPoint := state.NewUtxoViewpoint()
1492+ if err = store.GetTransactionsUtxo(gotUtxoViewPoint, gotTransactions); err != nil {
1493+ t.Fatal(err)
1494+ }
1495+
1496+ if !testutil.DeepEqual(gotUtxoViewPoint, c.wantChainData.utxoViewPoint) {
1497+ t.Fatal(err)
1498+ }
1499+
1500+ blockDB.Close()
1501+ os.RemoveAll("block_db")
1502+ movDB.Close()
1503+ os.RemoveAll("mov_db")
1504+
1505+ }
1506+}
1507+
1508+func blocksEquals(blocks1 []*types.Block, blocks2 []*types.Block) bool {
1509+ blockHashMap1 := make(map[string]interface{})
1510+ for _, block := range blocks1 {
1511+ hash := block.Hash()
1512+ blockHashMap1[hash.String()] = nil
1513+ }
1514+
1515+ blockHashMap2 := make(map[string]interface{})
1516+ for _, block := range blocks2 {
1517+ hash := block.Hash()
1518+ blockHashMap2[hash.String()] = nil
1519+ }
1520+ return testutil.DeepEqual(blockHashMap1, blockHashMap2)
1521+}
1522+
1523+func getBcTransactions(blocks []*types.Block) []*bc.Tx {
1524+ var txs []*bc.Tx
1525+ for _, block := range blocks {
1526+ for _, tx := range block.Transactions {
1527+ txs = append(txs, tx.Tx)
1528+ }
1529+ }
1530+ return txs
1531+}
1532+
1533+func mustSaveBlocks(blocks []*types.Block, store *database.Store) {
1534+ for _, block := range blocks {
1535+ status := bc.NewTransactionStatus()
1536+ for index := range block.Transactions {
1537+ if err := status.SetStatus(index, false); err != nil {
1538+ panic(err)
1539+ }
1540+ }
1541+ if err := store.SaveBlock(block, status); err != nil {
1542+ panic(err)
1543+ }
1544+ }
1545+}
1546+
1547+func mustGetBlocksFromStore(chain *protocol.Chain) []*types.Block {
1548+ var blocks []*types.Block
1549+ for height := int64(chain.BestBlockHeight()); height >= 0; height-- {
1550+ block, err := chain.GetBlockByHeight(uint64(height))
1551+ if err != nil {
1552+ panic(err)
1553+ }
1554+
1555+ blocks = append(blocks, block)
1556+ }
1557+ return blocks
1558+}
1559+
1560+func mustGetConsensusResultFromStore(store *database.Store, chain *protocol.Chain) []*state.ConsensusResult {
1561+ var consensusResults []*state.ConsensusResult
1562+ for seq := int64(state.CalcVoteSeq(chain.BestBlockHeight())); seq >= 0; seq-- {
1563+ consensusResult, err := store.GetConsensusResult(uint64(seq))
1564+ if err != nil {
1565+ panic(err)
1566+ }
1567+
1568+ consensusResults = append(consensusResults, consensusResult)
1569+ }
1570+ return consensusResults
1571+}
--- a/version/version.go
+++ b/version/version.go
@@ -47,7 +47,7 @@ const (
4747
4848 var (
4949 // The full version string
50- Version = "1.0.4"
50+ Version = "1.1.0"
5151 // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse HEAD)"
5252 GitCommit string
5353 Status *UpdateStatus
旧リポジトリブラウザで表示