The problem scenario: a hacker sends multiple CloudScript requests simultaneously, in order to do things like open a chest twice, complete a quest twice, etc. I've read the answer here, https://community.playfab.com/questions/25146/multiple-calls-to-a-executecloudscript-how-to-deal.html, which proposes to use after-the-fact analytics to detect when such behaviors occur. However, in a multiplayer game, allowing duplicate CloudScripts to successfully execute could have negative impacts on other players - I'd like to try and prevent that if possible.
My proposed solution is to use an Entity Object on title_player as a semaphor, aka, a multi-thread lock, by utilizing the optimistic concurrency option. I would also disable title_player access to itself using the global policy, making the objects in title_player entity similar to internal player data. Here is a simple example in pseudo code (I haven't used the Entity API yet) of how I envision the system working:
Context: sensitive actions are performed when a player completes a quest; thus, the player should only be able to complete a given quest once. Two calls are made by the client:
A: finishQuest(questId: 12345)
B: finishQuest(questId: 12345)
Assume at the start that LastQuestCompleted != 12345
handlers.finishQuest(questId){ var entity = server.GetEntity(playerId); var getObjectsResult = entity.GetObjects(key: "LastQuestCompleted"); var lastQuestCompleted = getObjectsResult ["LastQuestCompleted"]; var profileVersion = getObjectsResult ["ProfileVersion"]; // this check will fail if A and B are executed simultaneously if(lastQuestCompleted == questId) return denied; // as I understand it, this will always fail for either A or B var result = entity.SetObjects( "LastQuestCompleted": questId, "ExpectedProfileVersion": profileVersion ); if (result == error) return denied; doStuff(); }
Can I expect this approach to work, and is it a reasonable? Thanks!
edit: I realized that it looks possible to turn it into a pessimistic concurrency system quite easily...does this work?
var getObjectsResult = entity.GetObjects() // deny if currently locked if(getObjectsResult.Locked) return denied; // attempt to lock var result = entity.SetObjects(Locked: true); // deny if optimistic concurrency failed if(result == error) return denied; doStuff(); // unlock entity.SetObjects(Lock: false);