import React from 'react'; import Grid from '@material-ui/core/Grid'; import { Theme, withStyles, StyleRules } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; import Button from '@material-ui/core/Button'; import Slider from '@material-ui/lab/Slider'; import FormGroup from '@material-ui/core/FormGroup'; import Typography from '@material-ui/core/Typography'; import Link from '@material-ui/core/Link'; import { Line } from 'react-chartjs-2'; import Color from 'color'; import Matrix, { getNodeColor } from './Matrix'; const styles = (theme: Theme): StyleRules => ({ inputLabel: { fontSize: 16, paddingRight: 0, textAlign: 'right', width: '30%' }, inputValue: { textAlign: 'left' }, buttonSpacer: { marginBottom: theme.spacing.unit * 4, }, bottomButtons: { marginTop: 10, textAlign: 'center', }, slider: { padding: '22px 0px', }, errorHint: { fontSize: 16, paddingLeft: 16, lineHeight: '32px', color: theme.palette.secondary.main }, grid: { textAlign: 'center' } }); interface SnowProps { classes: { inputLabel: string, inputValue: string, buttonSpacer: string, bottomButtons: string, slider: string, errorHint: string, grid: string } } function getRandomInt(max: number) { return Math.floor(Math.random() * Math.floor(max)); } /* Code from: https://stackoverflow.com/a/11935263/544806 */ function getRandomSubarray(arr: number[], size: number) { let shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index; while (i-- > min) { index = Math.floor((i + 1) * Math.random()); temp = shuffled[index]; shuffled[index] = shuffled[i]; shuffled[i] = temp; } return shuffled.slice(min); } const watchedD = [15, 10, 5, 1]; class Snow extends React.Component { static genMatrix(n: number) { let d = []; for (let i = 0; i < n; i++) { let r = []; for (let j = 0; j < n; j++) r.push({ d: [0, 0], col: getRandomInt(2) }); d.push(r); } return d; } state = { colorMatrix: Snow.genMatrix(20), n: '20', k: '10', alpha: '8', nodesPerTick: '20', maxInactiveTicks: '200', loaded: true, ticking: false, simulationSpeed: 100, dialogOpen: false, dialogMsg: { title: '', message: '' }, nError: false, kError: false, alphaError: false, nodesPerTickError: false, maxInactiveTicksError: false, dcnts: [watchedD.map(() => [] as number[]), watchedD.map(() => [] as number[])] as number[][][], ticks: [] as number[], N: 400 }; config = { iter: 0, n: 20, k: 10, alpha: 8, nodesPerTick: 10, maxInactiveTicks: 200, inactiveTicks: 0 }; getNodeState(n: number, u: number) { let r = Math.floor(u / n); let c = u % n; return this.state.colorMatrix[r][c]; } setNodeState(n: number, u: number, s: { d: number[], col: number }) { let r = Math.floor(u / n); let c = u % n; let m = [...this.state.colorMatrix]; m[r][c] = s; this.setState({ colorMatrix: m }); } tick(n: number, m: number) { let N = n * n; let active = false; let nodes = []; for (let j = 0; j < N; j++) nodes.push(j); getRandomSubarray(nodes, m).forEach(v => { let u = getRandomInt(N); let peers = []; for (let j = 0; j < N; j++) if (j != u) peers.push(j); let sample = getRandomSubarray(peers, this.config.k); let cnt = [0, 0]; sample.forEach((v) => { let ss = this.getNodeState(n, v); cnt[ss.col]++; }); let s = this.getNodeState(n, u); for (let c = 0; c < 2; c++) { if (cnt[c] >= this.config.alpha) { s.d[c]++; if (s.d[c] > s.d[s.col]) { if (s.col != c) active = true; s.col = c; this.setNodeState(n, u, s); } } } }); if (this.config.iter % 10 == 0) { let dcnts = []; for (let c = 0; c < 2; c++) { dcnts.push(watchedD.map((d, i) => { let dcnt = 0; for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) { const s = this.state.colorMatrix[i][j]; if (s.d[c] >= d) dcnt++; } if (c == 0) dcnt = -dcnt; return [...this.state.dcnts[c][i], dcnt].splice(-50); })); } this.setState({ dcnts: dcnts, ticks: [...this.state.ticks, this.config.iter].splice(-50) }); } return active; } pauseTick() { this.setState({ ticking: false }); } startTick() { const n = Number(this.state.n); const N = n * n; const k = Number(this.state.k); const alpha = Number(this.state.alpha); const nodesPerTick = Number(this.state.nodesPerTick); const maxInactiveTicks = Number(this.state.maxInactiveTicks); if (!Number.isInteger(n) || n < 2 || n > 40) { this.setState({ nError: true }); return; } if (!Number.isInteger(k) || k < 1 || k >= N) { this.setState({ kError: true }); return; } if (!Number.isInteger(alpha) || !(k / 2 < alpha && alpha <= k)) { this.setState({ alphaError: true }); return; } if (!Number.isInteger(nodesPerTick) || nodesPerTick < 1 || nodesPerTick > N) { this.setState({ nodesPerTickError: true }); return; } if (!Number.isInteger(maxInactiveTicks) || maxInactiveTicks < 1 || maxInactiveTicks > 1e6) { this.setState({ maxInactiveTicksError: true }); return; } if (!this.state.loaded) { this.config.iter = 0; this.config.n = n; this.setState({ loaded: true, colorMatrix: Snow.genMatrix(this.config.n), dcnts: [watchedD.map(() => [] as number[]), watchedD.map(() => [] as number[])], ticks: [], N: n * n }); } this.config.alpha = alpha; this.config.k = k; this.config.nodesPerTick = nodesPerTick; this.config.inactiveTicks = 0; this.config.maxInactiveTicks = maxInactiveTicks; this.autoTick(); } autoTick() { this.setState({ ticking: true }); setTimeout(() => { let active = this.tick(this.config.n, this.config.nodesPerTick); this.config.iter++; if (!active) { if (++this.config.inactiveTicks > this.config.maxInactiveTicks) { this.pauseTick(); return; } } else this.config.inactiveTicks = 0; if (this.state.ticking) this.autoTick(); }, 1000 / this.state.simulationSpeed); } reset() { this.setState({ ticking: false, loaded: false }); } flipNode(i: number, j: number) { let s = this.state.colorMatrix[i][j]; const n = this.config.n; s.col = 1 - s.col; this.setNodeState(n, i * n + j, s); } render() { const { classes } = this.props; return ( this.flipNode(i, j)} onHoverNode={(i: number, j: number) => this.flipNode(i, j)} />
{ let datasets = this.state.dcnts.map((dd, c) => dd.map((line, i) => { const base = getNodeColor(watchedD[i], c); return { data: line, label: `${c == 0 ? 'A' : 'B'}(d-${watchedD[i]})`, borderColor: base, backgroundColor: Color(base).fade(0.5).rgb().string(), borderWidth: 2 }; })).flat(); return { datasets, labels: this.state.ticks } }} options={{ scales: { yAxes: [{ ticks: { min: -this.state.N, max: this.state.N }}]}, maintainAspectRatio: false }} />
This demo shows the Snowball protocol used as the core of a peer-to-peer payment system, Avalanche, introduced in  this paper  . It visualizes the process of a binary, single-decree, probabilistic Snowball consensus that harnesses metastability to guarantee safety. Little squares represent different nodes, wherein the color of each square represents its current proposal. Darkness of the color shows the node's conviction in that proposal. Expectedly, all nodes will collapse to the same color in the end. Try to click or move the mouse when clicked to flip the color of squares. Are you able to prevent them from going to a single color? n = this.setState({ n: event.target.value, nError: false })} /> 2 {this.state.nError && n must be in 2..40} k = this.setState({ k: event.target.value, kError: false })}/> {this.state.kError && k must be in 1..(n-1)} alpha = this.setState({ alpha: event.target.value, alphaError: false })}/> {this.state.alphaError && alpha must be in (k/2, k]} nodesPerTick = this.setState({ nodesPerTick: event.target.value, nodesPerTickError: false })}/> {this.state.nodesPerTickError && nodesPerTick must be in 1..n} maxInactiveTicks = this.setState({ maxInactiveTicks: event.target.value, maxInactiveTicksError: false })}/> {this.state.maxInactiveTicksError && maxInactiveTicks must be in 1..1000000} simulationSpeed this.setState({ simulationSpeed: value })} />
); } } export default withStyles(styles)(Snow);