OSDN Git Service

add the comment (#439)
authorPaladz <yzhu101@uottawa.ca>
Tue, 12 Nov 2019 02:16:39 +0000 (10:16 +0800)
committerGitHub <noreply@github.com>
Tue, 12 Nov 2019 02:16:39 +0000 (10:16 +0800)
application/mov/mov_core.go
application/mov/mov_core_test.go

index 929d163..34496ec 100644 (file)
@@ -42,9 +42,77 @@ func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
        return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
 }
 
-// Name return the name of current module
-func (m *MovCore) Name() string {
-       return "MOV"
+// ApplyBlock parse pending order and cancel from the the transactions of block
+// and add pending order to the dex db, remove cancel order from dex db.
+func (m *MovCore) ApplyBlock(block *types.Block) error {
+       if block.Height < m.startBlockHeight {
+               return nil
+       }
+
+       if block.Height == m.startBlockHeight {
+               blockHash := block.Hash()
+               if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
+                       return err
+               }
+
+               return nil
+       }
+
+       if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
+               return err
+       }
+
+       addOrders, deleteOrders, err := applyTransactions(block.Transactions)
+       if err != nil {
+               return err
+       }
+
+       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+/*
+       @issue: I have two orders A and B, order A's seller program is order B and order B's seller program is order A.
+    Assume consensus node accept 0% fee and This two orders are the only two order of this trading pair, will this
+    become an infinite loop and DDoS attacks the whole network?
+*/
+// BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
+func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64) ([]*types.Tx, int64, error) {
+       if blockHeight <= m.startBlockHeight {
+               return nil, 0, nil
+       }
+
+       orderTable, err := buildOrderTable(m.movStore, txs)
+       if err != nil {
+               return nil, 0, err
+       }
+
+       matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
+       tradePairMap := make(map[string]bool)
+       tradePairIterator := database.NewTradePairIterator(m.movStore)
+
+       var packagedTxs []*types.Tx
+       for gasLeft > 0 && tradePairIterator.HasNext() {
+               tradePair := tradePairIterator.Next()
+               if tradePairMap[tradePair.Key()] {
+                       continue
+               }
+               tradePairMap[tradePair.Key()] = true
+               tradePairMap[tradePair.Reverse().Key()] = true
+
+               for gasLeft > 0 && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
+                       matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
+                       if err != nil {
+                               return nil, 0, err
+                       }
+
+                       gasUsed := calcMatchedTxGasUsed(matchedTx)
+                       if gasLeft-gasUsed >= 0 {
+                               packagedTxs = append(packagedTxs, matchedTx)
+                       }
+                       gasLeft -= gasUsed
+               }
+       }
+       return packagedTxs, gasLeft, nil
 }
 
 // ChainStatus return the current block height and block hash in dex core
@@ -57,6 +125,36 @@ func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
        return state.Height, state.Hash, nil
 }
 
+// DetachBlock parse pending order and cancel from the the transactions of block
+// and add cancel order to the dex db, remove pending order from dex db.
+func (m *MovCore) DetachBlock(block *types.Block) error {
+       if block.Height <= m.startBlockHeight {
+               return nil
+       }
+
+       deleteOrders, addOrders, err := applyTransactions(block.Transactions)
+       if err != nil {
+               return err
+       }
+
+       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+// IsDust block the transaction that are not generated by the match engine
+func (m *MovCore) IsDust(tx *types.Tx) bool {
+       for _, input := range tx.Inputs {
+               if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
+                       return true
+               }
+       }
+       return false
+}
+
+// Name return the name of current module
+func (m *MovCore) Name() string {
+       return "MOV"
+}
+
 // 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 {
@@ -73,6 +171,7 @@ func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResul
        return nil
 }
 
+// ValidateTxs validate one transaction.
 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
        if common.IsMatchedTx(tx) {
                if err := validateMatchedTx(tx, verifyResult); err != nil {
@@ -101,38 +200,6 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro
        return nil
 }
 
-func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
-       if verifyResult.StatusFail {
-               return errStatusFailMustFalse
-       }
-
-       fromAssetIDMap := make(map[string]bool)
-       toAssetIDMap := make(map[string]bool)
-       for i, input := range tx.Inputs {
-               if !segwit.IsP2WMCScript(input.ControlProgram()) {
-                       return errInputProgramMustP2WMCScript
-               }
-
-               if contract.IsCancelClauseSelector(input) {
-                       return errExistCancelOrderInMatchedTx
-               }
-
-               order, err := common.NewOrderFromInput(tx, i)
-               if err != nil {
-                       return err
-               }
-
-               fromAssetIDMap[order.FromAssetID.String()] = true
-               toAssetIDMap[order.ToAssetID.String()] = true
-       }
-
-       if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
-               return errAssetIDMustUniqueInMatchedTx
-       }
-
-       return validateMatchedTxFeeAmount(tx)
-}
-
 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
        if verifyResult.StatusFail {
                return errStatusFailMustFalse
@@ -150,20 +217,6 @@ func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error
        return nil
 }
 
-func validateMatchedTxFeeAmount(tx *types.Tx) error {
-       txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
-       if err != nil {
-               return err
-       }
-
-       for _, amount := range txFee {
-               if amount.FeeAmount > amount.MaxFeeAmount {
-                       return errAmountOfFeeGreaterThanMaximum
-               }
-       }
-       return nil
-}
-
 func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
        contractArgs, err := segwit.DecodeP2WMCProgram(program)
        if err != nil {
@@ -180,34 +233,55 @@ func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
        return nil
 }
 
