From 233e7feafa959e65a76bff485e2fce00d13a1c41 Mon Sep 17 00:00:00 2001 From: Greg Edwards Date: Wed, 27 Jul 2022 02:06:44 -0400 Subject: [PATCH] Lock a GTS offer when initiating a trade. --- gts/pokemondpds.ashx.cs | 8 ++++++++ gts/syachi2ds.ashx.cs | 8 ++++++++ library/Data/DataMysql.cs | 38 ++++++++++++++++++++++++++++++++++++++ library/Data/Database.cs | 6 ++++++ library/database.sql | 6 +++++- 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/gts/pokemondpds.ashx.cs b/gts/pokemondpds.ashx.cs index 6721fcf0..5c923d01 100644 --- a/gts/pokemondpds.ashx.cs +++ b/gts/pokemondpds.ashx.cs @@ -443,6 +443,14 @@ namespace PkmnFoundations.GTS return; } + if (!Database.Instance.GtsLockPokemon4(targetPid, pid)) + { + // failed to acquire lock, implying someone else beat us here. Say already traded. + SessionManager.Remove(session); + response.Write(new byte[] { 0x02, 0x00 }, 0, 2); + break; + } + // uncomment these two lines if you're replaying gamestats requests and need to skip the random token //session = new GamestatsSession(this.GameId, this.Salt, pid, "/pokemondpds/worldexchange/exchange.asp"); //SessionManager.Add(session); diff --git a/gts/syachi2ds.ashx.cs b/gts/syachi2ds.ashx.cs index 7b70ad70..0b6a925b 100644 --- a/gts/syachi2ds.ashx.cs +++ b/gts/syachi2ds.ashx.cs @@ -406,6 +406,14 @@ namespace PkmnFoundations.GTS return; } + if (!Database.Instance.GtsLockPokemon5(targetPid, pid)) + { + // failed to acquire lock, implying someone else beat us here. Say already traded. + SessionManager.Remove(session); + response.Write(new byte[] { 0x02, 0x00 }, 0, 2); + break; + } + object[] tag = new GtsRecord5[2]; tag[0] = upload; tag[1] = result; diff --git a/library/Data/DataMysql.cs b/library/Data/DataMysql.cs index 97d2364c..6619158a 100644 --- a/library/Data/DataMysql.cs +++ b/library/Data/DataMysql.cs @@ -349,6 +349,23 @@ namespace PkmnFoundations.Data return true; } + public override bool GtsLockPokemon4(int offer_pid, int partner_pid) + { + return WithTransaction(tran => GtsLockPokemon4(offer_pid, partner_pid)); + } + + public bool GtsLockPokemon4(MySqlTransaction tran, int offer_pid, int partner_pid) + { + DateTime now = DateTime.UtcNow; + int rows = tran.ExecuteNonQuery("UPDATE GtsPokemon4 SET LockedUntil = @locked_until, LockedBy = @locked_by " + + "WHERE LockedUntil < @now OR LockedUntil IS NULL", + new MySqlParameter("@locked_until", now.AddSeconds(GTS_LOCK_DURATION)), + new MySqlParameter("@locked_by", partner_pid), + new MySqlParameter("@now", now)); + + return rows > 0; + } + public override bool GtsTradePokemon4(GtsRecord4 upload, GtsRecord4 result, int partner_pid) { return WithTransactionSuccessful(tran => GtsTradePokemon4(tran, upload, result, partner_pid)); @@ -1520,6 +1537,23 @@ namespace PkmnFoundations.Data return true; } + public override bool GtsLockPokemon5(int offer_pid, int partner_pid) + { + return WithTransaction(tran => GtsLockPokemon5(offer_pid, partner_pid)); + } + + public bool GtsLockPokemon5(MySqlTransaction tran, int offer_pid, int partner_pid) + { + DateTime now = DateTime.UtcNow; + int rows = tran.ExecuteNonQuery("UPDATE GtsPokemon5 SET LockedUntil = @locked_until, LockedBy = @locked_by " + + "WHERE LockedUntil < @now OR LockedUntil IS NULL", + new MySqlParameter("@locked_until", now.AddSeconds(GTS_LOCK_DURATION)), + new MySqlParameter("@locked_by", partner_pid), + new MySqlParameter("@now", now)); + + return rows > 0; + } + public override bool GtsTradePokemon5(GtsRecord5 upload, GtsRecord5 result, int partner_pid) { return WithTransactionSuccessful(tran => GtsTradePokemon5(tran, upload, result, partner_pid)); @@ -2491,6 +2525,10 @@ namespace PkmnFoundations.Data private void BattleVideoUpdateHypeTimes(MySqlTransaction tran, string tableName, DateTime hypeTime) { + // todo: run this less often by caching the HypeTimestamp somewhere + // run it only once a week + // use cached timestamp for insertions/updates too + // common case: HypeTimestamp is in the past and needs to be updated and decay applied. tran.ExecuteNonQuery("UPDATE " + tableName + " " + "SET Hype = Hype / (1 << FLOOR(DATEDIFF(@hypetime, HypeTimestamp) / @decay)), HypeTimestamp = @hypetime " + diff --git a/library/Data/Database.cs b/library/Data/Database.cs index ab2d9ccc..85accb7b 100644 --- a/library/Data/Database.cs +++ b/library/Data/Database.cs @@ -96,6 +96,8 @@ namespace PkmnFoundations.Data return dateNow.AddDays(-(int)dateNow.DayOfWeek); } + internal const double GTS_LOCK_DURATION = 60.0d; + #endregion #region GTS 4 @@ -108,6 +110,8 @@ namespace PkmnFoundations.Data public abstract bool GtsDeletePokemon4(int pid); + public abstract bool GtsLockPokemon4(int offer_pid, int partner_pid); + public abstract bool GtsTradePokemon4(int pidSrc, int pidDest); public abstract bool GtsTradePokemon4(GtsRecord4 upload, GtsRecord4 result, int partner_pid); @@ -158,6 +162,8 @@ namespace PkmnFoundations.Data public abstract bool GtsDeletePokemon5(int pid); + public abstract bool GtsLockPokemon5(int offer_pid, int partner_pid); + public abstract bool GtsTradePokemon5(int pidSrc, int pidDest); public abstract bool GtsTradePokemon5(GtsRecord5 upload, GtsRecord5 result, int partner_pid); diff --git a/library/database.sql b/library/database.sql index cd2b1fb5..07ba88ad 100644 --- a/library/database.sql +++ b/library/database.sql @@ -329,7 +329,9 @@ CREATE TABLE IF NOT EXISTS `GtsPokemon4` ( `IsExchanged` tinyint(3) unsigned NOT NULL, `TrainerVersion` tinyint(3) unsigned NOT NULL, `TrainerLanguage` tinyint(3) unsigned NOT NULL, - `ParseVersion` int(11) unsigned DEFAULT NULL, + `ParseVersion` int(10) unsigned DEFAULT NULL, + `LockedBy` int(11) DEFAULT NULL, + `LockedUntil` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `pid` (`pid`), KEY `Species` (`Species`), @@ -368,6 +370,8 @@ CREATE TABLE IF NOT EXISTS `GtsPokemon5` ( `TrainerBadges` tinyint(3) unsigned NOT NULL, `TrainerUnityTower` tinyint(3) unsigned NOT NULL, `ParseVersion` int(10) unsigned DEFAULT NULL, + `LockedBy` int(11) DEFAULT NULL, + `LockedUntil` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `pid` (`pid`), KEY `Species` (`Species`),