Example #1
0
func (c *Controller) EDelOrder() (string, error) {

	c.r.ParseForm()
	orderId := utils.StrToInt64(c.r.FormValue("order_id"))

	// возвращаем сумму ордера на кошелек + возращаем комиссию.
	order, err := utils.DB.OneRow("SELECT amount, sell_currency_id FROM e_orders WHERE id  =  ? AND user_id  =  ? AND del_time  =  0 AND empty_time  =  0", orderId, c.SessUserId).String()
	if err != nil {
		return "", utils.ErrInfo(err)
	}
	if len(order) == 0 {
		return "", utils.ErrInfo("order_id error")
	}
	sellCurrencyId := utils.StrToInt64(order["sell_currency_id"])
	amount := utils.StrToFloat64(order["amount"])

	amountAndCommission := utils.StrToFloat64(order["amount"]) / (1 - c.ECommission/100)
	// косиссия биржи
	commission := amountAndCommission - amount
	err = userLock(c.SessUserId)
	if err != nil {
		return "", err
	}

	// отмечаем, что ордер удален
	err = utils.DB.ExecSql("UPDATE e_orders SET del_time = ? WHERE id = ? AND user_id = ?", utils.Time(), orderId, c.SessUserId)
	if err != nil {
		return "", utils.ErrInfo(err)
	}

	// возвращаем остаток ордера на кошель
	userAmount := utils.EUserAmountAndProfit(c.SessUserId, sellCurrencyId)
	err = utils.DB.ExecSql("UPDATE e_wallets SET amount = ?, last_update = ? WHERE user_id = ? AND currency_id = ?", userAmount+amountAndCommission, utils.Time(), c.SessUserId, sellCurrencyId)
	if err != nil {
		return "", utils.ErrInfo(err)
	}

	// вычитаем комиссию биржи
	userAmount = utils.EUserAmountAndProfit(1, sellCurrencyId)
	err = utils.DB.ExecSql("UPDATE e_wallets SET amount = ?, last_update = ? WHERE user_id = 1 AND currency_id = ?", userAmount-commission, utils.Time(), sellCurrencyId)
	if err != nil {
		return "", utils.ErrInfo(err)
	}

	userUnlock(c.SessUserId)

	return ``, nil
}
Example #2
0
func (c *Controller) EWithdraw() (string, error) {

	if c.SessUserId == 0 {
		return "", errors.New(c.Lang["sign_up_please"])
	}

	c.r.ParseForm()
	currencyId := utils.StrToInt64(c.r.FormValue("currency_id"))
	if !utils.CheckInputData(c.r.FormValue("amount"), "amount") {
		return "", fmt.Errorf("incorrect amount")
	}
	method := c.r.FormValue("method")
	if !utils.CheckInputData(method, "method") {
		return "", fmt.Errorf("incorrect method")
	}
	account := c.r.FormValue("account")
	if !utils.CheckInputData(account, "account") {
		return "", fmt.Errorf("incorrect account")
	}
	amount := utils.StrToFloat64(c.r.FormValue("amount"))

	curTime := utils.Time()

	// нужно проверить, есть ли нужная сумма на счету юзера
	userAmount := utils.EUserAmountAndProfit(c.SessUserId, currencyId)
	if userAmount < amount {
		return "", fmt.Errorf("%s (%f<%f)", c.Lang["not_enough_money"], userAmount, amount)
	}
	if method != "Dcoin" && currencyId < 1000 {
		return "", fmt.Errorf("incorrect method")
	}

	err := userLock(c.SessUserId)
	if err != nil {
		return "", utils.ErrInfo(err)
	}
	err = c.ExecSql(`UPDATE e_wallets SET amount = ?, last_update = ? WHERE user_id = ? AND currency_id = ?`, userAmount-amount, curTime, c.SessUserId, currencyId)
	if err != nil {
		return "", utils.ErrInfo(err)
	}
	var commission float64
	if method == "Dcoin" {
		commission = utils.StrToFloat64(c.EConfig["dc_commission"])
	} else if method == "Perfect-money" {
		commission = utils.StrToFloat64(c.EConfig["pm_commission"])
	}
	wdAmount := utils.ClearNull(utils.Float64ToStr(amount*(1-commission/100)), 2)

	err = c.ExecSql(`INSERT INTO e_withdraw (open_time, user_id, currency_id, account, amount, wd_amount, method) VALUES (?, ?, ?, ?, ?, ?, ?)`, curTime, c.SessUserId, currencyId, account, amount, wdAmount, method)
	if err != nil {
		return "", utils.ErrInfo(err)
	}
	userUnlock(c.SessUserId)

	return utils.JsonAnswer(c.Lang["request_is_created"], "success").String(), nil
}
Example #3
0
func (c *Controller) ESaveOrder() (string, error) {

	if c.SessUserId == 0 {
		return "", errors.New(c.Lang["sign_up_please"])
	}
	c.r.ParseForm()
	sellCurrencyId := utils.StrToInt64(c.r.FormValue("sell_currency_id"))
	buyCurrencyId := utils.StrToInt64(c.r.FormValue("buy_currency_id"))
	amount := utils.StrToFloat64(c.r.FormValue("amount"))
	sellRate := utils.StrToFloat64(c.r.FormValue("sell_rate"))
	orderType := c.r.FormValue("type")
	// можно ли торговать такими валютами
	checkCurrency, err := c.Single("SELECT count(id) FROM e_currency WHERE id IN (?, ?)", sellCurrencyId, buyCurrencyId).Int64()
	if err != nil {
		return "", utils.ErrInfo(err)
	}
	if checkCurrency != 2 {
		return "", errors.New("Currency error")
	}
	if orderType != "sell" && orderType != "buy" {
		return "", errors.New("Type error")
	}
	if amount == 0 {
		return "", errors.New(c.Lang["amount_error"])
	}
	if amount < 0.001 && sellCurrencyId < 1000 {
		return "", errors.New(strings.Replace(c.Lang["save_order_min_amount"], "[amount]", "0.001", -1))
	}
	if sellRate < 0.0001 {
		return "", errors.New(strings.Replace(c.Lang["save_order_min_price"], "[price]", "0.0001", -1))
	}
	reductionLock, err := utils.EGetReductionLock()
	if err != nil {
		return "", utils.ErrInfo(err)
	}
	if reductionLock > 0 {
		return "", errors.New(strings.Replace(c.Lang["creating_orders_unavailable"], "[minutes]", "30", -1))
	}

	// нужно проверить, есть ли нужная сумма на счету юзера
	userAmountAndProfit := utils.EUserAmountAndProfit(c.SessUserId, sellCurrencyId)
	if userAmountAndProfit < amount {
		return "", errors.New(c.Lang["not_enough_money"] + " (" + utils.Float64ToStr(userAmountAndProfit) + "<" + utils.Float64ToStr(amount) + ")" + strings.Replace(c.Lang["add_funds_link"], "[currency]", "USD", -1))
	}

	err = NewForexOrder(c.SessUserId, amount, sellRate, sellCurrencyId, buyCurrencyId, orderType, utils.StrToMoney(c.EConfig["commission"]))
	if err != nil {
		return "", utils.ErrInfo(err)
	} else {
		return utils.JsonAnswer(c.Lang["order_created"], "success").String(), nil
	}

	return ``, nil
}
Example #4
0
func Exchange(chBreaker chan bool, chAnswer chan string) {

	defer func() {
		if r := recover(); r != nil {
			log.Error("daemon Recovered", r)
			panic(r)
		}
	}()

	const GoroutineName = "Exchange"
	d := new(daemon)
	d.DCDB = DbConnect(chBreaker, chAnswer, GoroutineName)
	if d.DCDB == nil {
		return
	}
	d.goRoutineName = GoroutineName
	d.chAnswer = chAnswer
	d.chBreaker = chBreaker
	if utils.Mobile() {
		d.sleepTime = 3600
	} else {
		d.sleepTime = 60
	}
	if !d.CheckInstall(chBreaker, chAnswer, GoroutineName) {
		return
	}
	d.DCDB = DbConnect(chBreaker, chAnswer, GoroutineName)
	if d.DCDB == nil {
		return
	}

BEGIN:
	for {
		log.Info(GoroutineName)
		MonitorDaemonCh <- []string{GoroutineName, utils.Int64ToStr(utils.Time())}

		// проверим, не нужно ли нам выйти из цикла
		if CheckDaemonsRestart(chBreaker, chAnswer, GoroutineName) {
			break BEGIN
		}

		blockId, err := d.GetConfirmedBlockId()
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}

		var myPrefix string
		community, err := d.GetCommunityUsers()
		if len(community) > 0 {
			adminUserId, err := d.GetPoolAdminUserId()
			if err != nil {
				if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
					break BEGIN
				}
				continue BEGIN
			}
			myPrefix = utils.Int64ToStr(adminUserId) + "_"
		} else {
			myPrefix = ""
		}

		eConfig, err := d.GetMap(`SELECT * FROM e_config`, "name", "value")
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}
		confirmations := utils.StrToInt64(eConfig["confirmations"])
		mainDcAccount := utils.StrToInt64(eConfig["main_dc_account"])

		// все валюты, с которыми работаем
		currencyList, err := utils.EGetCurrencyList()
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}

		// ++++++++++++ reduction ++++++++++++

		// максимальный номер блока для процентов. Чтобы брать только новые
		maxReductionBlock, err := d.Single(`SELECT max(block_id) FROM e_reduction`).Int64()
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}

		// если есть свежая reduction, то нужно остановить торги
		reduction, err := d.Single(`SELECT block_id FROM reduction WHERE block_id > ? and pct > 0`, maxReductionBlock).Int64()
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}
		if reduction > 0 {
			err = d.ExecSql(`INSERT INTO e_reduction_lock (time) VALUES (?)`, utils.Time())
			if err != nil {
				log.Error("%v", utils.ErrInfo(err))
			}
		}

		// если уже прошло 10 блоков с момента обнаружения reduction, то производим сокращение объема монет
		rows, err := d.Query(d.FormatQuery(`SELECT pct, currency_id, time, block_id FROM reduction WHERE block_id > ? AND
	block_id < ?`), maxReductionBlock, blockId-confirmations)
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}
		for rows.Next() {
			var pct float64
			var currencyId, rTime, blockId int64
			err = rows.Scan(&pct, &currencyId, &rTime, &blockId)
			if err != nil {
				rows.Close()
				if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
					break BEGIN
				}
				continue BEGIN
			}
			//	$k = (100-$row['pct'])/100;
			k := (100 - pct) / 100
			// уменьшаем все средства на счетах
			err = d.ExecSql(`UPDATE e_wallets SET amount = amount * ? WHERE currency_id = ?`, k, currencyId)

			// уменьшаем все средства на вывод
			err = d.ExecSql(`UPDATE e_withdraw SET amount = amount * ? WHERE currency_id = ? AND close_time = 0`, k, currencyId)

			// уменьшаем все ордеры на продажу
			err = d.ExecSql(`UPDATE e_orders SET amount = amount * ?, begin_amount = begin_amount * ? WHERE sell_currency_id = ?`, k, k, currencyId)

			err = d.ExecSql(`INSERT INTO e_reduction (
					time,
					block_id,
					currency_id,
					pct
				)
				VALUES (
					?,
					?,
					?,
					?
				)`, rTime, blockId, currencyId, pct)
			if err != nil {
				rows.Close()
				if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
					break BEGIN
				}
				continue BEGIN
			}
		}
		rows.Close()

		// ++++++++++++ DC ++++++++++++
		/*
		 * Важно! отключать в кроне при обнулении данных в БД
		 *
		 * 1. Получаем инфу о входящих переводах и начисляем их на счета юзеров
		 * 2. Обновляем проценты
		 * 3. Чистим кол-во отправленных смс-ок
		 * */
		nodePrivateKey, err := d.GetNodePrivateKey(myPrefix)
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}
		/*
		 * Получаем инфу о входящих переводах и начисляем их на счета юзеров
		 * */

		// если всё остановлено из-за найденного блока с reduction, то входящие переводы не обрабатываем
		reductionLock, err := utils.EGetReductionLock()
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}
		if reductionLock > 0 {
			if d.dPrintSleep(utils.ErrInfo("reductionLock"), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}

		rows, err = d.Query(d.FormatQuery(`
				SELECT amount, id, block_id, type_id, currency_id, to_user_id, time, comment, comment_status
				FROM my_dc_transactions
				WHERE type = 'from_user' AND
					  block_id < ? AND
					  exchange_checked = 0 AND
					  status = 'approved' AND
					  to_user_id = ?
				ORDER BY id DESC`), blockId-confirmations, mainDcAccount)
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}
		for rows.Next() {
			var amount float64
			var id, blockId, typeId, currencyId, toUserId, txTime int64
			var comment, commentStatus string
			err = rows.Scan(&amount, &id, &blockId, &typeId, &currencyId, &toUserId, &txTime, &comment, &commentStatus)
			if err != nil {
				rows.Close()
				if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
					break BEGIN
				}
				continue BEGIN
			}
			// отметим exchange_checked=1, чтобы больше не брать эту тр-ию
			err = d.ExecSql(`UPDATE my_dc_transactions SET exchange_checked = 1 WHERE id = ?`, id)
			if err != nil {
				rows.Close()
				if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
					break BEGIN
				}
				continue BEGIN
			}

			// вначале нужно проверить, точно ли есть такой перевод в блоке
			binaryData, err := d.Single(`SELECT data FROM block_chain WHERE id = ?`, blockId).Bytes()
			if err != nil {
				rows.Close()
				if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
					break BEGIN
				}
				continue BEGIN
			}
			p := new(dcparser.Parser)
			p.DCDB = d.DCDB
			p.BinaryData = binaryData
			p.ParseDataLite()
			for _, txMap := range p.TxMapArr {
				// пропускаем все ненужные тр-ии
				if utils.BytesToInt64(txMap["type"]) != utils.TypeInt("SendDc") {
					continue
				}
				log.Debug("md5hash %s", txMap["md5hash"])

				// если что-то случится с таблой my_dc_transactions, то все ввода на биржу будут зачислены по новой
				// поэтому нужно проверять e_adding_funds
				exists, err := d.Single(`SELECT id FROM e_adding_funds WHERE hex(tx_hash) = ?`, string(txMap["md5hash"])).Int64()
				if err != nil {
					rows.Close()
					if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
						break BEGIN
					}
					continue BEGIN
				}
				log.Debug("exists %d", exists)
				if exists != 0 {
					continue
				}

				log.Debug("user_id = %d / typeId = %d / currency_id = %d / currencyId = %d / amount = %f / amount = %f / comment = %s / comment = %s / to_user_id = %d / toUserId = %d ", utils.BytesToInt64(txMap["user_id"]), typeId, utils.BytesToInt64(txMap["currency_id"]), currencyId, utils.BytesToFloat64(txMap["amount"]), amount, string(utils.BinToHex(txMap["comment"])), comment, utils.BytesToInt64(txMap["to_user_id"]), toUserId)
				// сравнение данных из таблы my_dc_transactions с тем, что в блоке
				if utils.BytesToInt64(txMap["user_id"]) == typeId && utils.BytesToInt64(txMap["currency_id"]) == currencyId && utils.BytesToFloat64(txMap["amount"]) == amount && string(utils.BinToHex(txMap["comment"])) == comment && utils.BytesToInt64(txMap["to_user_id"]) == toUserId {

					decryptedComment := comment
					if commentStatus == "encrypted" {
						// расшифруем коммент
						block, _ := pem.Decode([]byte(nodePrivateKey))
						if block == nil || block.Type != "RSA PRIVATE KEY" {
							rows.Close()
							if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
								break BEGIN
							}
							continue BEGIN
						}
						private_key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
						if err != nil {
							rows.Close()
							if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
								break BEGIN
							}
							continue BEGIN
						}
						decryptedComment_, err := rsa.DecryptPKCS1v15(rand.Reader, private_key, utils.HexToBin(comment))
						if err != nil {
							rows.Close()
							if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
								break BEGIN
							}
							continue BEGIN
						}
						decryptedComment = string(decryptedComment_)
						// запишем расшифрованный коммент, чтобы потом можно было найти перевод в ручном режиме
						err = d.ExecSql("UPDATE "+myPrefix+"my_dc_transactions SET comment = ?, comment_status = 'decrypted' WHERE id = ?", decryptedComment, id)
						if err != nil {
							rows.Close()
							if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
								break BEGIN
							}
							continue BEGIN
						}
					}

					// возможно юзер сделал перевод в той валюте, которая у нас на бирже еще не торгуется
					if len(currencyList[currencyId]) == 0 {
						log.Error("currencyId %d not trading", currencyId)
						continue
					}

					// возможно, что чуть раньше было reduction, а это значит, что все тр-ии,
					// которые мы ещё не обработали и которые были До блока с reduction нужно принимать с учетом reduction
					// т.к. средства на нашем счете уже урезались, а  вот те, что после reduction - остались в том виде, в котором пришли
					lastReduction, err := d.OneRow("SELECT block_id, pct FROM reduction WHERE currency_id  = ? ORDER BY block_id", currencyId).Int64()
					if err != nil {
						rows.Close()
						if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
							break BEGIN
						}
						continue BEGIN
					}
					if blockId <= lastReduction["block_id"] {
						// сумму с учетом reduction
						k0 := (100 - lastReduction["pct"]) / 100
						amount = amount * float64(k0)
					}

					// начисляем средства на счет того, чей id указан в комменте
					r, _ := regexp.Compile(`(?i)\s*#\s*([0-9]+)\s*`)
					user := r.FindStringSubmatch(decryptedComment)
					if len(user) == 0 {
						log.Error("len(user) == 0")
						continue
					}
					log.Debug("user %s", user[1])
					// user_id с биржевой таблы
					uid := utils.StrToInt64(user[1])
					userAmountAndProfit := utils.EUserAmountAndProfit(uid, currencyId)
					newAmount_ := userAmountAndProfit + amount

					utils.UpdEWallet(uid, currencyId, utils.Time(), newAmount_, true)

					// для отчетности запишем в историю
					err = d.ExecSql(`
						INSERT INTO e_adding_funds (
							user_id,
							currency_id,
							time,
							amount,
							tx_hash
						)
						VALUES (
							?,
							?,
							?,
							?,
							?
						)`, uid, currencyId, txTime, amount, string(txMap["md5hash"]))
					if err != nil {
						rows.Close()
						if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
							break BEGIN
						}
						continue BEGIN
					}
				}
			}
		}
		rows.Close()

		/*
		 * Обновляем проценты
		 * */
		// максимальный номер блока для процентов. Чтобы брать только новые
		maxPctBlock, err := d.Single(`SELECT max(block_id) FROM e_user_pct`).Int64()

		log.Debug(`SELECT time, block_id, currency_id, user FROM pct WHERE  block_id < ` + utils.Int64ToStr(blockId-confirmations) + ` AND block_id > ` + utils.Int64ToStr(maxPctBlock))
		rows, err = d.Query(d.FormatQuery(`SELECT time, block_id, currency_id, user FROM pct WHERE  block_id < ? AND block_id > ?`), blockId-confirmations, maxPctBlock)
		if err != nil {
			if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
				break BEGIN
			}
			continue BEGIN
		}
		for rows.Next() {
			var pct float64
			var pTime, blockId, currencyId int64
			err = rows.Scan(&pTime, &blockId, &currencyId, &pct)
			if err != nil {
				rows.Close()
				if d.dPrintSleep(utils.ErrInfo(err), d.sleepTime) {
					break BEGIN
				}
				continue BEGIN
			}
			d.ExecSql(`
				INSERT INTO e_user_pct (
					time,
					block_id,
					currency_id,
					pct
				)
				VALUES (
					?,
					?,
					?,
					?
				)`, pTime, blockId, currencyId, pct)
		}
		rows.Close()

		if d.dSleep(d.sleepTime) {
			break BEGIN
		}
	}
	log.Debug("break BEGIN %v", GoroutineName)
}
Example #5
0
func NewForexOrder(userId int64, amount, sellRate float64, sellCurrencyId, buyCurrencyId int64, orderType string, eCommission float64) error {
	log.Debug("userId: %v / amount: %v / sellRate: %v / sellCurrencyId: %v / buyCurrencyId: %v / orderType: %v ", userId, amount, sellRate, sellCurrencyId, buyCurrencyId, orderType)
	curTime := utils.Time()
	err := userLock(userId)
	if err != nil {
		return utils.ErrInfo(err)
	}
	commission := utils.StrToFloat64(utils.ClearNull(utils.Float64ToStr(amount*(eCommission/100)), 6))
	newAmount := utils.StrToFloat64(utils.ClearNull(utils.Float64ToStr(amount-commission), 6))
	if newAmount < commission {
		commission = 0
		newAmount = amount
	}
	log.Debug("newAmount: %v / commission: %v ", newAmount, commission)
	if commission > 0 {
		userAmount := utils.EUserAmountAndProfit(1, sellCurrencyId)
		newAmount_ := userAmount + commission
		// наисляем комиссию системе
		err = utils.UpdEWallet(1, sellCurrencyId, utils.Time(), newAmount_, true)
		if err != nil {
			return utils.ErrInfo(err)
		}
		// и сразу вычитаем комиссию с кошелька юзера
		userAmount = utils.EUserAmountAndProfit(userId, sellCurrencyId)
		err = utils.DB.ExecSql("UPDATE e_wallets SET amount = ?, last_update = ? WHERE user_id = ? AND currency_id = ?", userAmount-commission, utils.Time(), userId, sellCurrencyId)
		if err != nil {
			return utils.ErrInfo(err)
		}
	}
	// обратный курс. нужен для поиска по ордерам
	reverseRate := utils.StrToFloat64(utils.ClearNull(utils.Float64ToStr(1/sellRate), 6))
	log.Debug("sellRate %v", sellRate)
	log.Debug("reverseRate %v", reverseRate)

	var totalBuyAmount, totalSellAmount float64
	if orderType == "buy" {
		totalBuyAmount = newAmount * reverseRate
	} else {
		totalSellAmount = newAmount
	}

	var debit float64
	var prevUserId int64
	// берем из БД только те ордеры, которые удовлетворяют нашим требованиям
	rows, err := utils.DB.Query(utils.DB.FormatQuery(`
				SELECT id, user_id, amount, sell_rate, buy_currency_id, sell_currency_id
				FROM e_orders
				WHERE buy_currency_id = ? AND
							 sell_rate >= ? AND
							 sell_currency_id = ?  AND
							 del_time = 0 AND
							 empty_time = 0
				ORDER BY sell_rate DESC
				`), sellCurrencyId, reverseRate, buyCurrencyId)
	if err != nil {
		return utils.ErrInfo(err)
	}
	defer rows.Close()
	for rows.Next() {
		var rowId, rowUserId, rowBuyCurrencyId, rowSellCurrencyId int64
		var rowAmount, rowSellRate float64
		err = rows.Scan(&rowId, &rowUserId, &rowAmount, &rowSellRate, &rowBuyCurrencyId, &rowSellCurrencyId)
		if err != nil {
			return utils.ErrInfo(err)
		}
		log.Debug("rowId: %v / rowUserId: %v / rowAmount: %v / rowSellRate: %v / rowBuyCurrencyId: %v / rowSellCurrencyId: %v", rowId, rowUserId, rowAmount, rowSellRate, rowBuyCurrencyId, rowSellCurrencyId)

		// чтобы ордеры одного и тоже юзера не вызывали стопор, пропускаем все его оредера
		if rowUserId == prevUserId {
			continue
		}
		// блокируем юзера, чей ордер взяли, кроме самого себя
		if rowUserId != userId {
			lockErr := userLock(rowUserId)
			if lockErr != nil {
				log.Error("%v", utils.ErrInfo(err))
				prevUserId = rowUserId
				continue
			}
		}
		if orderType == "buy" {
			// удовлетворит ли данный ордер наш запрос целиком
			if rowAmount >= totalBuyAmount {
				debit = totalBuyAmount
				log.Debug("order ENDED")
			} else {
				debit = rowAmount
			}
		} else {
			// удовлетворит ли данный ордер наш запрос целиком
			if rowAmount/rowSellRate >= totalSellAmount {
				debit = totalSellAmount * rowSellRate
			} else {
				debit = rowAmount
			}
		}
		log.Debug("totalBuyAmount: %v / debit: %v", totalBuyAmount, debit)
		if rowAmount-debit < 0.01 { // ордер опустошили
			err = utils.DB.ExecSql("UPDATE e_orders SET amount = 0, empty_time = ? WHERE id = ?", curTime, rowId)
			if err != nil {
				return utils.ErrInfo(err)
			}
		} else {
			// вычитаем забранную сумму из ордера
			err = utils.DB.ExecSql("UPDATE e_orders SET amount = amount - ? WHERE id = ?", debit, rowId)
			if err != nil {
				return utils.ErrInfo(err)
			}
		}
		mySellRate := utils.ClearNull(utils.Float64ToStr(1/rowSellRate), 6)
		myAmount := debit * utils.StrToFloat64(mySellRate)
		eTradeSellCurrencyId := sellCurrencyId
		eTradeBuyCurrencyId := buyCurrencyId

		// для истории сделок
		err = utils.DB.ExecSql("INSERT INTO e_trade ( user_id, sell_currency_id, sell_rate, amount, buy_currency_id, time, main ) VALUES ( ?, ?, ?, ?, ?, ?, 1 )", userId, eTradeSellCurrencyId, mySellRate, myAmount, eTradeBuyCurrencyId, curTime)
		if err != nil {
			return utils.ErrInfo(err)
		}

		// тот, чей ордер обрабатываем
		err = utils.DB.ExecSql("INSERT INTO e_trade ( user_id, sell_currency_id, sell_rate, amount, buy_currency_id, time ) VALUES ( ?, ?, ?, ?, ?, ? )", rowUserId, eTradeBuyCurrencyId, rowSellRate, debit, eTradeSellCurrencyId, curTime)
		if err != nil {
			return utils.ErrInfo(err)
		}

		// ==== Продавец валюты (тот, чей ордер обработали) ====
		// сколько продавец данного ордера продал валюты
		sellerSellAmount := debit

		// сколько продавец получил buy_currency_id с продажи суммы $seller_sell_amount по его курсу
		sellerBuyAmount := sellerSellAmount * (1 / rowSellRate)

		// начисляем валюту, которую продавец получил (R)
		userAmount := utils.EUserAmountAndProfit(rowUserId, rowBuyCurrencyId)
		newAmount_ := userAmount + sellerBuyAmount
		err = utils.UpdEWallet(rowUserId, rowBuyCurrencyId, utils.Time(), newAmount_, true)
		if err != nil {
			return utils.ErrInfo(err)
		}

		// ====== Покупатель валюты (наш юзер) ======

		// списываем валюту, которую мы продали (R)
		userAmount = utils.EUserAmountAndProfit(userId, rowBuyCurrencyId)
		newAmount_ = userAmount - sellerBuyAmount
		err = utils.UpdEWallet(userId, rowBuyCurrencyId, utils.Time(), newAmount_, true)
		if err != nil {
			return utils.ErrInfo(err)
		}

		// начисляем валюту, которую мы получили (U)
		userAmount = utils.EUserAmountAndProfit(userId, rowSellCurrencyId)
		newAmount_ = userAmount + sellerSellAmount
		err = utils.UpdEWallet(userId, rowSellCurrencyId, utils.Time(), newAmount_, true)
		if err != nil {
			return utils.ErrInfo(err)
		}

		if orderType == "buy" {
			totalBuyAmount -= rowAmount
			if totalBuyAmount <= 0 {
				userUnlock(rowUserId)
				log.Debug("total_buy_amount == 0 break")
				break // проход по ордерам прекращаем, т.к. наш запрос удовлетворен
			}
		} else {
			totalSellAmount -= rowAmount / rowSellRate
			if totalSellAmount <= 0 {
				userUnlock(rowUserId)
				log.Debug("total_sell_amount == 0 break")
				break // проход по ордерам прекращаем, т.к. наш запрос удовлетворен
			}
		}
		if rowUserId != userId {
			userUnlock(rowUserId)
		}
	}

	log.Debug("totalBuyAmount: %v / orderType: %v / sellRate: %v / reverseRate: %v", totalBuyAmount, orderType, sellRate, reverseRate)

	// если после прохода по всем имеющимся ордерам мы не набрали нужную сумму, то создаем свой ордер
	if totalBuyAmount > 0 || totalSellAmount > 0 {
		var newOrderAmount float64
		if orderType == "buy" {
			newOrderAmount = totalBuyAmount * sellRate
		} else {
			newOrderAmount = totalSellAmount
		}
		log.Debug("newOrderAmount: %v", newOrderAmount)
		if newOrderAmount >= 0.000001 {
			err = utils.DB.ExecSql("INSERT INTO e_orders ( time, user_id, sell_currency_id, sell_rate, begin_amount, amount, buy_currency_id ) VALUES ( ?, ?, ?, ?, ?, ?, ? )", curTime, userId, sellCurrencyId, sellRate, newOrderAmount, newOrderAmount, buyCurrencyId)
			if err != nil {
				return utils.ErrInfo(err)
			}

			// вычитаем с баланса сумму созданного ордера
			userAmount := utils.EUserAmountAndProfit(userId, sellCurrencyId)
			err = utils.DB.ExecSql("UPDATE e_wallets SET amount = ?, last_update = ? WHERE user_id = ? AND currency_id = ?", userAmount-newOrderAmount, utils.Time(), userId, sellCurrencyId)
			if err != nil {
				return utils.ErrInfo(err)
			}
		}
	}

	userUnlock(userId)

	return nil
}