tulipmania 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 43c6117496ffb6839f9d060bd44fedbf675f6236
4
+ data.tar.gz: 9128fd6314ecd688f6769946d9b7c05c906d6c9e
5
+ SHA512:
6
+ metadata.gz: a5338475a3b1366689431aba1be3789e3a783b89f87a01ba28cfb85a02da74d632c5cdb124a425e9be324fc744e41fd7312711ac4e27caea6aa39cdb47a32277
7
+ data.tar.gz: 8cc2013daab3ece6ce8b1303045ab0bbb6166240114bf51bc2a98795044ba8caa6e6906570703007b25dade78d4d3bcc4272831679ba93d1ea941ae3027d330e
data/HISTORY.md ADDED
@@ -0,0 +1,3 @@
1
+ ### 0.1.0 / 2017-12-18
2
+
3
+ * Everything is new. First release.
data/Manifest.txt ADDED
@@ -0,0 +1,21 @@
1
+ HISTORY.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/tulipmania.rb
6
+ lib/tulipmania/block.rb
7
+ lib/tulipmania/blockchain.rb
8
+ lib/tulipmania/cache.rb
9
+ lib/tulipmania/exchange.rb
10
+ lib/tulipmania/ledger.rb
11
+ lib/tulipmania/node.rb
12
+ lib/tulipmania/transaction.rb
13
+ lib/tulipmania/version.rb
14
+ lib/tulipmania/views/_blockchain.erb
15
+ lib/tulipmania/views/_ledger.erb
16
+ lib/tulipmania/views/_peers.erb
17
+ lib/tulipmania/views/_pending_transactions.erb
18
+ lib/tulipmania/views/_wallet.erb
19
+ lib/tulipmania/views/index.erb
20
+ lib/tulipmania/views/style.scss
21
+ lib/tulipmania/wallet.rb
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # tulipmania (anno 1673) library / gem and command line tool
2
+
3
+
4
+ tulips on the blockchain; learn by example from the real world (anno 1637) - buy! sell! hodl! enjoy the beauty of admiral of admirals, semper augustus, and more;
5
+ run your own hyper ledger tulip exchange nodes on the blockchain peer-to-peer over HTTP; revolutionize the world one block at a time
6
+
7
+
8
+ * home :: [github.com/openblockchains/tulipmania](https://github.com/openblockchains/tulipmania)
9
+ * bugs :: [github.com/openblockchains/tulipmania/issues](https://github.com/openblockchains/tulipmania/issues)
10
+ * gem :: [rubygems.org/gems/tulipmania](https://rubygems.org/gems/tulipmania)
11
+ * rdoc :: [rubydoc.info/gems/tulipmania](http://rubydoc.info/gems/tulipmania)
12
+
13
+
14
+
15
+
16
+ ## Development
17
+
18
+ For local development - clone or download (and unzip) the tulipmania code repo.
19
+ Next install all dependencies using bundler with a Gemfile e.g.:
20
+
21
+ ``` ruby
22
+ # Gemfile
23
+
24
+ source "https://rubygems.org"
25
+
26
+ gem 'sinatra'
27
+ gem 'sass'
28
+ gem 'blockchain-lite'
29
+ ```
30
+
31
+ run
32
+
33
+ ```
34
+ $ bundle ## will use the Gemfile (see above)
35
+ ```
36
+
37
+ and now you're ready to run your own hyper ledger tulip exchange server node.
38
+ Use the [`config.ru`](config.ru) script for rack:
39
+
40
+ ``` ruby
41
+ # config.ru
42
+
43
+ $LOAD_PATH << './lib'
44
+
45
+ require 'tulipmania'
46
+
47
+ run Tulipmania::Service
48
+ ```
49
+
50
+ and startup the money printing machine using rackup - the rack command line tool:
51
+
52
+ ```
53
+ $ rackup ## will use the config.ru - rackup configuration script (see above).
54
+ ```
55
+
56
+ In your browser open up the page e.g. `http://localhost:9292`. Voila!
57
+
58
+
59
+
60
+
61
+ ## License
62
+
63
+ ![](https://publicdomainworks.github.io/buttons/zero88x31.png)
64
+
65
+ The `tulipmania` scripts are dedicated to the public domain.
66
+ Use it as you please with no restrictions whatsoever.
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require 'hoe'
2
+ require './lib/tulipmania/version.rb'
3
+
4
+ Hoe.spec 'tulipmania' do
5
+
6
+ self.version = Tulipmania::VERSION
7
+
8
+ self.summary = 'tulipmania - tulips on the blockchain; learn by example from the real world (anno 1637) - buy! sell! hodl! enjoy the beauty of admiral of admirals, semper augustus, and more; run your own hyper ledger tulip exchange nodes on the blockchain peer-to-peer over HTTPprint your own money / cryptocurrency; run your own federated central bank nodes on the blockchain peer-to-peer over HTTP; revolutionize the world one block at a time'
9
+ self.description = summary
10
+
11
+ self.urls = ['https://github.com/openblockchains/tulipmania']
12
+
13
+ self.author = 'Gerald Bauer'
14
+ self.email = 'ruby-talk@ruby-lang.org'
15
+
16
+ # switch extension to .markdown for gihub formatting
17
+ self.readme_file = 'README.md'
18
+ self.history_file = 'History.md'
19
+
20
+ self.extra_deps = [
21
+ ['sinatra', '>=2.0'],
22
+ ['sass'], ## used for css style preprocessing (scss)
23
+ ['blockchain-lite', '>=1.2'],
24
+ ]
25
+
26
+ self.licenses = ['Public Domain']
27
+
28
+ self.spec_extras = {
29
+ required_ruby_version: '>= 2.3'
30
+ }
31
+
32
+ end
@@ -0,0 +1,40 @@
1
+
2
+
3
+ Block = BlockchainLite::ProofOfWork::Block
4
+
5
+ ## see https://github.com/openblockchains/blockchain.lite.rb/blob/master/lib/blockchain-lite/proof_of_work/block.rb
6
+
7
+
8
+ ######
9
+ # add more methods
10
+
11
+ class Block
12
+
13
+ def to_h
14
+ { index: @index,
15
+ timestamp: @timestamp,
16
+ nonce: @nonce,
17
+ transactions: @transactions.map { |tx| tx.to_h },
18
+ previous_hash: @previous_hash }
19
+ end
20
+
21
+ def self.from_h( h )
22
+ transactions = h['transactions'].map { |h_tx| Tx.from_h( h_tx ) }
23
+
24
+ ## parse iso8601 format e.g 2017-10-05T22:26:12-04:00
25
+ timestamp = Time.parse( h['timestamp'] )
26
+
27
+ self.new( h['index'],
28
+ transactions,
29
+ h['previous_hash'],
30
+ timestamp: timestamp,
31
+ nonce: h['nonce'].to_i )
32
+ end
33
+
34
+
35
+ def valid?
36
+ true ## for now always valid
37
+ end
38
+
39
+
40
+ end # class Block
@@ -0,0 +1,47 @@
1
+
2
+
3
+
4
+ class Blockchain
5
+ extend Forwardable
6
+ def_delegators :@chain, :[], :size, :each, :empty?, :last
7
+
8
+
9
+ def initialize( chain=[] )
10
+ @chain = chain
11
+ end
12
+
13
+ def <<( txs )
14
+ ## todo: check if is block or array
15
+ ## if array (of transactions) - auto-add (build) block
16
+ ## allow block - why? why not?
17
+ ## for now just use transactions (keep it simple :-)
18
+
19
+ if @chain.size == 0
20
+ block = Block.first( txs )
21
+ else
22
+ block = Block.next( @chain.last, txs )
23
+ end
24
+ @chain << block
25
+ end
26
+
27
+
28
+
29
+ def as_json
30
+ @chain.map { |block| block.to_h }
31
+ end
32
+
33
+ def transactions
34
+ ## "accumulate" get all transactions from all blocks "reduced" into a single array
35
+ @chain.reduce( [] ) { |acc, block| acc + block.transactions }
36
+ end
37
+
38
+
39
+
40
+ def self.from_json( data )
41
+ ## note: assumes data is an array of block records/objects in json
42
+ chain = data.map { |h| Block.from_h( h ) }
43
+ self.new( chain )
44
+ end
45
+
46
+
47
+ end # class Blockchain
@@ -0,0 +1,22 @@
1
+
2
+
3
+ class Cache
4
+ def initialize( name )
5
+ @name = name
6
+ end
7
+
8
+ def write( data )
9
+ File.open( @name, 'w' ) do |f| ## use utf8 - why? why not??
10
+ f.write JSON.pretty_generate( data )
11
+ end
12
+ end
13
+
14
+ def read
15
+ if File.exists?( @name )
16
+ data = File.read( @name ) ## use utf8 - why? why not??
17
+ JSON.parse( data )
18
+ else
19
+ nil
20
+ end
21
+ end
22
+ end ## class Cache
@@ -0,0 +1,113 @@
1
+
2
+
3
+
4
+
5
+ class Exchange
6
+ attr_reader :pending, :chain, :ledger
7
+
8
+ COINBASE = "COINBASE"
9
+ MINING_REWARD = 5
10
+
11
+
12
+ def initialize( address )
13
+ @address = address
14
+
15
+ @cache = Cache.new( 'data.json' )
16
+ h = @cache.read
17
+ if h
18
+ ## restore blockchain
19
+ @chain = Blockchain.from_json( h['chain'] )
20
+ ## restore pending transactions too
21
+ @pending = h['transactions'].map { |h_tx| Tx.from_h( h_tx ) }
22
+ else
23
+ @chain = Blockchain.new
24
+ @chain << [Tx.new( COINBASE, @address, MINING_REWARD )] # genesis (big bang!) starter block
25
+ @pending = []
26
+ end
27
+
28
+ ## update ledger (balances) with confirmed transactions
29
+ @ledger = Ledger.new( @chain )
30
+ end
31
+
32
+
33
+
34
+ def mine_block!
35
+ add_transaction( Tx.new( COINBASE, @address, MINING_REWARD ))
36
+
37
+ ## add mined (w/ computed/calculated hash) block
38
+ @chain << @pending
39
+ @pending = []
40
+
41
+ ## update ledger (balances) with new confirmed transactions
42
+ @ledger = Ledger.new( @chain )
43
+
44
+ @cache.write as_json
45
+ end
46
+
47
+
48
+ def sufficient_funds?( wallet, amount )
49
+ ## (convenience) delegate for ledger
50
+ ## todo/check: use address instead of wallet - why? why not?
51
+ ## for now single address wallet (that is, wallet==address)
52
+ @ledger.sufficient_funds?( wallet, amount )
53
+ end
54
+
55
+
56
+ def add_transaction( tx )
57
+ if tx.valid? && transaction_is_new?( tx )
58
+ @pending << tx
59
+ @cache.write as_json
60
+ return true
61
+ else
62
+ return false
63
+ end
64
+ end
65
+
66
+
67
+ ##
68
+ # check - how to name incoming chain - chain_new, chain_candidate - why? why not?
69
+ # what's an intuitive name - what's gets used most often???
70
+
71
+ def resolve!( chain_new )
72
+ # TODO this does not protect against invalid block shapes (bogus COINBASE transactions for example)
73
+
74
+ if !chain_new.empty? && chain_new.last.valid? && chain_new.size > @chain.size
75
+ @chain = chain_new
76
+ ## update ledger (balances) with new confirmed transactions
77
+ @ledger = Ledger.new( @chain )
78
+
79
+
80
+ _transactions = @chain.transactions ## use a copy for reference (optimization) in inner loop
81
+ ## todo: cleanup ??? -- use tx2 for t ???
82
+ ## document - keep only pending transaction not yet in blockchain ????
83
+ @pending = @pending.select do |tx|
84
+ _transactions.none? { |tx_confirmed| tx_confirmed.id == tx.id }
85
+ end
86
+ @cache.write as_json
87
+ return true
88
+ else
89
+ return false
90
+ end
91
+ end
92
+
93
+
94
+
95
+ def as_json
96
+ { chain: @chain.as_json,
97
+ transactions: @pending.map { |tx| tx.to_h }
98
+ }
99
+ end
100
+
101
+
102
+
103
+ private
104
+
105
+ def transaction_is_new?( tx_new )
106
+ ## check if tx exists already in blockchain or pending tx pool
107
+
108
+ ## todo: use chain.include? to check for include
109
+ ## avoid loop and create new array for check!!!
110
+ (@chain.transactions + @pending).none? { |tx| tx_new.id == tx.id }
111
+ end
112
+
113
+ end ## class Exchange
@@ -0,0 +1,30 @@
1
+
2
+ class Ledger
3
+ attr_reader :wallets ## use addresses - why? why not? for now single address wallet (wallet==address)
4
+
5
+ def initialize( chain=[] )
6
+ @wallets = {}
7
+ chain.each do |block|
8
+ apply_transactions( block.transactions )
9
+ end
10
+ end
11
+
12
+ def sufficient_funds?( wallet, amount )
13
+ return true if wallet == Bank::COINBASE ### use Config::COINBASE why? why not ???
14
+ @wallets.has_key?( wallet ) && @wallets[wallet] - amount >= 0
15
+ end
16
+
17
+
18
+ private
19
+
20
+ def apply_transactions( transactions )
21
+ transactions.each do |tx|
22
+ if sufficient_funds?(tx.from, tx.amount)
23
+ @wallets[tx.from] -= tx.amount unless tx.from == Bank::COINBASE ### use Config::COINBASE why? why not ???
24
+ @wallets[tx.to] ||= 0
25
+ @wallets[tx.to] += tx.amount
26
+ end
27
+ end
28
+ end
29
+
30
+ end ## class Ledger
@@ -0,0 +1,88 @@
1
+
2
+
3
+ class Node
4
+ attr_reader :id, :peers, :wallet, :bank
5
+
6
+
7
+ WALLET_ADDRESSES = %w[Sepp Franz Sissi Maria Eva Ferdl Max Adam]
8
+
9
+ def initialize( address: nil )
10
+ ## pick "random" address if nil (none passed in)
11
+ address ||= WALLET_ADDRESSES[rand( WALLET_ADDRESSES.size )]
12
+
13
+ @id = SecureRandom.uuid
14
+ @peers = []
15
+ @wallet = Wallet.new( address )
16
+ @bank = Bank.new @wallet.address
17
+ end
18
+
19
+
20
+
21
+ def on_add_peer( host, port )
22
+ @peers << [host, port]
23
+ @peers.uniq!
24
+ # TODO/FIX: no need to send to every peer, just the new one
25
+ send_chain_to_peers
26
+ @bank.pending.each { |tx| send_transaction_to_peers( tx ) }
27
+ end
28
+
29
+ def on_delete_peer( index )
30
+ @peers.delete_at( index )
31
+ end
32
+
33
+
34
+ def on_add_transaction( from, to, amount, id )
35
+ ## note: for now must always pass in id - why? why not? possible tx without id???
36
+ tx = Tx.new( from, to, amount, id )
37
+ if @bank.sufficient_funds?( tx.from, tx.amount ) && @bank.add_transaction( tx )
38
+ send_transaction_to_peers( tx )
39
+ return true
40
+ else
41
+ return false
42
+ end
43
+ end
44
+
45
+ def on_send( to, amount )
46
+ tx = @wallet.generate_transaction( to, amount )
47
+ if @bank.sufficient_funds?( tx.from, tx.amount ) && @bank.add_transaction( tx )
48
+ send_transaction_to_peers( tx )
49
+ return true
50
+ else
51
+ return false
52
+ end
53
+ end
54
+
55
+
56
+ def on_mine!
57
+ @bank.mine_block!
58
+ send_chain_to_peers
59
+ end
60
+
61
+ def on_resolve( data )
62
+ chain_new = Blockchain.from_json( data )
63
+ if @bank.resolve!( chain_new )
64
+ send_chain_to_peers
65
+ return true
66
+ else
67
+ return false
68
+ end
69
+ end
70
+
71
+
72
+
73
+ private
74
+
75
+ def send_chain_to_peers
76
+ data = JSON.pretty_generate( @bank.as_json ) ## payload in json
77
+ @peers.each do |(host, port)|
78
+ Net::HTTP.post(URI::HTTP.build(host: host, port: port, path: '/resolve'), data )
79
+ end
80
+ end
81
+
82
+ def send_transaction_to_peers( tx )
83
+ @peers.each do |(host, port)|
84
+ Net::HTTP.post_form(URI::HTTP.build(host: host, port: port, path: '/transactions'), tx.to_h )
85
+ end
86
+ end
87
+
88
+ end ## class Node
@@ -0,0 +1,30 @@
1
+
2
+
3
+ class Transaction
4
+
5
+ attr_reader :from, :to, :amount, :id
6
+
7
+ def initialize( from, to, amount, id=SecureRandom.uuid )
8
+ @from = from
9
+ @to = to
10
+ @amount = amount
11
+ @id = id
12
+ end
13
+
14
+ def self.from_h( hash )
15
+ self.new *hash.values_at( 'from', 'to', 'amount', 'id' )
16
+ end
17
+
18
+ def to_h
19
+ { from: @from, to: @to, amount: @amount, id: @id }
20
+ end
21
+
22
+
23
+ def valid?
24
+ ## check signature in the future; for now always true
25
+ true
26
+ end
27
+
28
+ end # class Transaction
29
+
30
+ Tx = Transaction ## add Tx shortcut / alias
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ module Tulipmania
4
+
5
+ VERSION = '0.1.0'
6
+
7
+ def self.root
8
+ "#{File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )}"
9
+ end
10
+
11
+ end # module Tulipmania
@@ -0,0 +1,34 @@
1
+ <div class="blockchain">
2
+ <h2>
3
+ Blockchain<br>
4
+ <span><%= @node.bank.chain.size %> blocks</span>
5
+ </h2>
6
+ <form action="/mine" method="post">
7
+ <input type="submit" class="button" value="Mine a Block">
8
+ </form>
9
+
10
+ <div class="blocks">
11
+ <% @node.bank.chain.last(10).reverse.each do |block| %>
12
+ <div class="block">
13
+ <div class="header">
14
+ <%= block.index %> — <%= block.timestamp %><br>
15
+ </div>
16
+ <table>
17
+ <% block.transactions.each do |tx| %>
18
+ <tr>
19
+ <td class="id">
20
+ <%= tx.id[0..2] %>
21
+ </td>
22
+ <td>
23
+ $<%= tx.amount %>
24
+ </td>
25
+ <td>
26
+ <%= tx.from[0..7] %> → <%= tx.to[0..7] %>
27
+ </td>
28
+ </tr>
29
+ <% end %>
30
+ </table>
31
+ </div>
32
+ <% end %>
33
+ </div>
34
+ </div>
@@ -0,0 +1,15 @@
1
+ <div class="ledger">
2
+ <h2>Ledger</h2>
3
+ <table>
4
+ <tr>
5
+ <th>Address</th>
6
+ <th>Balance</th>
7
+ </tr>
8
+ <% @node.bank.ledger.wallets.each do |address, amount| %>
9
+ <tr>
10
+ <td><%= address[0..15] %></td>
11
+ <td>$<%= amount %></td>
12
+ </tr>
13
+ <% end %>
14
+ </table>
15
+ </div>
@@ -0,0 +1,24 @@
1
+ <div class="peers">
2
+ <h2>Peers</h2>
3
+ <% if @node.peers.any? %>
4
+ <ul>
5
+ <% @node.peers.each_with_index do |(host, port), i| %>
6
+ <li>
7
+ http://<%= host %>:<%= port %>
8
+ <form action="/peers/<%= i %>/delete" method="post">
9
+ <input type="submit" class="small" value="Remove" />
10
+ </form>
11
+ </li>
12
+ <% end %>
13
+ </ul>
14
+ <% else %>
15
+ <i>No peers</i>
16
+ <% end %>
17
+ <form action="/peers" method="post" class="peers-form">
18
+ <label for="host">http://</label>
19
+ <input type="text" name="host" id="host" placeholder="host" />
20
+ <label for="port">Port</label>
21
+ <input type="text" name="port" id="port" />
22
+ <input type="submit" class="button" value="Add Peer" />
23
+ </form>
24
+ </div>
@@ -0,0 +1,23 @@
1
+ <div class="pending-transactions">
2
+ <h2>Pending Transactions</h2>
3
+ <% if @node.bank.pending.any? %>
4
+ <table>
5
+ <tr>
6
+ <th>From</th>
7
+ <th>To</th>
8
+ <th>$</th>
9
+ <th>Id</th>
10
+ </tr>
11
+ <% @node.bank.pending.each do |tx| %>
12
+ <tr>
13
+ <td><%= tx.from[0..15] %></td>
14
+ <td><%= tx.to[0..15] %></td>
15
+ <td><%= tx.amount %></td>
16
+ <td><%= tx.id[0..2] %></td>
17
+ </tr>
18
+ <% end %>
19
+ </table>
20
+ <% else %>
21
+ <i>No pending transactions</i>
22
+ <% end %>
23
+ </div>
@@ -0,0 +1,16 @@
1
+
2
+ <div class="wallet">
3
+ <div class="details">
4
+ <h2>Address</h2>
5
+ <div><%= @node.wallet.address %></div>
6
+ <h2>Balance</h2>
7
+ <div class="balance">$<%= @node.bank.ledger.wallets[@node.wallet.address] || 0 %></div>
8
+ </div>
9
+ <form action="/send" method="post" class="transaction-form">
10
+ <label for="to">To</label>
11
+ <input type="text" name="to" id="to" placeholder="address" />
12
+ <label for="amount">Amount</label>
13
+ <input type="text" name="amount" id="amount" />
14
+ <input type="submit" class="button" value="Send" /><br>
15
+ </form>
16
+ </div>
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Central Bank Node</title>
5
+ <link rel="stylesheet" href="style.css">
6
+ </head>
7
+ <body>
8
+
9
+ <h1>Central Bank Node</h1>
10
+
11
+ <div class="columns">
12
+ <div class="left">
13
+ <%= erb :'_wallet' %>
14
+ <%= erb :'_pending_transactions' %>
15
+ <%= erb :'_peers' %>
16
+ <%= erb :'_ledger' %>
17
+ </div>
18
+
19
+ <div class="right">
20
+ <%= erb :'_blockchain' %>
21
+ </div>
22
+ </div>
23
+
24
+ <script>
25
+ var es = new EventSource('/events');
26
+ es.onmessage = function(e) { window.location.reload(false); };
27
+ </script>
28
+
29
+ </body>
30
+ </html>
@@ -0,0 +1,172 @@
1
+
2
+ body {
3
+ padding: 0;
4
+ margin: 0;
5
+ min-width: 960px;
6
+
7
+ font-family: 'menlo', monospace;
8
+ font-size: 14px;
9
+
10
+ background: #fff;
11
+ color: #2B2D2F;
12
+ }
13
+
14
+
15
+ .columns {
16
+ display: flex;
17
+
18
+ .left {
19
+ width: 66%;
20
+ }
21
+
22
+ .right {
23
+ width: 34%;
24
+ }
25
+ }
26
+
27
+
28
+ h1 {
29
+ font-size: 24px;
30
+ font-weight: normal;
31
+ padding-left: 15px;
32
+ margin-bottom: 20px;
33
+ }
34
+
35
+
36
+ h2 {
37
+ font-size: 16px;
38
+ }
39
+
40
+ h2 span {
41
+ font-size: 14px;
42
+ color: #597898;
43
+ font-weight: normal;
44
+ }
45
+
46
+ label {
47
+ display: inline-block;
48
+ width: 80px;
49
+ text-align: right;
50
+ padding-right: 10px;
51
+ }
52
+
53
+ input[type=text] {
54
+ display: inline-block;
55
+ font-size: 14px;
56
+ padding: 8px;
57
+ border-radius: 0;
58
+ border: 0;
59
+ }
60
+
61
+ table {
62
+ border-spacing: 0;
63
+ border-collapse: collapse;
64
+
65
+ th {
66
+ text-align: left;
67
+ }
68
+
69
+ td {
70
+ vertical-align: top;
71
+ padding: 5px 15px 5px 0;
72
+ }
73
+ }
74
+
75
+
76
+ ul {
77
+ list-style: none;
78
+ padding: 0;
79
+ margin: 0;
80
+ }
81
+
82
+ input[type=submit] {
83
+ font-size: 14px;
84
+ font-family: menlo, monospace;
85
+ border-radius: 5px;
86
+ padding: 8px 20px;
87
+ background: #FFDC00;
88
+ color: #2B2D2F;
89
+ border: 0;
90
+ }
91
+
92
+ input[type=submit].small {
93
+ font-size: 10px;
94
+ padding: 4px 10px;
95
+ }
96
+
97
+
98
+
99
+ .wallet {
100
+ padding: 15px;
101
+ background: #7FDBFF;
102
+
103
+ h2 {
104
+ margin-bottom: 0;
105
+ }
106
+
107
+ .balance {
108
+ font-size: 30px;
109
+ }
110
+ }
111
+
112
+
113
+ .pending-transactions {
114
+ padding: 15px;
115
+ background: #A3E6FF;
116
+ }
117
+
118
+ .peers {
119
+ padding: 15px;
120
+ background: #C6EFFF;
121
+
122
+ li form {
123
+ display: inline;
124
+ }
125
+
126
+ li {
127
+ padding: 5px 0px;
128
+ }
129
+ }
130
+
131
+
132
+ .ledger {
133
+ padding: 15px;
134
+ background: #E3F7FF;
135
+ }
136
+
137
+
138
+ .blockchain {
139
+ padding: 15px;
140
+ position: relative;
141
+ background: #001F3F;
142
+ color: #fff;
143
+
144
+ form {
145
+ position: absolute;
146
+ top: 30px;
147
+ right: 15px;
148
+ }
149
+
150
+ .blocks {
151
+ border: 1px solid #597898;
152
+ border-bottom: 0;
153
+
154
+ .block {
155
+ margin: 0;
156
+ border-bottom: 2px dashed #597898;
157
+ padding: 10px;
158
+
159
+ .header {
160
+ text-align: center;
161
+ padding: 0 8px 8px 8px;
162
+ color: #597898;
163
+ border-bottom: 1px solid #354c63;
164
+ margin-bottom: 10px;
165
+ }
166
+
167
+ .id {
168
+ color: #597898;
169
+ }
170
+ }
171
+ }
172
+ }
@@ -0,0 +1,15 @@
1
+ ###########
2
+ # Single Address Wallet
3
+
4
+ class Wallet
5
+ attr_reader :address
6
+
7
+ def initialize( address )
8
+ @address = address
9
+ end
10
+
11
+ def generate_transaction( to, amount )
12
+ Tx.new( address, to, amount )
13
+ end
14
+
15
+ end # class Wallet
data/lib/tulipmania.rb ADDED
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+
3
+ # stdlibs
4
+ require 'json'
5
+ require 'digest'
6
+ require 'net/http'
7
+ require 'set'
8
+ require 'pp'
9
+
10
+
11
+ ### 3rd party gems
12
+ require 'sinatra/base' # note: use "modular" sinatra app / service
13
+ require 'blockchain-lite/proof_of_work/block' # note: use proof-of-work block only (for now)
14
+
15
+
16
+ ### our own code
17
+ require 'tulipmania/version' ## let version always go first
18
+ require 'tulipmania/block'
19
+ require 'tulipmania/cache'
20
+ require 'tulipmania/transaction'
21
+ require 'tulipmania/blockchain'
22
+ require 'tulipmania/exchange'
23
+ require 'tulipmania/ledger'
24
+ require 'tulipmania/wallet'
25
+
26
+ require 'tulipmania/node'
27
+
28
+
29
+
30
+
31
+ module Tulipmania
32
+
33
+ class Service < Sinatra::Base
34
+
35
+ def self.banner
36
+ "tulipmania/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] on Sinatra/#{Sinatra::VERSION} (#{ENV['RACK_ENV']})"
37
+ end
38
+
39
+
40
+ ## - for now hard-code address e.g. Sepp
41
+ NODE = Node.new( address: 'Sepp' )
42
+
43
+
44
+ PUBLIC_FOLDER = "#{Tulipmania.root}/lib/tulipmania/public"
45
+ VIEWS_FOLDER = "#{Tulipmania.root}/lib/tulipmania/views"
46
+
47
+ puts "[tulipmania] boot - setting public folder to: #{PUBLIC_FOLDER}"
48
+ puts "[tulipmania] boot - setting views folder to: #{VIEWS_FOLDER}"
49
+
50
+ set :public_folder, PUBLIC_FOLDER # set up the static dir (with images/js/css inside)
51
+ set :views, VIEWS_FOLDER # set up the views dir
52
+
53
+ set :static, true # set up static file routing -- check - still needed?
54
+
55
+
56
+ set connections: []
57
+
58
+
59
+ get '/style.css' do
60
+ scss :style ## note: converts (pre-processes) style.scss to style.css
61
+ end
62
+
63
+
64
+ get '/' do
65
+ @node = NODE
66
+ erb :index
67
+ end
68
+
69
+ post '/send' do
70
+ NODE.on_send( params[:to], params[:amount].to_i )
71
+ settings.connections.each { |out| out << "data: added transaction\n\n" }
72
+ redirect '/'
73
+ end
74
+
75
+
76
+ post '/transactions' do
77
+ if NODE.on_add_transaction(
78
+ params[:from],
79
+ params[:to],
80
+ params[:amount].to_i,
81
+ params[:id]
82
+ )
83
+ settings.connections.each { |out| out << "data: added transaction\n\n" }
84
+ end
85
+ redirect '/'
86
+ end
87
+
88
+ post '/mine' do
89
+ NODE.on_mine!
90
+ redirect '/'
91
+ end
92
+
93
+ post '/peers' do
94
+ NODE.on_add_peer( params[:host], params[:port].to_i )
95
+ redirect '/'
96
+ end
97
+
98
+ post '/peers/:index/delete' do
99
+ NODE.on_delete_peer( params[:index].to_i )
100
+ redirect '/'
101
+ end
102
+
103
+
104
+
105
+ post '/resolve' do
106
+ data = JSON.parse(request.body.read)
107
+ if data['chain'] && NODE.on_resolve( data['chain'] )
108
+ status 202 ### 202 Accepted; see httpstatuses.com/202
109
+ settings.connections.each { |out| out << "data: resolved\n\n" }
110
+ else
111
+ status 200 ### 200 OK
112
+ end
113
+ end
114
+
115
+
116
+ get '/events', provides: 'text/event-stream' do
117
+ stream :keep_open do |out|
118
+ settings.connections << out
119
+ out.callback { settings.connections.delete(out) }
120
+ end
121
+ end
122
+
123
+
124
+ end # class Service
125
+ end # module Tulipmania
126
+
127
+
128
+ # say hello
129
+ puts Tulipmania::Service.banner
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tulipmania
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gerald Bauer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-12-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sass
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: blockchain-lite
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rdoc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hoe
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.16'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.16'
83
+ description: tulipmania - tulips on the blockchain; learn by example from the real
84
+ world (anno 1637) - buy! sell! hodl! enjoy the beauty of admiral of admirals, semper
85
+ augustus, and more; run your own hyper ledger tulip exchange nodes on the blockchain
86
+ peer-to-peer over HTTPprint your own money / cryptocurrency; run your own federated
87
+ central bank nodes on the blockchain peer-to-peer over HTTP; revolutionize the world
88
+ one block at a time
89
+ email: ruby-talk@ruby-lang.org
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files:
93
+ - HISTORY.md
94
+ - Manifest.txt
95
+ - README.md
96
+ files:
97
+ - HISTORY.md
98
+ - Manifest.txt
99
+ - README.md
100
+ - Rakefile
101
+ - lib/tulipmania.rb
102
+ - lib/tulipmania/block.rb
103
+ - lib/tulipmania/blockchain.rb
104
+ - lib/tulipmania/cache.rb
105
+ - lib/tulipmania/exchange.rb
106
+ - lib/tulipmania/ledger.rb
107
+ - lib/tulipmania/node.rb
108
+ - lib/tulipmania/transaction.rb
109
+ - lib/tulipmania/version.rb
110
+ - lib/tulipmania/views/_blockchain.erb
111
+ - lib/tulipmania/views/_ledger.erb
112
+ - lib/tulipmania/views/_peers.erb
113
+ - lib/tulipmania/views/_pending_transactions.erb
114
+ - lib/tulipmania/views/_wallet.erb
115
+ - lib/tulipmania/views/index.erb
116
+ - lib/tulipmania/views/style.scss
117
+ - lib/tulipmania/wallet.rb
118
+ homepage: https://github.com/openblockchains/tulipmania
119
+ licenses:
120
+ - Public Domain
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options:
124
+ - "--main"
125
+ - README.md
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '2.3'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.5.2
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: tulipmania - tulips on the blockchain; learn by example from the real world
144
+ (anno 1637) - buy! sell! hodl! enjoy the beauty of admiral of admirals, semper augustus,
145
+ and more; run your own hyper ledger tulip exchange nodes on the blockchain peer-to-peer
146
+ over HTTPprint your own money / cryptocurrency; run your own federated central bank
147
+ nodes on the blockchain peer-to-peer over HTTP; revolutionize the world one block
148
+ at a time
149
+ test_files: []