Introduction to the blockchain
Important
Pre-requisites
- Python >= 3.7
-
We start the game with
$N$ players and$R$ reward points. The goal of each player is to maximize their own rewards. -
Participants have to commit to one of two strategies:
-
Fate; In this strategy, players are ranked based on when they committed to their strategy. The first one to commit is ranked 1st, and so on. Rewards then follows a power law such that players are given diminishing rewards from the first to the last.
-
Control; In this strategy, players chose what portion of the remaining rewards they assign to themselves. Those chosing control will have priority over those who play Fate, and players chosing Fate will receive rewards based on what's left.
-
-
Also, if 'Control' players collectively assign to themselves more than 50% of the total rewards, they are excluded from the game. Rewards are then split between 'Fate' players only.
-
Players can change their decision as many times as they wish until the game is over.
-
The game ends at the end of an agreed time period.
Note
In the Fate strategy, we'll split rewards using the following function:
So for example, for 4 players, the rewards distribution looks like this:
| Rank | Rewards |
|---|---|
| 1st | 40% |
| 2nd | 30% |
| 3rd | 20% |
| 4th | 10% |
Setting up PyCardano
To get started, we'll need to install PyCardano. Let's use a virtual environment for this to avoid potential clashes with the rest of the system.
python3 -m venv ./venv
source venv/bin/activate
pip install pycardano
To connect to a blockchain network, one needs to run a node that connects to the peer-to-peer network. In this workshop, however, we'll use an external blockchain provider which alleviates the burden of running the infrastructure ourselves. Blockchain providers are hosted services which are themselves connected to one or more node and provide entrypoints to the chain, often through higher level API.
In PyCardano's lingua, a blockchain provider is called a context. For this exercise, we'll use Blockfrost. When needed, you can create a context as follows:
from pycardano import BlockFrostChainContext
context = BlockFrostChainContext(
project_id="<BLOCKFROST_API_KEY_GOES_HERE>",
base_url="https://cardano-preview.blockfrost.io/api/",
)To play the game, you must first create a wallet; or said differently, generate credentials to receive funds and produce digital signatures. We'll use PyCardano to create cryptographic credentials for the game as such:
from pycardano import Address, Network, PaymentSigningKey
signing_key = PaymentSigningKey.generate()
verification_key = PaymentVerificationKey.from_signing_key(signing_key)
address = Address(payment_part=verification_key.hash(),
network=Network.TESTNET)Tasks:
- Create one file
me.skwith a freshly generated secret key. - Create one file
me.addrwith an address identifying the secret key.
Tip
You can turn a signing_key and an address into a text-friendly representation by using str. For example str(signing_key).
You'll need funds in order to submit transactions to the network. But worry not, we are only using a test network for this workshop. Once you have created your address, share it with the event organisers so they can allocate send funds to you. They will give you a transaction id which you can inspect on CardanoScan.
- Request test funds from the event organisers.
- Monitor funds locked by your address using CardanoScan.
- (Bonus) Monitor funds locked by your address using PyCardano.
Tip
To do it programmatically, you'll need to query your available unspent transaction outputs (UTxO). You can do this directly from a context. Note also that you can load your address as follows:
from pycardano import Address
with open("me.addr", "r") as f:
address = Address.from_primitive(f.read())Now is time to play the game and chose a strategy. The game is played by submitting a transaction to the network with some specific auxiliary data. On Cardano, auxiliary are associated with a label which is a non-negative number and some structured object.
Note
For this session, we'll use the label: 42.
Using PyCardano, we can create metadata using JSON, and we will expect them in the following form:
{ "strategy": "fate" }{ "strategy": "control", "percentage": P }, where0 < P <= 100
For example, one can create auxiliary data as follows:
from pycardano import (
AlonzoMetadata,
AuxiliaryData,
Metadata
)
auxiliary_data = AuxiliaryData(AlonzoMetadata(metadata=Metadata({
42: {
"strategy": "fate"
}
})))To build the transaction, have a look at PyCardano's Transaction builder.
Remember that the goal of the game is to maximize your own gain. Hence, you may want to monitor what others are doing before committing to anything. Note that you may still commit to a different strategy even after you played. The final state of the game will be determined precisely 2h after the beginning of the game.
Your last decision will be the one taken into consideration.
- Commit to a strategy
- (Bonus) Change your strategy based on other players
To adjust and adapt your strategy, you will most likely want to watch the chain and monitor what other players are doing.
One way to this is to check on CardanoScan / Metadata. However, this isn't very practical as it requires manually checking and monitoring the page. So it's good for a quick sanity check, but you'll likely want to write a script of your own instead.
You may also use either Blockfrost to monitor the chain.
With Blockfrost, you'll likely want to look at:
- The transaction metadata content in JSON endpoint. Be careful that we aren't the only users of the chain, so others may be using the same metadata tag for other purposes!
- Monitor other players choices
- (Bonus) Automate changes in your strategy. For example, automatically fire new transactions when you detect an opportunity. Remember that you can change your strategy until the very end.
The game ends after a fixed period of time, the blockchain ledger serves as final arbitrage for strategies committed by each player. After a short break, we'll collect final results and present the leaderboard to all participants.
The game illustrates a coordination problem where actors need to coordinate in the presence of possible adversaries. It is in the best interest of all participants to end the game quickly. While the most rationale strategy is for everyone to quickly commit to a 'Fate' strategy, it is almost certain that at least one person will try to commit to a 'Control' strategy. Because it is hard to know what the final state of the game is since anyone can change their decision up until the very end, it makes it also hard to trust anyone.
This is meant to emphasize two things:
-
Trust is very difficult to define and obtain in practice because self interest often gets in the way.
-
Distributed systems are complex but powerful as they can be accessed and modified by everyone concurrently.
print("foo")with open("filename.extension", "w") as f:
f.write(foo)with open("filename.extension", "r") as f:
foo = f.read()