Skip to content
Permalink
Browse files

Initial commit

  • Loading branch information...
cserb committed Apr 12, 2019
0 parents commit cece506457f60d6c0b9cdbb9e92614ef7b945316
@@ -0,0 +1,6 @@
/doc/
/lib/
/bin/
/.shards/

TODOs.org
373 LICENSE

Large diffs are not rendered by default.

@@ -0,0 +1,78 @@
<h1 align="center" style="font-size: 40px; font-weight: 200;">COCOL!</p>

<p align="center">
<img src="https://github.com/cocol-project/cocol/blob/master/demo/demo.gif" alt="Network" />
</p>

## About
The Cocol Project has the goal to lower the entry barrier for developers interested in building blockchains and dApps.
There is still a long way to go and your help is needed.

## Installation
Cocol is written in [Crystal](https://crystal-lang.org/), so make sure to follow the [installation instructions](https://crystal-lang.org/reference/installation/) first.

After setting up Crystal you can clone the Cocol repository and install the dependencies:
```
> git clone https://gitlab.com/cocol/cocol.git
> cd cocol
> shards install
```

## Usage
Make your changes to the code-base and than build Cocol
```
> shards build
```
The binary `./bin/cocol` offers the following CLI options

```
Options:
-p --port The port your Cocol node is going to run on
-m --master Making this node a master (there can only be one)
--max-connections Setting the max-connections for peers.
--miner Making this node a miner
--update Triggering an update on launch (will catch up with the current height)
```

There is also a script that starts multiple nodes and the master for you

```
> ./script/start.sh 66 5
```
First number is the amount of nodes and the second max-connections per node.
It will start the master node with the port `3000` and every other node with `3000 + n`


You can now start one or several miner like this:
```
> ./cocol -p 4100 --max-connections 5 --miner
```

Now go ahead and open the explorer in a browser:
```
> open ./explorer/index.html
```

You should see 66 nodes and a miner (red border)

Each one of the nodes has a REST API on the corresponding port (e.g. `3001`)

You can create transactions or query the current ledger

## Development

Cocol is in a very early stage. Expect changes, bugs and messy code.

## Contributing

1. Fork it ( https://gitlab.com/cocol/cocol/forks/new )
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Add some feature')
4. Push to the branch (git push origin my-new-feature)
5. Create a new Pull Request

## Contributors

- [github: [cserb]](https://github.com/cserb) [twitter: [@cerbivore]](http://twitter.com/cerbivore) Cristian Serb - creator, maintainer
@@ -0,0 +1,42 @@
# All connected peers
GET http://localhost:3001/peers

# Create genesis block
GET http://localhost:3000/genesis

# Mine block and include pending transactions
GET http://localhost:3027/internal/mine

# All mined blocks
GET http://localhost:3100/ledger

# All mined blocks
GET http://localhost:3100/candidates

# All mined blocks
GET http://localhost:3100/orphans

# All mined blocks
GET http://localhost:3100/blocks

# All candidate votes
GET http://localhost:3002/blocks

# All pending transactions
GET http://localhost:3100/transactions

# Create a new transaction
POST http://localhost:3001/transactions
Content-Type: application/json
{
"from": "06303644-4b41-46d6-a094-1393ff6e2521",
"to": "06303644-4b41-46d6-a094-1393ff6e2521",
"timestamp": 1449970561,
"amount": 87,
"hash": "a2e8a2181f2783594c8c30faeef2ff2a531e3abe00238d01b0fc2cd75408f791"
}

# Connect lonely nodes :(
POST http://localhost:3101/internal/handshake/3034
Content-Type: application/json
{}
@@ -0,0 +1,6 @@
master:
host: localhost
port: 3000

node:
max_connections: 3
BIN +5.24 MB demo/demo.gif
Binary file not shown.

Large diffs are not rendered by default.

@@ -0,0 +1,190 @@
var nodes = [
{
port: '3000',
peers: []
}
];

var transactions = {};

var blocks = {
// Genesis block
0: [
{
hash: '00f82f15d9fee292860b2a37183d769efd3b617451c04017f700238fd472e8bb',
previous_hash: 'Olivia'
}
]
};

var stats = new Vue({
el: '#stats-list',
data: {transactions: transactions}
});

var chain = new Vue({
el: '#blocks',
data: {blocks: blocks}
});

var graph = Viva.Graph.graph();
var graphics = Viva.Graph.View.svgGraphics();
var nodeSize = 24;


var loopNodes = function () {
for (i=0; i < nodes.length; ++i) {
(function () {
var currentIndex = i;
if (!nodes[i].socket) {
connectToNode(currentIndex);
}
}());
}
};

var connectToNode = function (i) {
socket = new WebSocket('ws://localhost:'+nodes[i].port+'/events');
socket.addEventListener('message', function (event) {
console.log('===== now event ======');
var eventData = JSON.parse(event.data);
processEvent(eventData, i);
});
socket.addEventListener('close', function (event) {
console.log('===== now closing ======');
url = new URL(event.target.url);
removeNode(url.port);
});
nodes[i].socket = socket;
};

var processEvent = function (event, i) {
console.log('> process event');
if (event.event === 'onTxn') {
console.log('> + new txn');
transactions[event.hash] = event.amount;
stats.transactions = transactions;
stats.$forceUpdate();
} else if(event.event === 'onConnection') {
console.log('> + new peer');
checkConnections([event.peer_port], event.node_port);
} else if(event.event === 'onNewBlock') {
console.log('> + new block');
if (!blocks[event.height]) {
blocks[event.height] = [];
}
blocks[event.height].push({hash: event.hash, previous_hash: event.previous_hash});
chain.blocks = blocks;
chain.$forceUpdate();
} else {
// this we only do because we don't know the master node ident
console.log('> + new node');
nodes[i].height = event.height;
nodes[i].hash = event.hash;
nodes[i].peers = event.peers;
nodes[i].port = event.port;
nodes[i].txn = event.txn;
nodes[i].miner = event.miner;

if (nodes[i].port !== '3000' && nodes[i].port !== 3000) {
graph.addNode(nodes[i].port, {
height: nodes[i].height,
hash: nodes[i].hash || '-',
txn: nodes[i].txn || 0,
miner: nodes[i].miner
});
}

if (event.event === 'onInitialUpdate') {
checkConnections(event.peers, nodes[i].port);
}
}
};

var checkConnections = function (peers, parentApiPort) {
for (i=0; i < peers.length; ++i) {
(function () {
var peer = nodes.find(node => node.port === peers[i]);
if (!peer) {
nodes.push({port: peers[i]});
stats.nodes = nodes;
connectToNode(nodes.length - 1);
}
// also create link
if (parentApiPort !== '3000' && parentApiPort !== 3000) {
if (graph.getLink(peers[i], parentApiPort) === null && graph.getLink(parentApiPort, peers[i]) === null) {
graph.addLink(peers[i], parentApiPort);
}
}
}());
}
};

var removeNode = function (port) {
graph.removeNode(port);
nodes = nodes.filter(node => parseInt(port) !== node.port);
stats.nodes = nodes;
};

loopNodes();

graphics.node(function(node) {
// This time it's a group of elements: http://www.w3.org/TR/SVG/struct.html#Groups
var ui = Viva.Graph.svg('g');
// Create SVG text element with user id as content
var stringToColour = function(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
var colour = '#';
for (var i = 0; i < 3; i++) {
var value = (hash >> (i * 8)) & 0xFF;
colour += ('00' + value.toString(16)).substr(-2);
}
return colour;
};
var height = node.data ? node.data.height : '0';
var hash = node.data ? node.data.hash : '-';

var svgText = Viva.Graph.svg('text').attr('y', '-4px').text(node.id+' / '+height+' / '+hash.substr(-6));
var svgCircle = Viva.Graph.svg('circle')
.attr('r', '7px')
.attr('cy', '12')
.attr('cx', '12')
.attr('fill', stringToColour(hash));

if ( node.data && node.data.miner === true ) {
svgCircle = svgCircle.attr('stroke', '#ff6859').attr('stroke-width', 3);
}

ui.append(svgText);
ui.append(svgCircle);

return ui;
}).placeNode(function(nodeUI, pos) {
// 'g' element doesn't have convenient (x,y) attributes, instead
// we have to deal with transforms: http://www.w3.org/TR/SVG/coords.html#SVGGlobalTransformAttribute
nodeUI.attr('transform',
'translate(' +
(pos.x - nodeSize/2) + ',' + (pos.y - nodeSize/2) +
')');
});

graphics.link(function(link){
return Viva.Graph.svg('line')
.attr('stroke', 'rgba(0,0,0,0.1)')
.attr('stroke-width', 1);
});
var layout = Viva.Graph.Layout.forceDirected(graph, {
springLength: 300
});
var renderer = Viva.Graph.View.renderer(graph, {
layout: layout,
graphics : graphics,
container: document.getElementById('graph')
});

renderer.run();

// ============================================================================

0 comments on commit cece506

Please sign in to comment.
You can’t perform that action at this time.