In my first article I introduced blockchain theory, what it can do for your software project and the basics of interacting with the Nxt blockchain in PHP.
Today I am going to present a small Lottery program written in Go.
Pre-requisites:
Golang (tested with Go 1.6.2)
NRS 1.10.1 (https://bitbucket.org/JeanLucPicard/nxt/downloads/)
The program is fully functional and runs a lottery every Sunday [1].
It was originally written in PHP, both sources are available for download from the Nxt Data Cloud [2].
Application logic
Data feed for this app is ‘tickets’ submitted by users in attachments.
A lottery player sends 10 NXT to the lottery account and attaches a public unencrypted message with a string of 5 numbers, ranging from 0 to 30 separated by commas. The attachment must be unencrypted for rewards to be publicly auditable using the blockchain.
The app makes a request to the NXT server to fetch all transactions from the lottery account, sorts through them, selects only valid transactions and creates a slice of maps (multidimensional array in PHP) of players’ accounts and their strings of numbers. It also calculates the total sum of NXT payable to the players from the sums of all valid tickets.
Upon receiving all the valid data the app runs three rounds of the lottery. Each round receives a portion of the total payable sum, split between concurrent winners. In the round of 5, the app finds user(s) who have correctly guessed 5 numbers, and sends out rewards. In the round of 4, the app does the same for users who have guessed 4 numbers, the slice of participating tickets is now short of winner(s) of round of 5. Rinse and repeat for the round of 3.
This is the gist of it.
A little bit on internals
For each of the three rounds the lottery generates sequences of 5 numbers and compares them to the strings of numbers in tickets until one or more winners are found. It can be said that the lottery “forces” the winning sequence onto ticket(s).
With a limited number of users this seems to be the only sensible way of running a lottery and not having to collect and keep a large jackpot for months and years.
Let’s take a look at the function that generates sequences of 5 numbers and returns an array of them to caller function. On average, this function is called hundreds of thousands of times to find the sequence of 5 matching one of the tickets, when we have a very limited number of participants. It takes fractions of a second. In PHP it takes a tiny bit longer (a second or two), although PHP 7 performance is really good.
func genFive(seed string) [5]int { var r [5]int seedInt, _ := strconv.Atoi(seed) d := false for a := offset; a < offset+5; a++ { rand.Seed(int64(seedInt + offset)) var dup [31]int d = false r[0] = rand.Intn(31) r[1] = rand.Intn(31) r[2] = rand.Intn(31) r[3] = rand.Intn(31) r[4] = rand.Intn(31) for _, v := range r { dup[v]++ } for k, _ := range dup { if dup[k] > 1 { d = true } } offset = offset + 5 if d == false { return r } } return r }
An important characteristic of a blockchain lottery app is: it must be completely trust-free.
Everybody must be able to validate that results of the lottery haven’t been gamed. A logical and simple solution to this is to generate sequences of numbers with a deterministic seed.
The problem with deterministic seeds: if they are known in advance, sequences of numbers can be predicted and the lottery can be gamed. To address this problem we turn to the Nxt blockchain again, to find a source of seed with a getSeed() function.
func getSeed() (string, string) { type BlockchainStatus struct { NumberOfBlocks int `json:"numberOfBlocks"` } var status BlockchainStatus if seedBlockOutput, b := sendQuery("requestType=getBlockchainStatus", true); b != false { if err := json.Unmarshal([]byte(seedBlockOutput), &status); err != nil { fmt.Println(err) } } seedBlockHeight := strconv.Itoa(status.NumberOfBlocks - 11) type BlockId struct { Block string `json:"block"` } var block BlockId if seedBlockId, b := sendQuery("requestType=getBlockId&height=" +seedBlockHeight, true); b != false { if err := json.Unmarshal([]byte(seedBlockId), &block); err != nil { fmt.Println(err) } } seed := block.Block[len(block.Block)-5:] return seed, seedBlockHeight }
The app runs at 18:00 UTC on Sunday.
The first thing it does in the getSeed() function is to fetch the block id of the block which was generated 10 blocks before the app started (as seen in a local copy of the blockchain on the lottery node) and take the 5 last digits of the block id as a seed. Due to network latency and occasional blockchain reorganizations of 1-3 blocks, the lottery node may not see the same block as other nodes. The number 10 for getting the block for seed was chosen to be reasonably sure that block would not be reorganized.
It can be argued that there is a theoretical possibility that the block id is predictable. The odds of this are tiny in my opinion but I’ll leave it to the readers to debate and decide.
Now that the app has its seed, it can do its job in a way so users don’t need to trust the lottery host.
The Go source code doesn’t include the routine to verify past results.
The PHP source code does have it, it is fully functional and can be used to independently verify all past results with the deterministic seeds from the blockchain.
For Go I use this function to send queries to the Nxt Server and return results.
func sendQuery(Query string, Active bool) (output string, b bool) { output = "" b = false if Active == false { output = "Function disabled" return } body := strings.NewReader(Query) req, err := http.NewRequest("POST", "http://127.0.0.1:7876/nxt", body) if err != nil { output = fmt.Sprintf("%s", err) return } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := http.DefaultClient.Do(req) if err != nil { output = fmt.Sprintf("%s", err) return } bo, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() output = fmt.Sprintf("%s", bo) match, _ := regexp.MatchString(".*errorDescription.*", output) if match == true { fileHandle, _ := os.OpenFile("./error.log", os.O_APPEND, 0666) writer := bufio.NewWriter(fileHandle) defer fileHandle.Close() fmt.Fprintln(writer, output) writer.Flush() return } b = true return }
The results are returned as a JSON string and need to be unmarshaled into proper structs.
validPlayers := make([]map[string]string, 0) lotteryAccount := "NXT-YXC4-RB92-F6MQ-2ZRA6" type Attachment struct { Message string `json:"message"` MessageIsText bool `json:"messageIsText"` } type Transaction struct { Timestamp int `json:"timestamp"` AmountNQT string `json:"amountNQT"` ID string `json:"transaction"` SenderRS string `json:"senderRS"` RecipientRS string `json:"recipientRS"` Attached Attachment `json:"attachment"` } type Response struct { Transactions []Transaction `json:"transactions"` } Query := "requestType=getBlockchainTransactions&account=" + lotteryAccount + "&type=0&subtype=0&executedOnly=true" if v, a := sendQuery(Query, true); a == true { var transactions Response if err := json.Unmarshal([]byte(v), &transactions); err != nil { fmt.Println(err) } p := 0 for k, _ := range transactions.Transactions { // code to check tickets for validity. // if transaction satisfies all criteria // add it to the slice of valid tickets. validPlayers = append(validPlayers, make(map[string]string)) validPlayers[p][txSender] = lotteryNumbers p++ } }
Now that ‘validPlayers’ has all the good tickets we can start the game.
process() receives an integer (5, 4, or 3) and other parameters, including validPlayers and runs three rounds of the lottery. It makes a call to getWinners() function, that one calls genFive() to generate sequences of numbers until at least one winner is found. getWinners() returns results to process() which sends out reward, removes the winner ticket from eligible tickets and returns remaining tickets into main() for subsequent rounds. There is a helper function preparePlayers() which recreates validPlayers without the empty spots freed by the removed tickets.
I encourage every programmer to try coding on the Nxt blockchain. It is very easy with its rich API hooking into all the functionality of the core engine. https://nxtwiki.org/wiki/The_Nxt_API
My next app will probably be a poll app, with immutable records of votes saved into the blockchain. Do you think an app like that can find usage in the modern world? By the way, Nxt has its own built-in Voting. It’s too easy to forget what Nxt has, because it has so many features and all those features are accessible through API, kindly programmed by the core developers for userland. You can ‘mine’ your first NXT coins to send transactions in the Lucky node project running a public node, come to nxtforum.org and you will find out how.
Please leave your comments and suggestions.
2.
To access Nxt Data Cloud, download and install the NRS (Nxt Reference Software 1.10.1) and search by ‘lottery’ keyword. Source code can also be downloaded from any of the public Open API Nxt servers, for instance:
Go: http://23.94.134.161:7876/nxt?requestType=downloadTaggedData&transaction=7872865106538381099&retrieve=true
PHP: http://23.94.134.161:7876/nxt?requestType=downloadTaggedData&transaction=13031806327722095646&retrieve=true
BACK TO POST.