Created
November 3, 2015 23:22
-
-
Save NicMcPhee/838ea3659eebd98cdb66 to your computer and use it in GitHub Desktop.
Distributed cryptographically fair coin toss in Elixir with multiple nodes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Person A flips and Person B chooses "heads" or "tails". | |
# Person B wins if their choice matches the flip, otherwise | |
# person A wins. | |
defmodule CoinToss do | |
def run_simulation(first_host, second_host, num_people) do | |
counterPid = spawn(__MODULE__, :counter_loop, [0, 0]) | |
:global.register_name(:counter, counterPid) | |
people = create_people(first_host, second_host, num_people) | |
pair_all(people) | |
end | |
def create_people(first_host, second_host, num_people) do | |
Enum.map 0..num_people-1, &(make_person(first_host, second_host, &1)) | |
end | |
def make_person(first_host, second_host, name) do | |
host = case :crypto.rand_uniform(0, 2) do | |
0 -> first_host | |
1 -> second_host | |
end | |
{ Node.spawn(host, __MODULE__, :wait, [name]), name } | |
end | |
def pair({firstPID, firstName}, {secondPID, secondName}) do | |
send(firstPID, {:start, secondPID, secondName}) | |
end | |
def pair_all([]) do true end | |
def pair_all([p, q | others]) do | |
pair(p, q) | |
pair_all(others) | |
end | |
def update_counter(choice, result) do | |
counterPid = :global.whereis_name(:counter) | |
send(counterPid, {choice, result}) | |
end | |
def getReport() do | |
counterPid = :global.whereis_name(:counter) | |
send(counterPid, {:report}) | |
end | |
def counter_loop(heads_wins, tails_wins) do | |
receive do | |
{:report} -> | |
IO.puts("#{heads_wins} heads wins and #{tails_wins} tails wins.\n") | |
{:heads, :won} -> | |
IO.puts("Called heads and won") | |
counter_loop(heads_wins+1, tails_wins) | |
{:tails, :lost} -> | |
IO.puts("Called tails and lost") | |
counter_loop(heads_wins+1, tails_wins) | |
{:heads, :lost} -> | |
IO.puts("Called heads and lost") | |
counter_loop(heads_wins, tails_wins+1) | |
{:tails, :won} -> | |
IO.puts("Called tails and won") | |
counter_loop(heads_wins, tails_wins+1) | |
end | |
end | |
def wait(myName) do | |
# We should be using the newer :rand module from Erlang, but it's not in | |
# the version in the lab. | |
# We need to see separately for each player since (apparently) every | |
# player's random number generator is in its own process space in Elixir. | |
:random.seed(:os.timestamp) | |
receive do | |
{:start, partnerID, partnerName} -> | |
IO.puts("Player #{myName} is flipping for Player #{partnerName}.") | |
# We've been asked to flip | |
# This would be nicer with the Elixir Enum.random([:heads, :tails]) | |
# but that's new and not in the versions we have in the lab. | |
flip = case :random.uniform(2)-1 do | |
0 -> :tails | |
1 -> :heads | |
end | |
IO.puts("The flip for Player #{myName} was #{flip}.") | |
{salt, hashed_flip} = hash_flip(flip) | |
send(partnerID, {:choose, self(), myName, hashed_flip}) | |
wait_for_choice(partnerID, partnerName, myName, flip, salt, hashed_flip) | |
{:choose, partnerID, partnerName, hashed_flip} -> | |
IO.puts("Player #{myName} is choosing for Player #{partnerName}.") | |
# The fib call is just to put some expensive computation in | |
# the system so we can see the parallelism on the system monitor. | |
# Feel free to remove this, or raise or lower the argument to change | |
# the amount of work being done here. | |
_ = fib(30+:random.uniform(10)) | |
# We've been asked to choose heads or tails | |
# This would be nicer with the Elixir Enum.random([:heads, :tails]) | |
# but that's new and not in the versions we have in the lab. | |
choice = case :random.uniform(2)-1 do | |
0 -> :tails | |
1 -> :heads | |
end | |
IO.puts("The choice for Player #{myName} was #{choice}.") | |
send(partnerID, {:check_outcome, self(), myName, choice, hashed_flip}) | |
wait_for_outcome(partnerID, partnerName, myName, choice, hashed_flip) | |
end | |
end | |
# This actor has flipped, and is waiting to receive their | |
# partner's choice. | |
def wait_for_choice(partnerID, partnerName, myName, flip, salt, hashed_flip) do | |
IO.puts("Player #{myName} is waiting for outcome from Player #{partnerName}.") | |
receive do | |
{:check_outcome, ^partnerID, ^partnerName, choice, ^hashed_flip} -> | |
IO.puts("Player #{myName} recieved :check_outcome from #{partnerName}.") | |
# The result here is from the perspective of the chooser | |
# not the flipper. | |
result = case flip == choice do | |
true -> :won | |
false -> :lost | |
end | |
IO.puts("Player #{myName} checked: Flip was #{flip}, choice was #{choice}, result was #{result}") | |
send(partnerID, {:confirm_result, myName, result, flip, salt, hashed_flip}) | |
end | |
end | |
# This actor has chosen, and waiting to receive and confirm the result. | |
def wait_for_outcome(partnerID, partnerName, myName, choice, hashed_flip) do | |
IO.puts("Player #{myName} is waiting for the result from Player #{partnerName}.") | |
receive do | |
{:confirm_result, ^partnerName, result, flip, salt, ^hashed_flip} -> | |
IO.puts("Player #{myName} received :confirm_result message from #{partnerName}.") | |
case (choice == flip) == (result == :won) do | |
true -> true | |
false -> exit("Player #{partnerName} didn't process toss correctly") | |
end | |
{^salt, hash_check} = hash_flip(salt, flip) | |
case hash_check do | |
hashed_flip -> true; | |
_ -> exit("Player #{partnerName}'s hashed flip didn't match") | |
end | |
IO.puts("Processing choice #{choice} and result #{result}") | |
update_counter(choice, result) | |
end | |
end | |
defp hash_flip(flip) do | |
salt = to_string(:crypto.rand_uniform(0, 1000000)) | |
hash_flip(salt, flip) | |
end | |
defp hash_flip(salt, flip) do | |
{salt, :crypto.md5(salt <> to_string(flip))} | |
end | |
defp fib(0) do 0 end | |
defp fib(1) do 1 end | |
defp fib(n) do fib(n-1) + fib(n-2) end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment