tulipmania 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []