Global market trading

This describes, what is global market trading system, and how it was implemented. It also contains discussion of security and race conditions

This is sub-layout for documentation pages


1 Introduction

2 Requirements

We require following things:

3 Implementation

The solution uses direct sql database access method, which means, that (almost) every request executes an sql query, and then returns some result (asynchronously).
This is extremely dangerous since it must be implemented correctly, and even a smallest flaw can lead to serious game exploits.
On the other hand, the persistence (data saving) happens immediatelly

3.1 Database structure

We will have 2 database tables: dol_globalMarketOffers will contain all available sales, and dol_globalMarketDepositBoxes will contain all deposit boxes data for all users.
The data will never be deleted from these tables, which will allow players to see a transaction history. Only the old records will have some bit indicating, that they are no longer valid now.

3.2 The public server methods

By this, we reffer to code functions, which will be called as a result of a user input (=when packet is received)
This defines, what actions can player take upon the global market:

3.2.1 Search for existing sale offers, using some filter

This is harmless function, which only executes a select upon a database. There is no mutex needed, and no race condition may occur. The worst thing that can happen, is that the player will see not up to date results, (due to insert statement completing right after select statement).
But really, player has no way to tell that such a thing happen, and even if so, we do not care, as he can search again...

3.2.2 Buy some items from an existing offer

When used, the player would buy X items out of Y offered, where \(X < Y\). If finishes correctly, will give the buyer bought item into a deposit box, and to seller, the money to his deposit box.
This function executes following sql statements: select, insert, update.
It is vital, that this function uses a mutex, since calling this function multiple times in fast succession will generate undefined behavior.
If mutex is used, and all input is validated (and all situations are validated), then this is safe, and can not be exploited. Here is a rough list of situations, taken to consideration:

and so on, and so on...

3.2.3 Collecting items from a deposit box

When used, the items ready for collection in the target deposit box are given to the owner player, and removed from the deposit box. If the deposit box shall not contain any more items, and the underlying offer is completed, the deposit box is freed.
This function may execute following sql statements: select, delete, update.
As this is user-only related feature, there is not much that can go wrong. Of course, validate all input, but there are no serous race cunditions that may occur

3.2.4 Create a new sell offer

When used, a new sell offer will be created, and added to the system for everyone to see. The sold goods will be removed from the seller.
This function may execute following sql statements: select, insert.
This function needs a mutex aswell, but again, there are no situations, when you might create some serious race conditions.

3.3 Exploit discussion

Let's now have a look on how we might try to exploit the game, crash the server, or gain nefarious advantage, and discuss, whether it would be possible, and why.
Exploits that contain bad values, numbers, or non existent items, are quite silly, and they are impossible in my implemetation. Let's have a look at more advanced stuff here:

3.3.1 Race conditions

There are 2 situations, which can lead to udefined behavior. The basic idea is, that when player (seller of offer \(A\)) logs in, and in the same time, offer for that seller is bought, then the select and update can lead to undefined behavior.

Scenario 1

Players, A, B. Player A is offline, player B is online. Player A has created a sell offer. Player B buys that sell offer. In that exact moment, player A logs in, loading all his saves / data.
player B's loaded data would contain all except the just completed offer, as it would be saved to the database after loading player, but therefore, would not be loaded into a memory.

Here are diagrams:

This is quite vicious problem. Normally, we would not let action \(1\) proceed, while action \(2\) is taking place, but in this case, we must load the data for user.
The solution is to create a third class, which contains priority queue for loading user data, and then a mutex.