Skip to content

Instantly share code, notes, and snippets.

@lukepighetti
Created March 5, 2020 19:37
Show Gist options
  • Save lukepighetti/70f2e758831cee1cae8d72edf2787182 to your computer and use it in GitHub Desktop.
Save lukepighetti/70f2e758831cee1cae8d72edf2787182 to your computer and use it in GitHub Desktop.
A dead simple blockchain written in Dart in 93 lines
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