updated micro:coin

This commit is contained in:
Peli de Halleux 2018-03-02 06:41:04 -08:00
parent 614c3b0950
commit c1fd464cb5

View File

@ -27,300 +27,14 @@ Build yourself a [@boardname@ wallet](/projects/wallet) to hold your coins!
## ~ ## ~
## Full source code ## Code
**JavaScript only!** This program uses features that won't work in blocks... The code uses blocks from [radio-blockchain](https://makecode.microbit.org/pkg/microsoft/pxt-radio-blockchain) package.
```typescript * Click on **Advanced**, then **Add Package**
/* * search for **blockchain** and add **radio-blockchain**
* micro:coin, A minimalistic blockchain for micro:bit
*
* DISCLAIMER: this is just an example.
*
*/
/**
* Message types sent over radio.
*/
enum Message {
// ask peer to get the full chain
QueryChain = 1,
// a block flying around
Block = 2
}
/**
* A block is an immutable (can't change it) piece of a block chain.
* The block chain is like a list where each block is built from
* the previous block.
*/
class Block {
// index in the chain
index: number;
// timestamp on the device when the block was created
timestamp: number;
// in this implementation, data is the device serial number
data: number;
// hash of the previous block as a single unsigned byte
previousHash: number; // uint8
// hash of the current block as a single unsigned byte
hash: number; // uint8
/**
* Construct the block and computes the hash
*/
constructor(
index: number,
timestamp: number,
data: number,
previousHash: number) {
this.index = index;
this.timestamp = timestamp;
this.data = data;
this.previousHash = previousHash;
this.hash = this.computeHash();
}
/**
* Compute the hash of the current block
*/
computeHash() {
let s = "" + this.index + this.timestamp + this.data + this.previousHash;
/**
* This function takes a string and hashes it into a number. It simply takes the sum of characters,
* it's not great but will work for a super-simple example.
*/
let sum = 0;
for (let i = 0; i < s.length; i++)
sum += s.charCodeAt(i);
return sum % 0xff;
}
/**
* Create the next block with the given data
*/
next(data: number) {
return new Block(this.index + 1, input.runningTime(), data, this.hash);
}
/**
* Render the block as a string
*/
toString() {
return `block ${this.index} ${this.timestamp} ${this.data} ${this.hash}`;
}
/**
* Send the block over radio
*/
broadcast() {
serial.writeLine(`broadcast ${this}`);
/**
* We pack all the block data into a buffer and send it over radio
*/
const buf = pins.createBuffer(16);
buf.setNumber(NumberFormat.UInt8LE, 0, Message.Block);
buf.setNumber(NumberFormat.UInt8LE, 1, this.hash);
buf.setNumber(NumberFormat.UInt8LE, 2, this.previousHash);
buf.setNumber(NumberFormat.Int32LE, 4, this.index);
buf.setNumber(NumberFormat.Int32LE, 4 + 4, this.timestamp);
buf.setNumber(NumberFormat.Int32LE, 4 + 8, this.data);
radio.sendBuffer(buf)
}
/**
* Try to read the block from the buffer. If anything is wrong, return undefined.
*/
static receive(buf: Buffer): Block {
// check the message type
if (buf.getNumber(NumberFormat.UInt8LE, 0) != Message.Block)
return undefined;
// read all the parts of the block back from the buffer
const b = new Block(
buf.getNumber(NumberFormat.Int32LE, 4), // index
buf.getNumber(NumberFormat.Int32LE, 4 + 4), // timestamp
buf.getNumber(NumberFormat.Int32LE, 4 + 8), // data
buf.getNumber(NumberFormat.UInt8LE, 2) // previoushash
);
const h = buf.getNumber(NumberFormat.UInt8LE, 1); // hash
if (b.hash != h) {
serial.writeLine(`received invalid block ${b.hash} != ${h}`);
return undefined;
}
serial.writeLine(`received ${b}`);
return b;
}
}
/**
* A block chain is a sequence of block
*/
class BlockChain {
id: number; // device serial number
chain: Block[];
/**
* Constructs a new coin with the given id
*/
constructor(id: number) {
this.id = id;
this.chain = [];
// if ID is set, this coin is a mirror of a peer coin
// otherwise add genesis block
if (!this.id) {
this.chain.push(new Block(0, input.runningTime(), 0, 0));
this.id = control.deviceSerialNumber();
}
}
/**
* Grab the last block in the chain
*/
lastBlock() {
return this.chain[this.chain.length - 1];
}
/**
* Add a new block with your coin in the chain
*/
addCoin() {
this.chain.push(this.lastBlock().next(this.id));
this.lastBlock().broadcast();
}
/**
* Test if we have all the blocks in the chain available
*/
isComplete() {
for (let i = 0; i < this.chain.length; ++i)
if (!this.chain[i]) return false; // missing block
return this.lastBlock().index == this.chain.length - 1;
}
/**
* Test if the block chain is valid
*/
isValid() {
if (!this.isComplete()) {
serial.writeLine("coin not complete");
return false;
}
for (let i = 0; i < this.chain.length - 1; ++i) {
const prev = this.chain[i];
const next = this.chain[i + 1];
if (prev.index + 1 != next.index) {
serial.writeLine("invalid index");
return false;
}
if (prev.hash != next.previousHash) {
serial.writeLine("invalid prev hash");
}
if (next.computeHash() != next.hash) {
serial.writeLine("invalid hash");
return false;
}
}
return true;
}
/**
* Insert a block received over the radio
*/
insert(block: Block) {
this.chain[block.index] = block;
}
/**
* We've received a block chain and we are trying to replace the chain if it's been updated.
*/
replace(other: BlockChain) {
if (other.isValid() && other.chain.length > me.chain.length) {
serial.writeLine("replacing chain");
this.chain = other.chain.slice(0, other.chain.length);
this.lastBlock().broadcast()
basic.showIcon(IconNames.SmallSquare)
}
}
/**
* Broadcast the chains
*/
broadcastChain() {
for (let i = 0; i < this.chain.length; ++i) {
this.chain[i].broadcast();
}
}
}
/**
* Request all peers (or a single on) for the entire chain
*/
function broadcastQueryChain(serialNumber: number = 0) {
const msg = pins.createBuffer(6);
msg.setNumber(NumberFormat.UInt8LE, 0, Message.QueryChain);
msg.setNumber(NumberFormat.Int32LE, 2, serialNumber);
radio.sendBuffer(msg);
}
const me = new BlockChain(0);
const peers: BlockChain[] = [];
/**
* Get or create a block chain to store the blocks of a peer
*/
function peer(id: number): BlockChain {
for (let i = 0; i < peers.length; ++i) {
if (peers[i].id == id) return peers[i];
}
const r = new BlockChain(id);
peers.push(r);
return r;
}
/**
* Settings for the radio receiver
*/
radio.setGroup(42);
radio.setTransmitSerialNumber(true);
radio.onDataPacketReceived(({ receivedBuffer, serial: serialNumber }) => {
// processing a message received by ppers
let id: number;
switch (receivedBuffer[0]) {
case Message.QueryChain:
// so a peer asking to broadcast the chain
serial.writeLine("msg: query chain");
id = receivedBuffer.getNumber(NumberFormat.Int32LE, 2);
// either all peers should send or just me
if (!id || id == me.id) {
me.broadcastChain();
me.broadcastChain(); // send it twice as we might loose patterns
}
break;
case Message.Block:
// so we've received a block from a peer
serial.writeLine("msg: block");
const other = peer(serialNumber);
const block = Block.receive(receivedBuffer);
if (!block) return; // something got corrupted
other.insert(block);
serial.writeLine(`check ${other.lastBlock().index} > ${me.lastBlock().index}`)
// if the other chain is longer, we should update ours maybe
if (other.lastBlock().index > me.lastBlock().index) {
if (!other.isComplete()) {
// we don't have the entire chain
serial.writeLine(`peer incomplete`)
broadcastQueryChain(serialNumber);
} else {
// we have a full chain, try replacing it
serial.writeLine(`peer complete, try replace`)
me.replace(other);
}
}
break;
}
})
```blocks
// shaking is mining... // shaking is mining...
input.onGesture(Gesture.Shake, () => { input.onGesture(Gesture.Shake, () => {
led.stopAnimation() led.stopAnimation()
@ -328,7 +42,7 @@ input.onGesture(Gesture.Shake, () => {
basic.pause(200) // display a short pause basic.pause(200) // display a short pause
if (Math.random(3) == 0) { // 30% chances to add a transaction if (Math.random(3) == 0) { // 30% chances to add a transaction
// we found a coin!!! // we found a coin!!!
me.addCoin(); blockchain.addBlock(1);
basic.showIcon(IconNames.Diamond); basic.showIcon(IconNames.Diamond);
} else { } else {
// missed! // missed!
@ -336,34 +50,32 @@ input.onGesture(Gesture.Shake, () => {
} }
}) })
// show my score // show my coins
input.onButtonPressed(Button.A, () => { input.onButtonPressed(Button.A, () => {
led.stopAnimation() led.stopAnimation()
let score = 0; let coins = blockchain.valuesFrom(blockchain.id()).length;
for (let i = 0; i < me.chain.length; ++i) { basic.showNumber(coins);
if (me.chain[i].data == me.id) basic.showString("COINS");
score++;
}
basic.showNumber(score);
}) })
// show the block chain size // show the block chain size
input.onButtonPressed(Button.B, () => { input.onButtonPressed(Button.B, () => {
led.stopAnimation() led.stopAnimation()
basic.showNumber(me.chain.length - 1); basic.showNumber(blockchain.length());
}) basic.showString("BLOCKS");
input.onButtonPressed(Button.AB, () => {
led.stopAnimation()
for (let i = 1; i < me.chain.length; ++i) {
basic.showNumber(me.chain[i].data);
}
}) })
// ask neighbors for chains // instructions on how to play
broadcastQueryChain();
basic.showString("A=SCORE B=CHAIN SHAKE=MINE", 100) basic.showString("A=SCORE B=CHAIN SHAKE=MINE", 100)
``` ```
## How does it work?
The [radio-blockchain](https://makecode.microbit.org/pkg/microsoft/pxt-radio-blockchain) uses
radio to transmit blocks between @boardname@. You read it all by reading the JavaScript sources at https://github.com/microsoft/pxt-radio-blockchain or the code below.
[main.ts](https://github.com/Microsoft/pxt-radio-blockchain/blob/master/main.ts) contains the full JavaScript source of the blockhain. Have a good read!
## References ## References
* https://medium.com/@lhartikk/a-blockchain-in-200-lines-of-code-963cc1cc0e54 * https://medium.com/@lhartikk/a-blockchain-in-200-lines-of-code-963cc1cc0e54
@ -373,4 +85,5 @@ basic.showString("A=SCORE B=CHAIN SHAKE=MINE", 100)
```package ```package
radio radio
github:Microsoft/pxt-radio-blockchain
``` ```