Hi,
we promised a detailed technical explanation of how we pick the raffle ticket winner and here it is. In English, the following steps are performed:
1) We retrieve the elegible raffle tickets (based on user submit-% setting) and insert them into a table which holds submitted raffle tickets for each user for the given month. Tickets are inserted in blocks and the table structure looks like this (the data for this example is made-up):
|Year |Month|UserID|StartNum|EndNum|
|2015| 06 | 123 | 1 | 123 |
|2015| 06 | 675 | 124 | 768 |
|2015| 06 | 12 | 768 | 2212 |
2) We then take the block hash of the first block mined after the beginning of the new month (after 00:00:00 GMT):
00000000000000001437607ffa24583aa964f84579254570d6b25b4fe65d658f
3) We calculate the product of all non-zero hexadecimal digits until we get a number greater than 10 Million. For the hash displayed in #2 this is:
15876000
4) We take the number of submitted raffle tickets (31208 for June) and use this in a modulus operation like this:
WinningTicket# = HashProduct % SubmittedRaffleTickets
which comes to
15876000 % 31208 = 22336
So for this example, the winning ticket number is 22336. Once we have this number we look up the submitted tickets blocks from step #1 and find the user for who 'owns' the ticket block into which the winning ticket falls.
This is the explanation in plain english.
The actual code which does this is below:
public static function submitRaffleTickets ()
{
$tables = $GLOBALS['tables'];
$dbConn = $GLOBALS['dbConn'];
$pagesize = 500;
$month = date ('m') - 1;
$year = date ('Y');
$page = 0;
$cUsers = 0;
$cRt = 0;
$stmt = $dbConn->query ("SELECT COUNT(1) FROM $tables[rt_used] WHERE month = $month");
$stmt->bindValue (':month', $month, PDO::PARAM_INT);
$stmt->bindValue (':month', $month, PDO::PARAM_INT);
$row = $stmt->fetch (PDO::FETCH_NUM);
if ($row && $row[0]) {
die ("Raffle Tickets for month [$month] already exist ... exiting");
}
do {
$offset = $pagesize * $page;
$stmt = $dbConn->query ("SELECT id, alias, rt_redeem_percent, total_bets_rt FROM $tables[users] WHERE rt_redeem_percent != 0 AND total_bets_rt != 0 ORDER BY alias LIMIT $offset, $pagesize");
$rows = $stmt->fetchAll (PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$amt = (int)(($row['total_bets_rt']/$GLOBALS['hconf']['raffleTicketTurns']) * ($row['rt_redeem_percent']/100));
if ($amt == 0) {
continue;
}
$stmt = $dbConn->prepare ("INSERT INTO $tables[rt_used] (uid, year, month, amt, start_num, end_num) VALUES (:uid, :year, :month, :amt, :startNum, :endNum)");
$stmt->bindValue (':uid', $row['id'], PDO::PARAM_INT);
$stmt->bindValue (':year', $year, PDO::PARAM_INT);
$stmt->bindValue (':month', $month, PDO::PARAM_INT);
$stmt->bindValue (':amt', $amt, PDO::PARAM_INT);
$stmt->bindValue (':startNum', $cRt+1, PDO::PARAM_INT);
$stmt->bindValue (':endNum', $cRt+$amt, PDO::PARAM_INT);
$stmt->execute ();
$stmt = $dbConn->prepare ("UPDATE $tables[users] SET total_bets_rt = total_bets_rt - :amt WHERE id = $row[id]");
$stmt->bindValue (':amt', ($amt*$GLOBALS['hconf']['raffleTicketTurns']), PDO::PARAM_INT);
$stmt->execute ();
$cUsers++;
$cRt += $amt;
}
$page++;
} while (count($rows) > 0);
echo sprintf ("Submitted %d RaffleTickets for %d users", $cRt, $cUsers);
}
The function to calculate the winning ticket looks like this:
public static function getWinningRaffleTicketForHash ()
{
$hash = DiceUtil::getPassedValue ('hash', null, 'GET');
$count = (int)DiceUtil::getPassedValue ('count', 0, 'GET');
$min = 1000*1000*10;
$sum = 0;
$off = 0;
if (!$hash) {
exit ('Invalid [hash] recieved');
}
if (!$count) {
exit ('Invalid raffle ticket [count] recieved');
}
if (!ctype_xdigit($hash)) {
exit ('Invalid [hash] characters recieved');
}
do {
$c = $hash[$off++];
if (!$sum) {
$sum = hexdec($c);
} elseif ($c) {
$sum *= hexdec($c);
}
} while ($sum < $min && $off < 30);
$winner = $sum%$count;
print "Hash = $hash
";
print "Product (> 10M) = $sum
";
print "Winning Ticket = $winner
";
}
Hopefully this assures everybody that the process of picking the raffle ticket winner is a transparent and fair one.
We will be adding a page to the site where the raffle ticket assignments and past winners will be listed; expect this page to be added to the site within a few days.
Lastly, we would like to announce a policy change: While we still require 500 qualified rolls to earn a raffle ticket, from now on (the new code is already live) only bets larger than 100 Satoshi will be counted towards raffle tickets. Furthermore, depending on bet amount, rolls will be counted towards raffle tickets as follows:
1) Bets > 1 BTC count as 2500 rolls towards a raffle ticket (ie: 5 raffle tickets per 1 BTC roll)
2) Bets > 0.1 BTC count as 250 rolls towards a raffle ticket (1/2 raffle ticket)
3) Bets > 0.001 BTC count as 25 rolls towards a raffle ticket (1/20th raffle ticket)
4) Bets > 0.0001 BTC count as 10 rolls towards a raffle ticket (1/50th raffle ticket)
5) Bets > 0.000001 BTC count as 1 roll towards a raffle ticket (1/500th raffle ticket)
6) Bets < 0.000001 BTC do not count towards a raffle ticketThe reasoning for this is as follows:
1) We wish to discourage people betting 1 satoshi bets all day long. If you wish to bet 1 satoshi bets, that's fine. But if your only purpose in doing so is to earn raffle tickets, then this is not fair towards users who bet larger amounts.
2) We wish to reward players who bet higher amounts and give them a proportionally greater chance at winning the raffle.
In light of these changes, we converted raffle-tickets which were not submitted to 10% of the leftover amount, so you had 1000 raffle tickets left after after the last draw, you now have 100 raffle tickets left. Had we not done this, this statistics would have been very badly skewed against newly earned raffle tickets. Given the new cutoff value of 100 Satoshi bets for raffle ticket bets, a conversion of 10% seemed the most fair and balanced one we could come up with.
Lastly, raffle ticket winners have until the next draw to claim their prize; after that they forfeit their right to the prize!