Lab 7: Transactions
In today’s lab, at long last, we will add transactions and actual GoatCoin to the network. Huzzah!
Mining helper program
I created a Rust program that you can use to help with nonce generation for goatcoin mining. The purpose of sharing this is not to diminish what you’ve already worked on, but to let us all mine at (close to) the same speed, so the network is more fair.
On your VM (or any other linux machine on the USNA network), run:
cargo install --git http://gitlab.usna.edu/roche/goatminer --root ~
source ~/.profile
This should automatically download, compile, and install a program called goatminer
to your ~/bin
directory. You should now be able to run this program goatminer
from the command-line.
Here’s how the program works:
- The number of leading zeros needed for the hash (e.g., 24) is given as a command-line argument.
- The JSON encoding of a block (with or without the nonce field filled in) is sent to the program through standard in.
- A JSON encoding of a completed block, with a
nonce
field set such that the resulting hash has the required number of leading zeros, is returned via standard out.
Here’s an example of using this directly on the command line. Here I am making a JSON string and saving it to a file directly, then redirecting that file into the goatminer
program, redirecting the output to another file. Finally, I use openssl
to confirm the hash at the end.
roche@cat:~$ cat >original.json
{ "version": 1,
"prev_hash": "000000a5919b38ddc98f4fdadd10ab507141f95908c908cd6994ea58e4e034b3",
"payload": {"chat": "heloooo"}
}
roche@cat:~$ goatminer 20 <original.json >mined.json
roche@cat:~$ cat mined.json; echo
{"payload":{"chat":"heloooo"},"prev_hash":"000000a5919b38ddc98f4fdadd10ab507141f95908c908cd6994ea58e4e034b3","version":1,"nonce":192058}
roche@cat:~$ openssl dgst -sha3-256 <mined.json
(stdin)= 000003a4e7d916e352ee019ee4b68862b3e1013ef99a39e1636cbf732301d6e3
Notice that the resulting hash at the end starts with 5 hex zeros, because we specified 20 binary zeros on the command line.
To use this in your Python program, you can use the subprocess library. I’m not going to show you exactly how to use this library to call goatminer
, but here is how you could use the tr
program to replace all a’s with b’s in a string in Python:
>>> import subprocess
>>> s = "I want to eat pizza."
>>> result = subprocess.run(['tr', 'a', 'b'], input=s, text=True, capture_output=True, check=True)
>>> result.stdout
'I wbnt to ebt pizzb.'
Simple transactions in GoatCoin
For now, GoatCoin transactions will be really simple: Any transaction represents exactly 1 GoatCoin, with 1 input and 1 output. There are no miner fees, and no way to split or combine coins.
This will involve a new piece of the payload field for any (version 1) block.
A single transaction is represented by a JSON dictionary, containing three fields, all of which are represented as strings:
input
: a SHA3-256 hash of the previous transaction that is being spentoutput
: a hex-encoded ED25519 public keymessage
: Any string
This transaction must be encoded as JSON (using
json.dumps
) into a JSON string, which we will call a TJ for “transaction JSON”.That TJ string must be signed with the private key of the input source, to produce an ED25519 signature.
(Separately, in some later transaction, a hash of the TJ string is what will go as the
input
when we want to spend this coin later on.)The
payload
of any block will now contain a field calledtxns
, which is a list of dictionaries. Each dictionary in the list will contain two fields:tj
which contains the TJ string described above, andsig
which contains a (hex-encoded) signature as described above.
All of the above rules apply for “normal” transactions. The first transaction in a block can be a special coinbase transaction which gets 1 GoatCoin block reward. This transaction will be the same as any other transaction except:
The input field must still be there as part of the TJ string, but it will be ignored. It should be some random hash, i.e., a 32-byte hex string that is randomly generated.
The signature
sig
should be the stringcoinbase
(instead of a real hex-encoded ED25519
Whew! That seems complicated but it’s really not too bad. Here is a complete example block:
{
"payload": {
"txns": [
{
"sig": "coinbase",
"tj": "{\"input\": \"af9ab22c6240ac91070a9593d61354417ceef08c8cddfed3b92140bb55093903\", \"output\": \"5e42953773000a66d1554135ff88cc57619631eeac7884c8300b135cf9f284b2\", \"message\": \"Pay me!\"}"
}
]
},
"prev_hash": "0000005953ce5f58ceb8f8fbd13251bbe94aab191492881697e16582d142d2eb",
"version": 1,
"nonce": 33016957
}
This block has one transaction, which is a coinbase transaction that pays the block reward of 1 GoatCoin to the public key that starts with 5e4295...
The hash of the tj
string in the example above starts with 675e449...
, which is the transaction id that we can use to refer to this coin when we want to spend it later.
Here’s another block where this coin gets spent (and a new coin is mined again):
{
"payload": {
"txns": [
{
"sig": "coinbase",
"tj": "{\"input\": \"ae9dcdb4cf8051fcdb019019e3aab0f7a7666d404f441ca6e1a12d68b79cef0e\", \"output\": \"5e42953773000a66d1554135ff88cc57619631eeac7884c8300b135cf9f284b2\", \"message\": \"Mine mine\"}"
},
{
"sig": "c18a0f2b7f849d6e600f0427ec10529de526ccde29a45261c2f7ef62c99631b3e20adbd799a6feb7a4040034a309935382ad554be2bc85ccceed2191e6bb5b07",
"tj": "{\"input\": \"675e449d7a2f51d6402f26b0485d3046ff258ed864f197a654890f6f879f8b4e\", \"output\": \"310fa0cf4c5ce45864fccf09c407c18da9716800a81875ca49d41b5d89dd38e2\", \"message\": \"full spend\"}"
}
]
},
"prev_hash": "0000000a9625319c204469a24541b4b2e54d3cccf0abf48ec1486190eb42aa97",
"version": 1,
"nonce": 14164503
}
Check server
As with last week’s lab, the horse
server can be used to check your work.
On port 5002, horse
is running a working GoatCoin node with a few dozen blocks, including a few blocks with valid transactions. This server will also accept /push
requests and validate new transactions, but its head node will never change. This way you can check that your code works without having to deal with a chain of thousands of blocks.
On port 5000, horse
is running a check server that will check your server is working properly. To use it, on the command line enter
curl http://horse:5000/check/HOST/PORT
Your tasks
Here’s what you need to do:
Modify your
server.py
so that it looks at any transaction information in payloads ofpush
requests and verifies the transactions before accepting the new block.Write a program
send_tx.py
, similar tosend_chat.py
, that mines a block and includes a coinbase transaction that pays out to your own key.Modify
send_tx.py
further so that it can also include additional transactions besides the coinbase transaction. Use that to make a transfer from a previously-mined coin.
There are a lot of checks you need to make to validate a transaction! We looked at these things in class:
Properly formed transaction: contains
tj
andsig
fields, and the string intj
is a valid JSON-encoded block withinput
,output
, andmessage
.Both
input
andoutput
should be 32-byte (64-character) hex strings, corresponding respectively to a hash digest and a ED25519 verification key.(Note, you can’t really check that it’s a “real” hash digest or “real” verification key without knowing the hash input or the private key. So you’re really just checking that it’s a 32-byte hex string.)
If the
sig
is"coinbase"
, then he have to make sure it’s a valid coinbase transaction:- Must be the first transaction in the block. (This ensures there is never more than one coinbase in a block — you can’t just print infinite money!)
Otherwise, the
sig
must be a 64-byte hex string, and:The
input
must be the hash digest of some previous transaction’stj
string on the same blockchain. (This is called a transaction id.)Call this previous transaction the input txn.
The input txn must not be already spent. (Meaning, the same
input
should not appear as theinput
of any existing transaction on the same blockchain.)This makes sure the owner of a GoatCoin can’t spend it more than once.
The
sig
on this transaction must be a valid signature over thetj
on this transaction and the public key specified by theoutput
of the input txn.This ensures that the person spending this GoatCoin actually owns it!
To make this work, you probably want to write some loop that goes backwards in the blockchain looking for either (1) the same input
, meaning a double-spend and this transaction is invalid; or (2) the matching hash of the transaction id, meaning you’ve found the input txn and can check the signature.
Work carefully and use the examples you have here and on the horse
server. You can do it! Go make that coin!