const maxFeeRate = 0.05
var (
- errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
- errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
- errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
- errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
- errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
- errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
- errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
- errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
- errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
- errNotMatchedOrder = errors.New("order in matched tx is not matched")
+ errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
+ errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
+ errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
+ errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
+ errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
+ errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
+ errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
+ errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
+ errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
+ errNotMatchedOrder = errors.New("order in matched tx is not matched")
+ errNotConfiguredRewardProgram = errors.New("reward program is not configured properly")
+ errRewardProgramIsWrong = errors.New("the reward program is not correct")
)
// MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
return nil, err
}
- rewardProgram, err := hex.DecodeString(consensus.ActiveNetParams.MovRewardProgram)
+ program, _ := getRewardProgram(blockHeight)
+ rewardProgram, err := hex.DecodeString(program)
if err != nil {
- return nil, err
+ return nil, errNotConfiguredRewardProgram
}
matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(maxFeeRate), rewardProgram)
// ValidateBlock no need to verify the block header, because the first module has been verified.
// just need to verify the transactions in the block.
func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
- return m.ValidateTxs(block.Transactions, verifyResults)
-}
-
-// ValidateTxs validate the trade transaction.
-func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
- for i, tx := range txs {
- if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
+ for i, tx := range block.Transactions {
+ if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
return err
}
}
}
// ValidateTx validate one transaction.
-func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
if common.IsMatchedTx(tx) {
- if err := validateMatchedTx(tx, verifyResult); err != nil {
+ if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil {
return err
}
} else if common.IsCancelOrderTx(tx) {
return nil
}
+// matchedTxFee is object to record the mov tx's fee information
+type matchedTxFee struct {
+ rewardProgram []byte
+ amount int64
+}
+
// calcFeeAmount return the amount of fee in the matching transaction
-func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]int64, error) {
- assetFeeMap := make(map[bc.AssetID]int64)
+func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
+ assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
dealProgMaps := make(map[string]bool)
for _, input := range matchedTx.Inputs {
- assetFeeMap[input.AssetID()] = int64(input.AssetAmount().Amount)
+ assetFeeMap[input.AssetID()] = &matchedTxFee{amount: int64(input.AssetAmount().Amount)}
contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
if err != nil {
return nil, err
for _, output := range matchedTx.Outputs {
assetAmount := output.AssetAmount()
if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
- assetFeeMap[*assetAmount.AssetId] -= int64(assetAmount.Amount)
- if assetFeeMap[*assetAmount.AssetId] <= 0 {
+ assetFeeMap[*assetAmount.AssetId].amount -= int64(assetAmount.Amount)
+ if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
delete(assetFeeMap, *assetAmount.AssetId)
}
+ } else {
+ assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
}
}
return assetFeeMap, nil
return nil
}
-func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
if verifyResult.StatusFail {
return errStatusFailMustFalse
}
return errAssetIDMustUniqueInMatchedTx
}
- return validateMatchedTxFeeAmount(tx)
+ return validateMatchedTxFee(tx, blockHeight)
}
-func validateMatchedTxFeeAmount(tx *types.Tx) error {
- orders, err := getDeleteOrdersFromTx(tx)
+func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
+ matchedTxFees, err := calcFeeAmount(tx)
if err != nil {
return err
}
- receivedAmount, priceDiff := match.CalcReceivedAmount(orders)
- feeAmounts, err := calcFeeAmount(tx)
+ for _, fee := range matchedTxFees {
+ if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
+ return err
+ }
+ }
+
+ orders, err := getDeleteOrdersFromTx(tx)
if err != nil {
return err
}
+ feeAmounts := make(map[bc.AssetID]int64)
+ for assetID, fee := range matchedTxFees {
+ feeAmounts[assetID] = fee.amount
+ }
+
+ receivedAmount, priceDiff := match.CalcReceivedAmount(orders)
feeStrategy := match.NewDefaultFeeStrategy(maxFeeRate)
return feeStrategy.Validate(receivedAmount, priceDiff, feeAmounts)
}
}
return addOrders, deleteOrders
}
+
+// getRewardProgram return the reward program by specified block height
+// if no reward program configured, then will return empty string
+// if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
+// the second return value represent whether to find exactly
+func getRewardProgram(height uint64) (string, bool) {
+ rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
+ if len(rewardPrograms) == 0 {
+ return "", false
+ }
+
+ var program string
+ for _, rewardProgram := range rewardPrograms {
+ program = rewardProgram.Program
+ if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
+ return program, true
+ }
+ }
+ return program, false
+}
+
+func validateRewardProgram(height uint64, program string) error {
+ rewardProgram, exact := getRewardProgram(height)
+ if exact && rewardProgram != program {
+ return errRewardProgramIsWrong
+ }
+ return nil
+}
}
func TestValidateBlock(t *testing.T) {
+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
+ {
+ BeginBlock: 0,
+ EndBlock: 100,
+ Program: hex.EncodeToString(mock.RewardProgram),
+ },
+ }
+
cases := []struct {
desc string
block *types.Block
types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
- types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+ types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
},
}),
},
},
Outputs: []*types.TxOutput{
types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
- types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+ types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
},
}),
},
// re-order
types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
// fee
- types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, mock.RewardProgram),
},
}),
},
desc string
tx types.TxData
maxFeeRate float64
- wantMatchedTxFee map[bc.AssetID]int64
+ wantMatchedTxFee map[bc.AssetID]*matchedTxFee
}{
{
desc: "fee less than max fee",
maxFeeRate: 0.05,
- wantMatchedTxFee: map[bc.AssetID]int64{mock.ETH: 10},
+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{mock.ETH: {amount: 10, rewardProgram: mock.RewardProgram}},
tx: mock.MatchedTxs[1].TxData,
},
{
desc: "fee refund in tx",
maxFeeRate: 0.05,
- wantMatchedTxFee: map[bc.AssetID]int64{mock.ETH: 25},
+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram}},
tx: mock.MatchedTxs[2].TxData,
},
{
desc: "fee is zero",
maxFeeRate: 0.05,
- wantMatchedTxFee: map[bc.AssetID]int64{},
+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{},
tx: mock.MatchedTxs[0].TxData,
},
}
}
func TestBeforeProposalBlock(t *testing.T) {
- consensus.ActiveNetParams.MovRewardProgram = hex.EncodeToString(mock.RewardProgram)
+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
+ {
+ BeginBlock: 0,
+ EndBlock: 100,
+ Program: hex.EncodeToString(mock.RewardProgram),
+ },
+ }
cases := []struct {
desc string
continue
}
- results, gasLeft := preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
+ results, gasLeft := b.preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
for _, result := range results {
if result.err != nil && !result.gasOnly {
log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("mining block generation: skip tx due to")
err error
}
-func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
+func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
var results []*validateTxResult
bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
bcTxs := make([]*bc.Tx, len(txs))
continue
}
- if err := validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
+ if err := b.validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
results = append(results, &validateTxResult{tx: txs[i], err: err})
continue
}
return results, gasLeft
}
-func validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
+func (b *blockBuilder) validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
for _, subProtocol := range subProtocols {
verifyResult := &bc.TxVerifyResult{StatusFail: statusFail}
- if err := subProtocol.ValidateTx(tx, verifyResult); err != nil {
+ if err := subProtocol.ValidateTx(tx, verifyResult, b.block.Height); err != nil {
return err
}
}