Created
March 5, 2020 19:37
-
-
Save lukepighetti/70f2e758831cee1cae8d72edf2787182 to your computer and use it in GitHub Desktop.
A dead simple blockchain written in Dart in 93 lines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:collection'; | |
import 'dart:convert' as convert; | |
import 'package:crypto/crypto.dart' as crypto; | |
import 'package:flutter_test/flutter_test.dart'; | |
main() { | |
testWidgets('Create, add, pass validate, tamper, fail validation', (_) async { | |
final blockchain = Blockchain(); | |
expect(blockchain.isValid, isTrue); | |
blockchain.addBlock("First"); | |
blockchain.addBlock("Second"); | |
expect(blockchain.isValid, isTrue); | |
blockchain._chain.removeAt(1); | |
expect(blockchain.isValid, isFalse); | |
}); | |
} | |
/// A dead simple blockchain sytem where each block carries a String payload | |
class Blockchain { | |
final List<Block> _chain = [Block.genesis()]; | |
/// Add a block to the top of the chain | |
String addBlock(String message) { | |
final block = Block(_chain.last.hash, message); | |
_chain.add(block); | |
return block.hash; | |
} | |
/// Compute if the chain is valid | |
bool get isValid { | |
/// The chain is valid if it only has a genesis block | |
if (_chain.length == 1) return true; | |
bool isValid = true; | |
_chain.pairwise((a, b) { | |
/// Given two adjacent blocks, the first block's computed hash value should | |
/// equal the stored _previousHash value on the second block. This is the link in the chain. | |
if (b._previousHash != a.hash) { | |
isValid = false; | |
} | |
}); | |
return isValid; | |
} | |
/// Get the current raw chain. | |
List<Block> get chain => UnmodifiableListView<Block>(_chain); | |
} | |
/// The entry must contain the previous block hash and its current value. | |
/// We can then compute it's hash on the fly. | |
class Block { | |
Block(this._previousHash, this.payload) : this.forgeDate = DateTime.now(); | |
/// A special constructor for the genesis block | |
Block.genesis() | |
: this._previousHash = "genesis", | |
this.payload = "genesis", | |
this.forgeDate = DateTime.now(); | |
/// The value to store in the block. In Bitcoin this would be a list of transactions, | |
/// for our purposes it will just be a string. | |
final String payload; | |
final DateTime forgeDate; | |
final String _previousHash; | |
/// To maintain chain integrity we roll the previous block hash into this block's hashed data. | |
String get hash => "$payload@$forgeDate via $_previousHash".sha256; | |
} | |
extension ListExtensions<E> on List<E> { | |
/// Like [forEach] but comparing two adjacent entries | |
void pairwise(void Function(E a, E b) f) { | |
for (int i = 0; i < this.length - 1; i++) { | |
f(this[i], this[i + 1]); | |
} | |
} | |
} | |
extension on String { | |
/// The UTF8 data list of this String | |
List<int> get utf8 => convert.utf8.encode(this); | |
/// The SHA256 hash of this String | |
String get sha256 => crypto.sha256.convert(this.utf8).toString(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment