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 }
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, ¤cyId, &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, ¤cyId, &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, ¤cyId, &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) }
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 }