-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathblockchain.tsx
154 lines (128 loc) · 3.7 KB
/
blockchain.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import React, {
useState,
useEffect,
useContext,
useRef,
createContext,
useReducer
} from 'react';
// @ts-ignore
import Miner from './miner.worker';
import ec from './curve';
import DataContext from './DataProvider';
import {
blockchainReducer,
genesisBlock,
hashVote,
getLongestChain
} from './chainUtils';
import useNetwork from './useNetwork';
import { auth } from './firebase';
//====================
// Interfaces
//====================
export interface VoteInfo {
to: string;
electionId: string;
position: string;
}
export interface Vote extends VoteInfo {
from: string;
timestamp: number;
signature: string;
}
export interface Block {
hash: string;
previousHash: string;
nonce: number;
vote: Vote;
}
//====================
// Context
//====================
type ContextProps = {
blockchain: Block[];
miningQueue: Vote[];
castVote: Function;
peersCount: number;
};
const BlockchainContext = createContext<Partial<ContextProps>>({});
export default BlockchainContext;
//====================
// Provider
//====================
export const BlockchainProvider = ({ children }) => {
const { currentUser, users, elections } = useContext(DataContext);
const [miningQueue, setMiningQueue] = useState<Array<Vote>>([]);
const [isMining, setIsMining] = useState(false);
const [blockchain, dispatch] = useReducer(blockchainReducer, [], () => {
const localChain = localStorage.getItem('blockchain');
if (localChain) return JSON.parse(localChain);
else return [genesisBlock];
});
const chainPreviousHashRef = useRef(genesisBlock.hash);
const [peers, sendVote, sendBlock] = useNetwork(
(vote: Vote) => setMiningQueue(currentQueue => [...currentQueue, vote]),
(blocks: Block[]) =>
dispatch({ value: blocks, context: { users, elections } })
);
//====================
// Side Effects
//====================
// Update new peers with the current local blockchain
useEffect(() => {
var localChain = localStorage.getItem('blockchain') || '[]';
sendBlock(JSON.parse(localChain));
}, [peers]);
// Update the local blockchain cache
// Update previous hash for next block,
useEffect(() => {
window.localStorage.setItem('blockchain', JSON.stringify(blockchain));
chainPreviousHashRef.current = getLongestChain(blockchain)[0].hash;
}, [blockchain]);
/**
* Listen to mining queue
* Mine votes one by one
* Broadcast votes to network
*/
useEffect(() => {
if (miningQueue.length === 0 || isMining) return;
const miner = new Miner();
const transaction = miningQueue[0];
miner.postMessage({
vote: transaction,
previousHash: chainPreviousHashRef.current
});
setIsMining(true);
miner.addEventListener('message', ({ data: block }: { data: Block }) => {
// Broadcast to network that this is a new block
sendBlock([block]);
setMiningQueue(miningQueue.splice(1, miningQueue.length));
setIsMining(false);
});
}, [miningQueue, isMining]);
return (
<BlockchainContext.Provider
value={{
blockchain: getLongestChain(blockchain),
miningQueue,
peersCount: Object.keys(peers).length,
castVote(partialVote: VoteInfo) {
const key = ec.keyFromPrivate(currentUser.privateKey, 'hex');
const vote: Vote = {
...partialVote,
timestamp: Date.now(),
from: auth.currentUser.uid,
signature: ''
};
// Sign the vote with the user's private key
const voteHash = hashVote(vote);
vote.signature = key.sign(voteHash, 'base64').toDER('hex');
return sendVote(vote);
}
}}
>
{children}
</BlockchainContext.Provider>
);
};