diff --git a/.gitignore b/.gitignore index f2cc188..ef724bb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ *.out .idea/ + +node_modules/ \ No newline at end of file diff --git a/drandexample/README.md b/drandexample/README.md new file mode 100644 index 0000000..2581867 --- /dev/null +++ b/drandexample/README.md @@ -0,0 +1,24 @@ +# drandexample +Example on how to use Drand as a client + +# What, Why, How? +Created a small script to show & describe how to get random numbers from the drand network. + +Example: Imagine you are a full time L5 software engineer and have more important things to think about than what to get for lunch. + You decide to leave it up to randomness to choose your next meal. But you still have preferences. + You assign weights to your preferences such that items you would like to eat most often have heavier weights (chances of being chosen) + And things you don't want to eat as often, have smaller probability of being chosen. + +Things to note: + Drand mainnet releases a random number every 30 seconds. The problem that arises is if you want to test if the biased randomness works or not, it would take a really long time to test. + There is work to shorten this time frame to 3 seconds, which is better, but its not as convenient as instant access of psuedo-random numbers like math.random() or crypto.getRandomValues(). + +This script shortens down a `randomness` value from the network and uses it as a probability for selecting an item. + +### Requirements +Node 12 and up + +# Run +1. `npm install` + +2. `node bias.js` diff --git a/drandexample/bias.js b/drandexample/bias.js new file mode 100644 index 0000000..04ab935 --- /dev/null +++ b/drandexample/bias.js @@ -0,0 +1,70 @@ +import Client, { HTTP } from 'drand-client'; +import fetch from 'node-fetch'; +import AbortController from 'abort-controller'; + +global.fetch = fetch; +global.AbortController = AbortController; + +const chainHash = '8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce'; // Mainnet chain hash (hex encoded) +const urls = [ + 'https://api.drand.sh', + 'https://drand.cloudflare.com' +]; // various endpoint options to access Drand network. + +const HEX = 16; +const FoodOptions = { "pho": 0.3, "croquets": 0.29, "pizza": 0.28, "pasta": 0.07, "molé_verde": 0.03, "shrimp": .03 }; + +// This function takes in a list of items and the probablilty of them being selected. +// returns the number that is randomly selected +async function weightedRandom(prob) { + if (validateWeights(prob) != 1) { + return "Weights not equal to 1" + } + const options = { chainHash }; + + const client = await Client.wrap(HTTP.forURLs(urls, chainHash), options); + + // e.g. use the client to get the latest randomness round: + const res = await client.get(); + + // Get a number between 0 and 100 from Drand. + const rand = randomPercentFrom(res.randomness); + + // This for loop selects which key:pair to return + let sum = 0, r = rand; + for (let [key, value] of Object.entries(prob)) { + sum += value * 100; + if (r <= sum) { + return key; + } + } +} + +//runs code above +weightedRandom(FoodOptions).then((lunch) => (console.log(lunch))) +// Press Ctrl + C to stop example! + +// This function returns a number between 0 & 99. +function randomPercentFrom(randomness) { + var i = 0; + var randomDecimal = -1; + var rand; + + while (randomDecimal <= 0 || randomDecimal > 100) { + // Grab only 2 digits from randomness, in a "sliding window" fashion + rand = randomness.slice(0 + i, 2 + i); + // Convert hexadecimal randomness value to decimal (base16 -> base10) + randomDecimal = parseInt(rand, HEX); + i++; + } + return randomDecimal; +} + +// This function checks the user input to make sure all probabilities add up to 1 +function validateWeights(probabilities) { + var sum = 0; + for (let [_, value] of Object.entries(probabilities)) { + sum += value; + } + return sum; +} \ No newline at end of file diff --git a/drandexample/package-lock.json b/drandexample/package-lock.json new file mode 100644 index 0000000..af08444 --- /dev/null +++ b/drandexample/package-lock.json @@ -0,0 +1,189 @@ +{ + "name": "drandexample", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "drandexample", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "drand-client": "^0.2.0", + "node-fetch": "^3.2.10" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/drand-client": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/drand-client/-/drand-client-0.2.0.tgz", + "integrity": "sha512-0L+X8rm1XCN908cIOYXAauXyBK32zf7y0cGLcoZVaMQmuZwbuqcVrrvQCOgh3qjptYuvnEFncMGpKf3p/7YjFw==" + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + } + }, + "dependencies": { + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" + }, + "drand-client": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/drand-client/-/drand-client-0.2.0.tgz", + "integrity": "sha512-0L+X8rm1XCN908cIOYXAauXyBK32zf7y0cGLcoZVaMQmuZwbuqcVrrvQCOgh3qjptYuvnEFncMGpKf3p/7YjFw==" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + } + } + } + \ No newline at end of file diff --git a/drandexample/package.json b/drandexample/package.json new file mode 100644 index 0000000..3223ad7 --- /dev/null +++ b/drandexample/package.json @@ -0,0 +1,21 @@ +{ + "name": "drandexample", + "version": "1.0.0", + "type": "module", + "description": "Example on how to use Drand", + "main": "bias.js", + "scripts": { + "test": "node bias.js" + }, + "keywords": [ + "drand", + "example" + ], + "author": "Marco Rodriguez-Salinas", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "drand-client": "^0.2.0", + "node-fetch": "^3.2.10" + } + } \ No newline at end of file