-// ApplyBlock parse pending order and cancel from the the transactions of block
-// and add pending order to the dex db, remove cancel order from dex db.
-func (m *MovCore) ApplyBlock(block *types.Block) error {
-       if block.Height < m.startBlockHeight {
-               return nil
+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+       if verifyResult.StatusFail {
+               return errStatusFailMustFalse
        }
 
-       if block.Height == m.startBlockHeight {
-               blockHash := block.Hash()
-               if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
+       fromAssetIDMap := make(map[string]bool)
+       toAssetIDMap := make(map[string]bool)
+       for i, input := range tx.Inputs {
+               if !segwit.IsP2WMCScript(input.ControlProgram()) {
+                       return errInputProgramMustP2WMCScript
+               }
+
+               if contract.IsCancelClauseSelector(input) {
+                       return errExistCancelOrderInMatchedTx
+               }
+
+               order, err := common.NewOrderFromInput(tx, i)
+               if err != nil {
                        return err
                }
 
-               return nil
+               fromAssetIDMap[order.FromAssetID.String()] = true
+               toAssetIDMap[order.ToAssetID.String()] = true
        }
 
-       if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
-               return err
+       if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+               return errAssetIDMustUniqueInMatchedTx
        }
 
-       addOrders, deleteOrders, err := applyTransactions(block.Transactions)
+       return validateMatchedTxFeeAmount(tx)
+}
+
+func validateMatchedTxFeeAmount(tx *types.Tx) error {
+       txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
        if err != nil {
                return err
        }
 
-       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+       for _, amount := range txFee {
+               if amount.FeeAmount > amount.MaxFeeAmount {
+                       return errAmountOfFeeGreaterThanMaximum
+               }
+       }
+       return nil
 }
 
+/*
+       @issue: the match package didn't support circle yet
+*/
 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
        orderTable, err := buildOrderTable(m.movStore, txs)
        if err != nil {
@@ -258,97 +332,36 @@ func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
        return nil
 }
 
-func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
-       assetMap := make(map[bc.AssetID]bc.AssetID)
-       var firstTradePair *common.TradePair
-       for _, tx := range tx.Inputs {
-               contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
+func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
+       deleteOrderMap := make(map[string]*common.Order)
+       addOrderMap := make(map[string]*common.Order)
+       for _, tx := range txs {
+               addOrders, err := getAddOrdersFromTx(tx)
                if err != nil {
-                       return nil, err
-               }
-
-               assetMap[tx.AssetID()] = contractArgs.RequestedAsset
-               if firstTradePair == nil {
-                       firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
+                       return nil, nil, err
                }
-       }
 
-       tradePairs := []*common.TradePair{firstTradePair}
-       for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
-               nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
-               if !ok {
-                       return nil, errInvalidTradePairs
+               for _, order := range addOrders {
+                       addOrderMap[order.Key()] = order
                }
 
-               tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
-               tradePairs = append(tradePairs, tradePair)
-       }
-
-       if len(tradePairs) != len(tx.Inputs) {
-               return nil, errInvalidTradePairs
-       }
-       return tradePairs, nil
-}
-
-// DetachBlock parse pending order and cancel from the the transactions of block
-// and add cancel order to the dex db, remove pending order from dex db.
-func (m *MovCore) DetachBlock(block *types.Block) error {
-       if block.Height <= m.startBlockHeight {
-               return nil
-       }
-
-       deleteOrders, addOrders, err := applyTransactions(block.Transactions)
-       if err != nil {
-               return err
-       }
-
-       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
-}
-
-// BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
-func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64) ([]*types.Tx, int64, error) {
-       if blockHeight <= m.startBlockHeight {
-               return nil, 0, nil
-       }
-
-       orderTable, err := buildOrderTable(m.movStore, txs)
-       if err != nil {
-               return nil, 0, err
-       }
-
-       matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
-       tradePairMap := make(map[string]bool)
-       tradePairIterator := database.NewTradePairIterator(m.movStore)
-
-       var packagedTxs []*types.Tx
-       for gasLeft > 0 && tradePairIterator.HasNext() {
-               tradePair := tradePairIterator.Next()
-               if tradePairMap[tradePair.Key()] {
-                       continue
+               deleteOrders, err := getDeleteOrdersFromTx(tx)
+               if err != nil {
+                       return nil, nil, err
                }
-               tradePairMap[tradePair.Key()] = true
-               tradePairMap[tradePair.Reverse().Key()] = true
-
-               for gasLeft > 0 && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
-                       matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
-                       if err != nil {
-                               return nil, 0, err
-                       }
 
-                       gasUsed := calcMatchedTxGasUsed(matchedTx)
-                       if gasLeft - gasUsed >= 0 {
-                               packagedTxs = append(packagedTxs, matchedTx)
-                       }
-                       gasLeft -= gasUsed
+               for _, order := range deleteOrders {
+                       deleteOrderMap[order.Key()] = order
                }
        }
-       return packagedTxs, gasLeft, nil
-}
 
-func calcMatchedTxGasUsed(tx *types.Tx) int64 {
-       return int64(len(tx.Inputs)) * 150 + int64(tx.SerializedSize)
+       addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
+       return addOrders, deleteOrders, nil
 }
 
+/*
+       @issue: if consensus node packed match transaction first then packed regular tx, this function's logic may make a valid block invalid
+*/
 func buildOrderTable(store database.MovStore, txs []*types.Tx) (*match.OrderTable, error) {
        var nonMatchedTxs []*types.Tx
        for _, tx := range txs {
@@ -376,57 +389,8 @@ func buildOrderTable(store database.MovStore, txs []*types.Tx) (*match.OrderTabl
        return match.NewOrderTable(store, arrivalAddOrders, arrivalDelOrders), nil
 }
 
-// IsDust block the transaction that are not generated by the match engine 
-func (m *MovCore) IsDust(tx *types.Tx) bool {
-       for _, input := range tx.Inputs {
-               if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
-                       return true
-               }
-       }
-       return false
-}
-
-func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
-       deleteOrderMap := make(map[string]*common.Order)
-       addOrderMap := make(map[string]*common.Order)
-       for _, tx := range txs {
-               addOrders, err := getAddOrdersFromTx(tx)
-               if err != nil {
-                       return nil, nil, err
-               }
-
-               for _, order := range addOrders {
-                       addOrderMap[order.Key()] = order
-               }
-
-               deleteOrders, err := getDeleteOrdersFromTx(tx)
-               if err != nil {
-                       return nil, nil, err
-               }
-
-               for _, order := range deleteOrders {
-                       deleteOrderMap[order.Key()] = order
-               }
-       }
-
-       addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
-       return addOrders, deleteOrders, nil
-}
-
-func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
-       var deleteOrders, addOrders []*common.Order
-       for orderID, order := range addOrderMap {
-               if _, ok := deleteOrderMap[orderID]; ok {
-                       delete(deleteOrderMap, orderID)
-                       continue
-               }
-               addOrders = append(addOrders, order)
-       }
-
-       for _, order := range deleteOrderMap {
-               deleteOrders = append(deleteOrders, order)
-       }
-       return addOrders, deleteOrders
+func calcMatchedTxGasUsed(tx *types.Tx) int64 {
+       return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
 }
 
 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
@@ -462,3 +426,51 @@ func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
        }
        return orders, nil
 }
