Golang implemented sidechain for Bytom
リビジョン | 4826d3905a9091bbca84b970b5aaa311aa5807df (tree) |
---|---|
日時 | 2019-10-23 15:12:02 |
作者 | shenao78 <shenao.78@163....> |
コミッター | shenao78 |
opt code
@@ -1,12 +1,12 @@ | ||
1 | 1 | package match |
2 | 2 | |
3 | 3 | import ( |
4 | + "encoding/hex" | |
4 | 5 | "math" |
5 | 6 | "math/big" |
6 | 7 | |
7 | 8 | "github.com/vapor/application/mov/common" |
8 | 9 | "github.com/vapor/application/mov/database" |
9 | - "github.com/vapor/application/mov/util" | |
10 | 10 | "github.com/vapor/consensus/segwit" |
11 | 11 | "github.com/vapor/errors" |
12 | 12 | vprMath "github.com/vapor/math" |
@@ -16,7 +16,7 @@ import ( | ||
16 | 16 | "github.com/vapor/protocol/vm/vmutil" |
17 | 17 | ) |
18 | 18 | |
19 | -var maxFeeRate = 0.05 | |
19 | +const maxFeeRate = 0.05 | |
20 | 20 | |
21 | 21 | type Engine struct { |
22 | 22 | orderTable *OrderTable |
@@ -27,22 +27,46 @@ func NewEngine(movStore database.MovStore, nodeProgram []byte) *Engine { | ||
27 | 27 | return &Engine{orderTable: NewOrderTable(movStore), nodeProgram: nodeProgram} |
28 | 28 | } |
29 | 29 | |
30 | +func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool { | |
31 | + if err := validateTradePairs(tradePairs); err != nil { | |
32 | + return false | |
33 | + } | |
34 | + | |
35 | + orders := e.peekOrders(tradePairs) | |
36 | + if len(orders) == 0 { | |
37 | + return false | |
38 | + } | |
39 | + | |
40 | + var contractArgsList []*vmutil.MagneticContractArgs | |
41 | + for _, order := range orders { | |
42 | + contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram) | |
43 | + if err != nil { | |
44 | + return false | |
45 | + } | |
46 | + | |
47 | + contractArgsList = append(contractArgsList, contractArgs) | |
48 | + } | |
49 | + | |
50 | + for i, contractArgs := range contractArgsList { | |
51 | + oppositeContractArgs := contractArgsList[getOppositeIndex(len(contractArgsList), i)] | |
52 | + if canNotBeMatched(contractArgs, oppositeContractArgs) { | |
53 | + return false | |
54 | + } | |
55 | + } | |
56 | + return true | |
57 | +} | |
58 | + | |
30 | 59 | // NextMatchedTx return the next matchable transaction by the specified trade pairs |
31 | -// the size of trade pairs at least, and the sequence of trade pairs can form a loop | |
60 | +// the size of trade pairs at least 2, and the sequence of trade pairs can form a loop | |
32 | 61 | // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA] |
33 | -func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) { | |
62 | +func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) { | |
34 | 63 | if err := validateTradePairs(tradePairs); err != nil { |
35 | 64 | return nil, err |
36 | 65 | } |
37 | 66 | |
38 | - var orders []*common.Order | |
39 | - for _, tradePair := range tradePairs { | |
40 | - order := e.orderTable.PeekOrder(tradePair) | |
41 | - if order == nil { | |
42 | - return nil, nil | |
43 | - } | |
44 | - | |
45 | - orders = append(orders, order) | |
67 | + orders := e.peekOrders(tradePairs) | |
68 | + if len(orders) == 0 { | |
69 | + return nil, errors.New("no order for the specified trade pair in the order table") | |
46 | 70 | } |
47 | 71 | |
48 | 72 | tx, err := e.buildMatchTx(orders) |
@@ -50,25 +74,35 @@ func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, err | ||
50 | 74 | return nil, err |
51 | 75 | } |
52 | 76 | |
53 | - if tx == nil { | |
54 | - return nil, nil | |
55 | - } | |
56 | - | |
57 | 77 | for _, tradePair := range tradePairs { |
58 | 78 | e.orderTable.PopOrder(tradePair) |
59 | 79 | } |
80 | + | |
60 | 81 | if err := addPartialTradeOrder(tx, e.orderTable); err != nil { |
61 | 82 | return nil, err |
62 | 83 | } |
63 | 84 | return tx, nil |
64 | 85 | } |
65 | 86 | |
87 | +func (e *Engine) peekOrders(tradePairs []*common.TradePair) []*common.Order { | |
88 | + var orders []*common.Order | |
89 | + for _, tradePair := range tradePairs { | |
90 | + order := e.orderTable.PeekOrder(tradePair) | |
91 | + if order == nil { | |
92 | + return nil | |
93 | + } | |
94 | + | |
95 | + orders = append(orders, order) | |
96 | + } | |
97 | + return orders | |
98 | +} | |
99 | + | |
66 | 100 | func validateTradePairs(tradePairs []*common.TradePair) error { |
67 | 101 | if len(tradePairs) < 2 { |
68 | 102 | return errors.New("size of trade pairs at least 2") |
69 | 103 | } |
70 | 104 | |
71 | - for i, tradePair:= range tradePairs { | |
105 | + for i, tradePair := range tradePairs { | |
72 | 106 | oppositeTradePair := tradePairs[getOppositeIndex(len(tradePairs), i)] |
73 | 107 | if *tradePair.FromAssetID != *oppositeTradePair.ToAssetID || *tradePair.ToAssetID != *oppositeTradePair.FromAssetID { |
74 | 108 | return errors.New("specified trade pairs is invalid") |
@@ -77,34 +111,28 @@ func validateTradePairs(tradePairs []*common.TradePair) error { | ||
77 | 111 | return nil |
78 | 112 | } |
79 | 113 | |
114 | +func canNotBeMatched(contractArgs, oppositeContractArgs *vmutil.MagneticContractArgs) bool { | |
115 | + if contractArgs.RatioNumerator == 0 || oppositeContractArgs.RatioDenominator == 0 { | |
116 | + return false | |
117 | + } | |
118 | + | |
119 | + buyRate := big.NewFloat(0).Quo(big.NewFloat(0).SetInt64(contractArgs.RatioDenominator), big.NewFloat(0).SetInt64(contractArgs.RatioNumerator)) | |
120 | + sellRate := big.NewFloat(0).Quo(big.NewFloat(0).SetInt64(oppositeContractArgs.RatioNumerator), big.NewFloat(0).SetInt64(oppositeContractArgs.RatioDenominator)) | |
121 | + return buyRate.Cmp(sellRate) < 0 | |
122 | +} | |
123 | + | |
80 | 124 | func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { |
81 | 125 | txData := &types.TxData{Version: 1} |
82 | - var partialTradeStatus []bool | |
83 | - var receiveAmounts []uint64 | |
84 | - | |
85 | 126 | for i, order := range orders { |
86 | - contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram) | |
87 | - if err != nil { | |
88 | - return nil, err | |
89 | - } | |
127 | + input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram) | |
128 | + txData.Inputs = append(txData.Inputs, input) | |
90 | 129 | |
91 | 130 | oppositeOrder := orders[getOppositeIndex(len(orders), i)] |
92 | - oppositeContractArgs, err := segwit.DecodeP2WMCProgram(oppositeOrder.Utxo.ControlProgram) | |
93 | - if err != nil { | |
131 | + if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil { | |
94 | 132 | return nil, err |
95 | 133 | } |
96 | - | |
97 | - if canNotBeMatched(contractArgs, oppositeContractArgs) { | |
98 | - return nil, nil | |
99 | - } | |
100 | - | |
101 | - txData.Inputs = append(txData.Inputs, types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)) | |
102 | - isPartialTrade, receiveAmount := addMatchTxOutput(txData, order, contractArgs, oppositeOrder.Utxo.Amount) | |
103 | - partialTradeStatus = append(partialTradeStatus, isPartialTrade) | |
104 | - receiveAmounts = append(receiveAmounts, receiveAmount) | |
105 | 134 | } |
106 | 135 | |
107 | - setMatchTxArguments(txData, partialTradeStatus, receiveAmounts) | |
108 | 136 | if err := e.addMatchTxFeeOutput(txData); err != nil { |
109 | 137 | return nil, err |
110 | 138 | } |
@@ -119,28 +147,23 @@ func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { | ||
119 | 147 | return tx, nil |
120 | 148 | } |
121 | 149 | |
122 | -func canNotBeMatched(contractArgs, oppositeContractArgs *vmutil.MagneticContractArgs) bool { | |
123 | - if contractArgs.RatioNumerator == 0 || oppositeContractArgs.RatioDenominator == 0 { | |
124 | - return false | |
150 | +func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error { | |
151 | + contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram) | |
152 | + if err != nil { | |
153 | + return err | |
125 | 154 | } |
126 | 155 | |
127 | - buyRate := big.NewFloat(0).Quo(big.NewFloat(0).SetInt64(contractArgs.RatioDenominator), big.NewFloat(0).SetInt64(contractArgs.RatioNumerator)) | |
128 | - sellRate := big.NewFloat(0).Quo(big.NewFloat(0).SetInt64(oppositeContractArgs.RatioNumerator), big.NewFloat(0).SetInt64(oppositeContractArgs.RatioDenominator)) | |
129 | - return buyRate.Cmp(sellRate) < 0 | |
130 | -} | |
131 | - | |
132 | -// addMatchTxOutput return whether partial matched | |
133 | -func addMatchTxOutput(txData *types.TxData, order *common.Order, contractArgs *vmutil.MagneticContractArgs, oppositeAmount uint64) (bool, uint64) { | |
134 | 156 | requestAmount := calcRequestAmount(order.Utxo.Amount, contractArgs) |
135 | 157 | receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount) |
136 | 158 | shouldPayAmount := CalcShouldPayAmount(receiveAmount, contractArgs) |
159 | + isPartialTrade := order.Utxo.Amount > shouldPayAmount | |
137 | 160 | |
161 | + setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount) | |
138 | 162 | txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram)) |
139 | - if order.Utxo.Amount > shouldPayAmount { | |
163 | + if isPartialTrade { | |
140 | 164 | txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram)) |
141 | - return true, receiveAmount | |
142 | 165 | } |
143 | - return false, receiveAmount | |
166 | + return nil | |
144 | 167 | } |
145 | 168 | |
146 | 169 | func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error { |
@@ -180,19 +203,14 @@ func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error { | ||
180 | 203 | return nil |
181 | 204 | } |
182 | 205 | |
183 | -func setMatchTxArguments(txData *types.TxData, partialTradeStatus []bool, receiveAmounts []uint64) { | |
184 | - var position int64 | |
185 | - for i, isPartial := range partialTradeStatus { | |
186 | - var arguments [][]byte | |
187 | - if isPartial { | |
188 | - arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts[i])), vm.Int64Bytes(position), vm.Int64Bytes(0)} | |
189 | - position += 2 | |
190 | - } else { | |
191 | - arguments = [][]byte{vm.Int64Bytes(position), vm.Int64Bytes(1)} | |
192 | - position++ | |
193 | - } | |
194 | - txData.Inputs[i].SetArguments(arguments) | |
206 | +func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) { | |
207 | + var arguments [][]byte | |
208 | + if isPartialTrade { | |
209 | + arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(0)} | |
210 | + } else { | |
211 | + arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(1)} | |
195 | 212 | } |
213 | + txInput.SetArguments(arguments) | |
196 | 214 | } |
197 | 215 | |
198 | 216 | func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error { |
@@ -215,24 +233,12 @@ func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error { | ||
215 | 233 | |
216 | 234 | func getOppositeIndex(size int, selfIdx int) int { |
217 | 235 | oppositeIdx := selfIdx + 1 |
218 | - if selfIdx >= size - 1 { | |
236 | + if selfIdx >= size-1 { | |
219 | 237 | oppositeIdx = 0 |
220 | 238 | } |
221 | 239 | return oppositeIdx |
222 | 240 | } |
223 | 241 | |
224 | -func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
225 | - return uint64(int64(fromAmount) * contractArg.RatioNumerator / contractArg.RatioDenominator) | |
226 | -} | |
227 | - | |
228 | -func CalcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
229 | - return uint64(math.Ceil(float64(receiveAmount) * float64(contractArg.RatioDenominator) / float64(contractArg.RatioNumerator))) | |
230 | -} | |
231 | - | |
232 | -func CalcMaxFeeAmount(shouldPayAmount uint64) uint64 { | |
233 | - return uint64(math.Ceil(float64(shouldPayAmount) * maxFeeRate)) | |
234 | -} | |
235 | - | |
236 | 242 | type feeAmount struct { |
237 | 243 | maxFeeAmount uint64 |
238 | 244 | payableFeeAmount uint64 |
@@ -244,28 +250,30 @@ func CalcFeeFromMatchedTx(txData *types.TxData) (map[bc.AssetID]*feeAmount, erro | ||
244 | 250 | assetAmountMap[input.AssetID()] = &feeAmount{} |
245 | 251 | } |
246 | 252 | |
247 | - for _, input := range txData.Inputs { | |
248 | - assetAmountMap[input.AssetID()].payableFeeAmount += input.AssetAmount().Amount | |
249 | - outputPos, err := util.GetTradeReceivePosition(input) | |
250 | - if err != nil { | |
251 | - return nil, err | |
253 | + receiveOutputMap := make(map[string]*types.TxOutput) | |
254 | + for _, output := range txData.Outputs { | |
255 | + // minus the amount of the re-order | |
256 | + if segwit.IsP2WMCScript(output.ControlProgram()) { | |
257 | + assetAmountMap[*output.AssetAmount().AssetId].payableFeeAmount -= output.AssetAmount().Amount | |
258 | + } else { | |
259 | + receiveOutputMap[hex.EncodeToString(output.ControlProgram())] = output | |
252 | 260 | } |
261 | + } | |
253 | 262 | |
254 | - receiveOutput := txData.Outputs[outputPos] | |
255 | - assetAmountMap[*receiveOutput.AssetAmount().AssetId].payableFeeAmount -= receiveOutput.AssetAmount().Amount | |
263 | + for _, input := range txData.Inputs { | |
256 | 264 | contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram()) |
257 | 265 | if err != nil { |
258 | 266 | return nil, err |
259 | 267 | } |
260 | 268 | |
261 | - assetAmountMap[input.AssetID()].maxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveOutput.AssetAmount().Amount, contractArgs)) | |
262 | - } | |
263 | - | |
264 | - for _, output := range txData.Outputs { | |
265 | - // minus the amount of the re-order | |
266 | - if segwit.IsP2WMCScript(output.ControlProgram()) { | |
267 | - assetAmountMap[*output.AssetAmount().AssetId].payableFeeAmount -= output.AssetAmount().Amount | |
269 | + assetAmountMap[input.AssetID()].payableFeeAmount += input.AssetAmount().Amount | |
270 | + receiveOutput, ok := receiveOutputMap[hex.EncodeToString(contractArgs.SellerProgram)] | |
271 | + if !ok { | |
272 | + return nil, errors.New("the input of matched tx has no receive output") | |
268 | 273 | } |
274 | + | |
275 | + assetAmountMap[*receiveOutput.AssetAmount().AssetId].payableFeeAmount -= receiveOutput.AssetAmount().Amount | |
276 | + assetAmountMap[input.AssetID()].maxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveOutput.AssetAmount().Amount, contractArgs)) | |
269 | 277 | } |
270 | 278 | |
271 | 279 | for assetID, amount := range assetAmountMap { |
@@ -275,3 +283,15 @@ func CalcFeeFromMatchedTx(txData *types.TxData) (map[bc.AssetID]*feeAmount, erro | ||
275 | 283 | } |
276 | 284 | return assetAmountMap, nil |
277 | 285 | } |
286 | + | |
287 | +func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
288 | + return uint64(int64(fromAmount) * contractArg.RatioNumerator / contractArg.RatioDenominator) | |
289 | +} | |
290 | + | |
291 | +func CalcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
292 | + return uint64(math.Ceil(float64(receiveAmount) * float64(contractArg.RatioDenominator) / float64(contractArg.RatioNumerator))) | |
293 | +} | |
294 | + | |
295 | +func CalcMaxFeeAmount(shouldPayAmount uint64) uint64 { | |
296 | + return uint64(math.Ceil(float64(shouldPayAmount) * maxFeeRate)) | |
297 | +} |
@@ -180,14 +180,12 @@ func TestGenerateMatchedTxs(t *testing.T) { | ||
180 | 180 | movStore := &database.MockMovStore{OrderMap: c.storeOrderMap} |
181 | 181 | matchEngine := NewEngine(movStore, []byte{0x51}) |
182 | 182 | var gotMatchedTxs []*types.Tx |
183 | - for { | |
183 | + for matchEngine.HasMatchedTx(c.tradePair, c.tradePair.Reverse()) { | |
184 | 184 | matchedTx, err := matchEngine.NextMatchedTx(c.tradePair, c.tradePair.Reverse()) |
185 | 185 | if err != nil { |
186 | 186 | t.Fatal(err) |
187 | 187 | } |
188 | - if matchedTx == nil { | |
189 | - break | |
190 | - } | |
188 | + | |
191 | 189 | gotMatchedTxs = append(gotMatchedTxs, matchedTx) |
192 | 190 | } |
193 | 191 |
@@ -3,13 +3,22 @@ package util | ||
3 | 3 | import ( |
4 | 4 | "encoding/hex" |
5 | 5 | |
6 | - "github.com/vapor/errors" | |
7 | 6 | "github.com/vapor/protocol/bc/types" |
8 | 7 | "github.com/vapor/protocol/vm" |
9 | 8 | ) |
10 | 9 | |
10 | +const ( | |
11 | + sizeOfCancelClauseArgs = 3 | |
12 | + sizeOfPartialTradeClauseArgs = 3 | |
13 | + sizeOfFullTradeClauseArgs = 2 | |
14 | + | |
15 | + partialTradeClauseSelector int64 = iota | |
16 | + fullTradeClauseSelector | |
17 | + cancelClauseSelector | |
18 | +) | |
19 | + | |
11 | 20 | func IsCancelClauseSelector(input *types.TxInput) bool { |
12 | - return len(input.Arguments()) == 3 && hex.EncodeToString(input.Arguments()[2]) == hex.EncodeToString(vm.Int64Bytes(2)) | |
21 | + return len(input.Arguments()) == sizeOfCancelClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(cancelClauseSelector)) | |
13 | 22 | } |
14 | 23 | |
15 | 24 | func IsTradeClauseSelector(input *types.TxInput) bool { |
@@ -17,20 +26,9 @@ func IsTradeClauseSelector(input *types.TxInput) bool { | ||
17 | 26 | } |
18 | 27 | |
19 | 28 | func IsPartialTradeClauseSelector(input *types.TxInput) bool { |
20 | - return len(input.Arguments()) == 3 && hex.EncodeToString(input.Arguments()[2]) == hex.EncodeToString(vm.Int64Bytes(0)) | |
29 | + return len(input.Arguments()) == sizeOfPartialTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(partialTradeClauseSelector)) | |
21 | 30 | } |
22 | 31 | |
23 | 32 | func IsFullTradeClauseSelector(input *types.TxInput) bool { |
24 | - return len(input.Arguments()) == 2 && hex.EncodeToString(input.Arguments()[1]) == hex.EncodeToString(vm.Int64Bytes(1)) | |
25 | -} | |
26 | - | |
27 | -func GetTradeReceivePosition(input *types.TxInput) (int64, error) { | |
28 | - if IsPartialTradeClauseSelector(input) { | |
29 | - return vm.AsInt64(input.Arguments()[1]) | |
30 | - } | |
31 | - | |
32 | - if IsFullTradeClauseSelector(input) { | |
33 | - return vm.AsInt64(input.Arguments()[0]) | |
34 | - } | |
35 | - return 0, errors.New("non trade transaction input") | |
33 | + return len(input.Arguments()) == sizeOfFullTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(fullTradeClauseSelector)) | |
36 | 34 | } |