Exemplo n.º 1
0
func EPayment(paymentInfo string, currencyId, txTime int64, amount float64, paymentId int64, paymentSystem string, eCommission float64) error {

	var userId int64
	r, _ := regexp.Compile(`(?i)token\-([0-9]+)`)
	t_ := r.FindStringSubmatch(paymentInfo)
	token := ""
	if len(t_) > 0 {
		token = t_[1]
	} else {
		userId = utils.StrToInt64(paymentInfo)
	}
	var buyCurrencyId int64
	if len(token) > 0 {
		err := utils.DB.ExecSql(`UPDATE e_tokens SET status = 'paid' WHERE id = ?`, token)
		if err != nil {
			return utils.ErrInfo(err)
		}
		data, err := utils.DB.OneRow(`SELECT user_id, buy_currency_id FROM e_tokens WHERE id = ?`, token).Int64()
		if err != nil {
			return utils.ErrInfo(err)
		}
		userId = data["user_id"]

		buyCurrencyId = data["buy_currency_id"]
	}
	err := utils.DB.ExecSql(`INSERT INTO e_adding_funds_`+paymentSystem+` (id, user_id, currency_id, time, amount) VALUES (?, ?, ?, ?, ?)`, paymentId, userId, currencyId, txTime, amount)
	if err != nil {
		return utils.ErrInfo(err)
	}
	if userId > 0 {
		err = utils.UpdEWallet(userId, currencyId, utils.Time(), amount, false)
		if err != nil {
			return utils.ErrInfo(err)
		}

		// автоматом создаем ордер, если это запрос через кошель Dcoin
		if len(token) > 0 {
			err = NewForexOrder(userId, amount, 1, currencyId, buyCurrencyId, "buy", eCommission)
			if err != nil {
				return utils.ErrInfo(err)
			}
		}
	}
	return nil
}
Exemplo n.º 2
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)
}
Exemplo n.º 3
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
}