updated micro:coin
This commit is contained in:
parent
614c3b0950
commit
c1fd464cb5
@ -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
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
})
|
||||
* Click on **Advanced**, then **Add Package**
|
||||
* search for **blockchain** and add **radio-blockchain**
|
||||
|
||||
```blocks
|
||||
// shaking is mining...
|
||||
input.onGesture(Gesture.Shake, () => {
|
||||
led.stopAnimation()
|
||||
@ -328,7 +42,7 @@ input.onGesture(Gesture.Shake, () => {
|
||||
basic.pause(200) // display a short pause
|
||||
if (Math.random(3) == 0) { // 30% chances to add a transaction
|
||||
// we found a coin!!!
|
||||
me.addCoin();
|
||||
blockchain.addBlock(1);
|
||||
basic.showIcon(IconNames.Diamond);
|
||||
} else {
|
||||
// missed!
|
||||
@ -336,34 +50,32 @@ input.onGesture(Gesture.Shake, () => {
|
||||
}
|
||||
})
|
||||
|
||||
// show my score
|
||||
// show my coins
|
||||
input.onButtonPressed(Button.A, () => {
|
||||
led.stopAnimation()
|
||||
let score = 0;
|
||||
for (let i = 0; i < me.chain.length; ++i) {
|
||||
if (me.chain[i].data == me.id)
|
||||
score++;
|
||||
}
|
||||
basic.showNumber(score);
|
||||
let coins = blockchain.valuesFrom(blockchain.id()).length;
|
||||
basic.showNumber(coins);
|
||||
basic.showString("COINS");
|
||||
})
|
||||
|
||||
// show the block chain size
|
||||
input.onButtonPressed(Button.B, () => {
|
||||
led.stopAnimation()
|
||||
basic.showNumber(me.chain.length - 1);
|
||||
})
|
||||
input.onButtonPressed(Button.AB, () => {
|
||||
led.stopAnimation()
|
||||
for (let i = 1; i < me.chain.length; ++i) {
|
||||
basic.showNumber(me.chain[i].data);
|
||||
}
|
||||
basic.showNumber(blockchain.length());
|
||||
basic.showString("BLOCKS");
|
||||
})
|
||||
|
||||
// ask neighbors for chains
|
||||
broadcastQueryChain();
|
||||
// instructions on how to play
|
||||
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
|
||||
|
||||
* 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
|
||||
radio
|
||||
github:Microsoft/pxt-radio-blockchain
|
||||
```
|
Loading…
Reference in New Issue
Block a user