+
+func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+       assetMap := make(map[bc.AssetID]bc.AssetID)
+       var firstTradePair *common.TradePair
+       for _, tx := range tx.Inputs {
+               contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
+               if err != nil {
+                       return nil, err
+               }
+
+               assetMap[tx.AssetID()] = contractArgs.RequestedAsset
+               if firstTradePair == nil {
+                       firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
+               }
+       }
+
+       tradePairs := []*common.TradePair{firstTradePair}
+       for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
+               nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
+               if !ok {
+                       return nil, errInvalidTradePairs
+               }
+
+               tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
+               tradePairs = append(tradePairs, tradePair)
+       }
+
+       if len(tradePairs) != len(tx.Inputs) {
+               return nil, errInvalidTradePairs
+       }
+       return tradePairs, nil
+}
+
+func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
+       var deleteOrders, addOrders []*common.Order
+       for orderID, order := range addOrderMap {
+               if _, ok := deleteOrderMap[orderID]; ok {
+                       delete(deleteOrderMap, orderID)
+                       continue
+               }
+               addOrders = append(addOrders, order)
+       }
+
+       for _, order := range deleteOrderMap {
+               deleteOrders = append(deleteOrders, order)
+       }
+       return addOrders, deleteOrders
+}
index 64d10ce..8fb0be3 100644 (file)
@@ -16,6 +16,13 @@ import (
        "github.com/vapor/testutil"
 )
 
+/*
+       @addTest:BeforeProposalBlock: will gas affect generate tx? will be packed tx affect generate tx?
+       @addTest:TestApplyBlock: one block has two different trade pairs & different trade pair won't affect each order(attach & detach)
+       @addTest:TestApplyBlock: node packed maker tx and match transaction in random order(attach & detach)
+       @addTest:TestValidateBlock: one tx has trade input and cancel input mixed
+       @addTest:TestValidateBlock: regular match transaction's seller program is also a P2WMCProgram
+*/
 func TestApplyBlock(t *testing.T) {
        initBlockHeader := &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
        cases := []struct {
@@ -199,7 +206,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
-                       wantError: nil,
+                       wantError:     nil,
                },
                {
                        desc: "block only has matched tx",
@@ -211,7 +218,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
-                       wantError: nil,
+                       wantError:     nil,
                },
                {
                        desc: "block has maker tx and matched tx",
@@ -225,7 +232,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
-                       wantError: nil,
+                       wantError:     nil,
                },
                {
                        desc: "status fail of maker tx is true",
@@ -236,7 +243,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
-                       wantError: errStatusFailMustFalse,
+                       wantError:     errStatusFailMustFalse,
                },
                {
                        desc: "status fail of matched tx is true",
@@ -247,7 +254,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
-                       wantError: errStatusFailMustFalse,
+                       wantError:     errStatusFailMustFalse,
                },
                {
                        desc: "asset id in matched tx is not unique",
@@ -267,7 +274,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
-                       wantError: errAssetIDMustUniqueInMatchedTx,
+                       wantError:     errAssetIDMustUniqueInMatchedTx,
                },
                {
                        desc: "common input in the matched tx",
@@ -289,7 +296,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError: errInputProgramMustP2WMCScript,
+                       wantError:     errInputProgramMustP2WMCScript,
                },
                {
                        desc: "cancel order in the matched tx",
@@ -311,7 +318,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError: errExistCancelOrderInMatchedTx,
+                       wantError:     errExistCancelOrderInMatchedTx,
                },
                {
                        desc: "common input in the cancel order tx",
@@ -330,7 +337,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError: errInputProgramMustP2WMCScript,
+                       wantError:     errInputProgramMustP2WMCScript,
                },
                {
                        desc: "amount of fee greater than max fee amount",
@@ -353,7 +360,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError: errAmountOfFeeGreaterThanMaximum,
+                       wantError:     errAmountOfFeeGreaterThanMaximum,
                },
                {
                        desc: "ratio numerator is zero",
@@ -366,7 +373,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError: errRatioOfTradeLessThanZero,
+                       wantError:     errRatioOfTradeLessThanZero,
                },
                {
                        desc: "ratio denominator is zero",
@@ -379,7 +386,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError: errRatioOfTradeLessThanZero,
+                       wantError:     errRatioOfTradeLessThanZero,
                },
                {
                        desc: "ratio numerator product input amount is overflow",
@@ -392,7 +399,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError: errNumeratorOfRatioIsOverflow,
+                       wantError:     errNumeratorOfRatioIsOverflow,
                },
        }