Skip to content

Fix coordinator lottery fairness#2507

Open
alicecoordinator wants to merge 1 commit into
RoboSats:mainfrom
alicecoordinator:fix-lottery-fairness
Open

Fix coordinator lottery fairness#2507
alicecoordinator wants to merge 1 commit into
RoboSats:mainfrom
alicecoordinator:fix-lottery-fairness

Conversation

@alicecoordinator
Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes the fairness in the coordinator lottery.

The goal of the lottery was to give each coordinator a chance to be the first in the coordinator list proportional to their donation to the dev fund, but the current logic doesn't do that.

It fails because using random inside the comparison function ends up favoring the elements in specific positions in the initial order.

For example:

const defaultFederation = {
  "temple": {
    "shortAlias": "temple",
    "badges": {
      "donatesToDevFund": 30,
    },
  },
  "lake": {
    "shortAlias": "lake",
    "badges": {
      "donatesToDevFund": 30,
    },
  },
  "veneto": {
    "shortAlias": "veneto",
    "badges": {
      "donatesToDevFund": 20,
    },
  },
  "moon": {
    "shortAlias": "moon",
    "badges": {
      "donatesToDevFund": 33,
    },
  },
  "bazaar": {
    "shortAlias": "bazaar",
    "badges": {
      "donatesToDevFund": 30,
    },
  },
  "freedomsats": {
    "shortAlias": "freedomsats",
    "badges": {
      "donatesToDevFund": 20,
    },
  },
  "whiteyesats": {
    "shortAlias": "whiteyesats",
    "badges": {
      "donatesToDevFund": 20,
    },
  },
  "alice": {
    "shortAlias": "alice",
    "badges": {
      "donatesToDevFund": 20,
    },
  }
}

export default function federationLottery(): string[] {
  // Create an array to store the coordinator short aliases and their corresponding weights (chance)
  const coordinatorChance: Array<{ shortAlias: string; chance: number }> = [];

  // Convert the `federation` object into an array of {shortAlias, chance}
  Object.values(defaultFederation).forEach((coor) => {
    const chance = coor.badges.donatesToDevFund > 50 ? 50 : coor.badges?.donatesToDevFund;
    coordinatorChance.push({ shortAlias: coor.shortAlias, chance });
  });

  // Sort randomly the coordinatorChance array using weighted shuffling algorithm
  const shuffledCoordinators = coordinatorChance.sort((a, b) => {
    return Math.random() * b.chance - Math.random() * a.chance;
  });

  // Extract the coordinator names from the shuffled array and return the result
  const sortedCoordinators = shuffledCoordinators.map((coordinator) => coordinator.shortAlias);

  return sortedCoordinators;
}

// run lottery 100 times, and count the number of times each coordinator is selected as the winner
const winnerCount: Record<string, number> = Object.fromEntries(Object.keys(defaultFederation).map((key) => [key, 0]));
for (let i = 0; i < 10000; i++) {
  const winners = federationLottery();
  const winner = winners[0]; // Get the first coordinator as the winner
  winnerCount[winner]++;
}

console.log(winnerCount);

With the configuration above (a reduced version of the actual federation.json), the results are always close to this:

{
  temple: 2827,
  lake: 1498,
  veneto: 466,
  moon: 2411,
  bazaar: 1329,
  freedomsats: 417,
  whiteyesats: 493,
  alice: 559
}

Notice how "temple" comes way more often as the first, even though it has the same contribution as "lake" and "bazaar". Also, even though "moon" has a slightly higher contribution than "temple" it still gets first less often.

With the new implementation the results are as follows:

{
  temple: 1459,
  lake: 1425,
  veneto: 1005,
  moon: 1637,
  bazaar: 1469,
  freedomsats: 1029,
  whiteyesats: 1002,
  alice: 974
}

Notice how "moon" got a slight advantage now, and how the chances are actually proportional to the contributions: "temple", "lake" and "bazaar" got first roughly 50% more often than "veneto", "freedomsats", "whiteyesats", and "alice", because the first group contributes 50% more than the second group (30% versus 20%).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant