From a blockchain perspective the most important thing to remember is: Fabric is not Ethereum. Though many of the same data design and concurrency principles will still apply, there are definitely differences to know as we design our game.
Unfortunately this post seemed to turned into another "Why Fabric" post. Sorry about that! I'll need to reorganize this later.
Ethereum vs. Fabric considerations
All of these comparisons are against Ethereum Main-Net, and not clones or private networks. For more info about why not Ethereum, see the Why Fabric post.
Since Fabric is Proof of Authority (PoA) the model is fundamentally different. Bad Actors can be excluded from the network, and Proof of Stake (PoS) can be built on top of the PoA consensus to ensure that people participating in the network have something to lose for abusing it. The PoA we implement is only as centralized as we make it. It's possible to do something like the popular Distributed Proof of Stake (dPOS) model where there are a decent number of trusted parties controlling the network that are voted in or out.
The PoA consensus model is often associated with "rug pulls", but it is not so simple. Yes, a fully centralized PoA model with no contractual limits would allow for a complete rug pull. However, a PoA model where there are multiple parties involved and there are contractual limits to what even they can do is quite secure. Also, if the network is actually decentralized and source code is publicly available, the community could hard fork away from the original blockchain (and the authorities running it).
In Ethereum, the idealistic definition of "secure" is to make sure it's 100% correct with no bugs and can never be changed once it is deployed. You don't have to look very long to find a news headline about a compromised Ethereum contract (FYI, bridges are contracts). This shows that making sure something is 100% correct, even with extensive audits, is very difficult. What's worse is when the vulnerability is found, and is actively being exploited and no one can do anything because it's not possible to modify the contract.
It's becoming more popular to make it possible to pause or upgrade contracts. But if this isn't controlled by some sort of consensus mechanism, then the owner of the contract can rug pull everyone by "upgrading" a contract that currently has no vulnerabilities and deploying a new version that lets them withdraw all of the assets. Implementing this kind of consensus (via multi-sig, oracles, or some other implementation) is more expensive from a development perspective and from a network fees perspective - so it's not done as often as it should be.
The other down-side to making sure the contract never changes means that contracts often have very limited functionality. They only do basic things like tracking ownership of assets, and not complicated things like provably scoring game matches or upgrading NFTs.
This was a brief introduction to how Ethereum supposedly makes things secure from a contract perspective, and also all the reasons it's not actually as secure as it is believed to be.
Network fees for Ethereum transactions can be quite expensive. This means that developers must spend a lot of time optimizing the contract to have as low of a fee as possible, which often leads to developers completely removing functionality from the contract and doing it off-chain instead. In my opinion, when we start taking important data off-chain we lose the transparency and reliability we were using blockchain for in the first place. This is one of the main reasons I dislike using existing Layer 1 blockchains: our project will have no control over the fees on that blockchain and we don't ever want our project to be held captive by high network fees. If that were to happen, we would then need to decide which network to move to or build our own. I'd rather preemptively solve that problem instead of hoping it doesn't bite us later.
This is why I prefer using Fabric, or a private network of some other framework, and bridging to Ethereum. Fabric has no concept of fees; this means we can charge nothing for network actions, or we can build an alternative fee structure. Of course, we still need to be able to fund our network to keep it online.
Because there are no fees in Fabric, we can put a lot more logic and data on-chain. We can have full transparency of all game actions if we really wanted it.
Ethereum, and many other Layer 1's, have much slower speeds than Fabric. There are many reasons for this, some more justifiable than others. But as far as we're concerned, they're too slow. Even if they aren't slow today, we need to be concerned that the network will slow down if it becomes popular. There was a time when even Ethereum was great to use because it was faster than Bitcoin and network speeds were low.
I picture public Layer 1's as high-speed freeways. Pretty much all cars will eventually need to drive on them, but it is not likely that cars will spend all of their time there. If we have a business operating an ice cream truck then it's not a great idea to pull over on the side of the freeway and expect local children to come get ice cream. It's a much better idea to drive our truck to the safer neighborhood streets where we don't have the same rules and regulations as the interstate freeway. Yes, we will still drive our ice cream truck on the freeway between jobs or to store the vehicle, but that's not where our main business takes place. That's good for the freeway, because it's less traffic there, and it's good for us because we can operate our business more effectively.
An awesome feature with Fabric is that network speeds can be adjusted. The speed of the network can be configured by time, block size, or message count. Furthermore, these parameters can be quickly adjusted while the network is running. This means a Fabric-based network can easily adjust over time, or even configure the network to support large-scale operations happening at a specific time.
Blockchain-as-a-Database, or BaaD, is how I like to describe Fabric. It gives us the flexibility and performance we need to treat the blockchain like a database, including constraints and triggers!
We will face issues similar to those in traditional database design and administration. Some of these concepts have different names in blockchain, but the solutions from traditional database technologies can still be applied.
Key Collisions, also called MVCC (Multiversion Concurrency Control) errors, happen when the same key is written multiple times in the same block. This is currently a limitation in Fabric that Ethereum does not have. This means we need to carefully design our data to make sure that the same key is not being edited by multiple transactions happening in the same block.
While this may seem like a win for Ethereum, it's not very often that an individual will get multiple transactions in the same block anyway. Due to network gas fees and speed, a single wallet's transactions will usually end up in different blocks unless they're automating the transactions.
The topic of concurrency control is well documented. The easiest solution to concurrency issues is to not run anything concurrently, but this does not scale well. We must design our data in a way that multiple players can interact with a game in real-time without blocking keys for other transactions. This is one of the reasons Bitcoin uses the UTXO model, but UTXO comes with the disadvantage of needing to do many more calculations just to find a balance. Since we want to treat our blockchain as a database and easily read values, UTXO isn't going to be our first choice.
The solutions to key collisions, in priority order, are:
- Data Design
- Design your data to separate writes to different keys. This means we will NOT be using the document database approach of storing everything in a single document and updating the entire document on each write.
- Contract Design
- Add functions to the contract that allow batch operations to happen. For example, instead of having a contract call like
MintToken(User)have a function like
MintToken(Array<User>)so that we can pass multiple users who will receive a newly minted token. This reduces the impact of blocking keys by requiring fewer contract calls to get the same result.
- Client Design
- Have the game client ensure actions are not happening too rapidly. This does not replace the Contract Design though, because people CAN and WILL hack or bypass the game client to send unexpected calls to our contract.
There are a few ways we can design our infrastructure:
Users run the game client (desktop, console, or web browser) and it connects to the game server:
This offers great performance and allows for easy maintenance. It does not offer any transparency or decentralization. This is the current state of most games.
Web 2.5 Gaming
At the time of writing, this is how almost everyone in "blockchain gaming" does it:
There are two big problems with this:
Those extra steps between the client and the blockchain mean more delays, which means more things running asynchronously and updating eventually. Or, it means less of the information is going to the blockchain which means less transparency.
Those components sitting between the client and the blockchain are (usually) operated by a central entity - this is the opposite of decentralizing and adding transparency. This creates a single point of failure if the game developer abandons the game or is forcibly shut down. This is also a security issue because the game developer is handling more private information than should have access to. There are ways around this, like using signed messages, but this is extra work and not all developers do it the right way.
Web 3 Gaming
This is truly decentralized gaming, and very few are doing it:
There is no "middle man" here. We can see that the client is going to be heavier because it will also communicate with the blockchain directly. In this idealist configuration, there is no "game server" because all game logic exists in the contract deployed to the blockchain. Any central authority (like a game developer) only has as much control over the game as they have programmed into the contract, and it can be inspected by 3rd parties to ensure the game developer can't "rug pull" everyone.
Making this architecture a reality is quite difficult. Many blockchains cannot handle the speed and volume of transactions to make a real-time game, which is why you see so many card-based or idle-battler games. These types of games have defined inputs, defined outputs, can run somewhat asynchronously, and don't need to have sub-second response times.
The other reason these types of games are common is that they are easy to implement because they have deterministic results. Usually in card-based games players playing the same cards in the same sequence will always yield the same results. This is a perfect fit for how blockchains work: all nodes must be able to calculate something and agree on the result. No single node has more authority than others in determining the result, and this helps prevent cheating.
How would this work for a fast-paced FPS? Two players are running around the map shooting at each other and each one says they killed the other first. Who is correct? There are perfectly legitimate reasons for there to be disagreement here (like network latency), and there are illegitimate reasons (like cheating). If the peers in a blockchain cannot come to a consensus on the result of a transaction, then it will fail. Not all games lend themselves to a fully deterministic and thus "trustless" scoring model, which means there WILL be some 3rd party that needs to make a decision about who won. The more popular a game is, the more valuable items for it will be. The more valuable items for it are, the more effort people are willing to put in to cheat and make money. Some games attempt to solve this through authority election or staking mechanisms; many games avoid this complexity all together and stick to game styles that are easier to adapt to blockchain, or ONLY deal with asset ownership on-chain and leave all other game actions in the off-chain and non-transparent ways of the past.
Wow, that was a lot of words. It still doesn't cover all of the things we need to consider to take this game live, but it does touch on some of the key points and decisions we need to make early on. By choosing a technology that lets us be flexible, we can solve some of these problems when they get closer and clearer. (I'm a big fan of the Last Responsible Moment)
Let's recap some of the high-level goals for this project:
- We want to be able build quickly now and decentralize later
- Which will include allowing people to run the nodes for the network
- We want to start out with a round-based game but eventually support faster-paced games in the same ecosystem
- We want the game to be accessible
- both from a network and cost perspective
- We want the game to be sustainable, which means:
- No Proof-of-Work
- No hyper-inflation
- No selling promises and leaving players holding the bag
To support this we will use Fabric, and an architecture that looks like a mixture of Web 2.5 and Web 3 shown above.
I want to try something new to make adoption easier: we will treat our blockchain as a "side-chain" to other blockchains. We will assign identity based on external blockchains, but maintain transparency and data for all transactions within our blockchain.
This is how I picture the initial architecture:
For now, I think the Blockchain Client will be our FireFly node.
Our architecture may evolve into this:
In this slightly modified approach, the client will take on the blockchain client functionality and maintain control of all keys and private material. Players may be able to run their own full FireFly nodes and participate in operating the network itself.
This was a lot of thinking... Now it's time to start doing. I'm going to put together a basic Go program that has the right objects and a basic flow in the game.