tulipmania 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43c6117496ffb6839f9d060bd44fedbf675f6236
4
- data.tar.gz: 9128fd6314ecd688f6769946d9b7c05c906d6c9e
3
+ metadata.gz: 9527e2c2fe08d3e412be319aa0602d2059d9b1b7
4
+ data.tar.gz: 8a206a9f8bc796e3293bc3f3bf4c4b9e45115b5e
5
5
  SHA512:
6
- metadata.gz: a5338475a3b1366689431aba1be3789e3a783b89f87a01ba28cfb85a02da74d632c5cdb124a425e9be324fc744e41fd7312711ac4e27caea6aa39cdb47a32277
7
- data.tar.gz: 8cc2013daab3ece6ce8b1303045ab0bbb6166240114bf51bc2a98795044ba8caa6e6906570703007b25dade78d4d3bcc4272831679ba93d1ea941ae3027d330e
6
+ metadata.gz: 7e9d26f3ac11a9600eefc7ab2bc543103c07afa2a47f036f4fe03b2de80c421c205488b23781feb859bf8ef7aab032a0edf5405e5d6569f056c6ff29c008fddb
7
+ data.tar.gz: 3d3b9bf1f770fcac4f0f45507f711bba029a47bfa5e0e40256b9b4eecb46cf09cef8d8642bf0a4a47b08f715eeed98f8c29310ed3a2d64948208ed5abce17bbb
@@ -0,0 +1,116 @@
1
+ CC0 1.0 Universal
2
+
3
+ Statement of Purpose
4
+
5
+ The laws of most jurisdictions throughout the world automatically confer
6
+ exclusive Copyright and Related Rights (defined below) upon the creator and
7
+ subsequent owner(s) (each and all, an "owner") of an original work of
8
+ authorship and/or a database (each, a "Work").
9
+
10
+ Certain owners wish to permanently relinquish those rights to a Work for the
11
+ purpose of contributing to a commons of creative, cultural and scientific
12
+ works ("Commons") that the public can reliably and without fear of later
13
+ claims of infringement build upon, modify, incorporate in other works, reuse
14
+ and redistribute as freely as possible in any form whatsoever and for any
15
+ purposes, including without limitation commercial purposes. These owners may
16
+ contribute to the Commons to promote the ideal of a free culture and the
17
+ further production of creative, cultural and scientific works, or to gain
18
+ reputation or greater distribution for their Work in part through the use and
19
+ efforts of others.
20
+
21
+ For these and/or other purposes and motivations, and without any expectation
22
+ of additional consideration or compensation, the person associating CC0 with a
23
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25
+ and publicly distribute the Work under its terms, with knowledge of his or her
26
+ Copyright and Related Rights in the Work and the meaning and intended legal
27
+ effect of CC0 on those rights.
28
+
29
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
30
+ protected by copyright and related or neighboring rights ("Copyright and
31
+ Related Rights"). Copyright and Related Rights include, but are not limited
32
+ to, the following:
33
+
34
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
35
+ and translate a Work;
36
+
37
+ ii. moral rights retained by the original author(s) and/or performer(s);
38
+
39
+ iii. publicity and privacy rights pertaining to a person's image or likeness
40
+ depicted in a Work;
41
+
42
+ iv. rights protecting against unfair competition in regards to a Work,
43
+ subject to the limitations in paragraph 4(a), below;
44
+
45
+ v. rights protecting the extraction, dissemination, use and reuse of data in
46
+ a Work;
47
+
48
+ vi. database rights (such as those arising under Directive 96/9/EC of the
49
+ European Parliament and of the Council of 11 March 1996 on the legal
50
+ protection of databases, and under any national implementation thereof,
51
+ including any amended or successor version of such directive); and
52
+
53
+ vii. other similar, equivalent or corresponding rights throughout the world
54
+ based on applicable law or treaty, and any national implementations thereof.
55
+
56
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59
+ and Related Rights and associated claims and causes of action, whether now
60
+ known or unknown (including existing as well as future claims and causes of
61
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
62
+ duration provided by applicable law or treaty (including future time
63
+ extensions), (iii) in any current or future medium and for any number of
64
+ copies, and (iv) for any purpose whatsoever, including without limitation
65
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66
+ the Waiver for the benefit of each member of the public at large and to the
67
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
68
+ shall not be subject to revocation, rescission, cancellation, termination, or
69
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
70
+ by the public as contemplated by Affirmer's express Statement of Purpose.
71
+
72
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
73
+ judged legally invalid or ineffective under applicable law, then the Waiver
74
+ shall be preserved to the maximum extent permitted taking into account
75
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
77
+ non transferable, non sublicensable, non exclusive, irrevocable and
78
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
79
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
80
+ provided by applicable law or treaty (including future time extensions), (iii)
81
+ in any current or future medium and for any number of copies, and (iv) for any
82
+ purpose whatsoever, including without limitation commercial, advertising or
83
+ promotional purposes (the "License"). The License shall be deemed effective as
84
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
85
+ License for any reason be judged legally invalid or ineffective under
86
+ applicable law, such partial invalidity or ineffectiveness shall not
87
+ invalidate the remainder of the License, and in such case Affirmer hereby
88
+ affirms that he or she will not (i) exercise any of his or her remaining
89
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
90
+ and causes of action with respect to the Work, in either case contrary to
91
+ Affirmer's express Statement of Purpose.
92
+
93
+ 4. Limitations and Disclaimers.
94
+
95
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
96
+ surrendered, licensed or otherwise affected by this document.
97
+
98
+ b. Affirmer offers the Work as-is and makes no representations or warranties
99
+ of any kind concerning the Work, express, implied, statutory or otherwise,
100
+ including without limitation warranties of title, merchantability, fitness
101
+ for a particular purpose, non infringement, or the absence of latent or
102
+ other defects, accuracy, or the present or absence of errors, whether or not
103
+ discoverable, all to the greatest extent permissible under applicable law.
104
+
105
+ c. Affirmer disclaims responsibility for clearing rights of other persons
106
+ that may apply to the Work or any use thereof, including without limitation
107
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
108
+ disclaims responsibility for obtaining any necessary consents, permissions
109
+ or other rights required for any use of the Work.
110
+
111
+ d. Affirmer understands and acknowledges that Creative Commons is not a
112
+ party to this document and has no duty or obligation with respect to this
113
+ CC0 or use of the Work.
114
+
115
+ For more information, please see
116
+ <http://creativecommons.org/publicdomain/zero/1.0/>
@@ -1,7 +1,9 @@
1
1
  HISTORY.md
2
+ LICENSE.md
2
3
  Manifest.txt
3
4
  README.md
4
5
  Rakefile
6
+ bin/tulipmania
5
7
  lib/tulipmania.rb
6
8
  lib/tulipmania/block.rb
7
9
  lib/tulipmania/blockchain.rb
@@ -9,6 +11,9 @@ lib/tulipmania/cache.rb
9
11
  lib/tulipmania/exchange.rb
10
12
  lib/tulipmania/ledger.rb
11
13
  lib/tulipmania/node.rb
14
+ lib/tulipmania/pool.rb
15
+ lib/tulipmania/service.rb
16
+ lib/tulipmania/tool.rb
12
17
  lib/tulipmania/transaction.rb
13
18
  lib/tulipmania/version.rb
14
19
  lib/tulipmania/views/_blockchain.erb
data/README.md CHANGED
@@ -12,8 +12,59 @@ run your own hyper ledger tulip exchange nodes on the blockchain peer-to-peer ov
12
12
 
13
13
 
14
14
 
15
+ ## Command Line
15
16
 
16
- ## Development
17
+ Use the `tulipmania` command line tool. Try:
18
+
19
+ ```
20
+ $ tulipmania -h
21
+ ```
22
+
23
+ resulting in:
24
+
25
+ ```
26
+ Usage: tulipmania [options]
27
+
28
+ Wallet options:
29
+ -n, --name=NAME Address name (default: Anne)
30
+
31
+ Server (node) options:
32
+ -o, --host HOST listen on HOST (default: 0.0.0.0)
33
+ -p, --port PORT use PORT (default: 4567)
34
+ -h, --help Prints this help
35
+ ```
36
+
37
+ To start a new (network) node using the default wallet
38
+ address (that is, Anne) and the default server host and port settings
39
+ use:
40
+
41
+ ```
42
+ $ tulipmania
43
+ ```
44
+
45
+ Stand back ten feets :-) while starting up the machinery.
46
+ Ready to exchange tulips on the blockchain?
47
+ In your browser open up the page e.g. `http://localhost:4567`. Voila!
48
+
49
+ ![](tulipmania.png)
50
+
51
+
52
+
53
+ Note: You can start a second node on your computer -
54
+ make sure to use a different port (use the `-p/--port` option)
55
+ and (recommended)
56
+ a different wallet address (use the `-n/--name` option).
57
+ Example:
58
+
59
+ ```
60
+ $ tulipmania -p 5678 -n Vincent
61
+ ```
62
+
63
+ Happy mining!
64
+
65
+
66
+
67
+ ## Local Development Setup
17
68
 
18
69
  For local development - clone or download (and unzip) the tulipmania code repo.
19
70
  Next install all dependencies using bundler with a Gemfile e.g.:
@@ -34,8 +85,7 @@ run
34
85
  $ bundle ## will use the Gemfile (see above)
35
86
  ```
36
87
 
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:
88
+ and now you're ready to run your own tulipmania server node. Use the [`config.ru`](config.ru) script for rack:
39
89
 
40
90
  ``` ruby
41
91
  # config.ru
@@ -47,15 +97,13 @@ require 'tulipmania'
47
97
  run Tulipmania::Service
48
98
  ```
49
99
 
50
- and startup the money printing machine using rackup - the rack command line tool:
100
+ and startup the tulip exchange machinery using rackup - the rack command line tool:
51
101
 
52
102
  ```
53
103
  $ rackup ## will use the config.ru - rackup configuration script (see above).
54
104
  ```
55
105
 
56
- In your browser open up the page e.g. `http://localhost:9292`. Voila!
57
-
58
-
106
+ In your browser open up the page e.g. `http://localhost:9292`. Voila! Happy mining!
59
107
 
60
108
 
61
109
  ## License
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ Hoe.spec 'tulipmania' do
5
5
 
6
6
  self.version = Tulipmania::VERSION
7
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'
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 HTTP; revolutionize the world one block at a time'
9
9
  self.description = summary
10
10
 
11
11
  self.urls = ['https://github.com/openblockchains/tulipmania']
@@ -20,7 +20,7 @@ Hoe.spec 'tulipmania' do
20
20
  self.extra_deps = [
21
21
  ['sinatra', '>=2.0'],
22
22
  ['sass'], ## used for css style preprocessing (scss)
23
- ['blockchain-lite', '>=1.2'],
23
+ ['blockchain-lite', '>=1.3.1'],
24
24
  ]
25
25
 
26
26
  self.licenses = ['Public Domain']
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ###################
4
+ # == DEV TIPS:
5
+ #
6
+ # For local testing run like:
7
+ #
8
+ # ruby -Ilib bin/tulipmania
9
+ #
10
+ # Set the executable bit in Linux. Example:
11
+ #
12
+ # % chmod a+x bin/tulipmania
13
+ #
14
+
15
+ require 'tulipmania'
16
+
17
+ Tulipmania.main
@@ -5,11 +5,14 @@ require 'json'
5
5
  require 'digest'
6
6
  require 'net/http'
7
7
  require 'set'
8
+ require 'optparse'
8
9
  require 'pp'
9
10
 
10
11
 
11
12
  ### 3rd party gems
12
13
  require 'sinatra/base' # note: use "modular" sinatra app / service
14
+
15
+ require 'merkletree'
13
16
  require 'blockchain-lite/proof_of_work/block' # note: use proof-of-work block only (for now)
14
17
 
15
18
 
@@ -19,111 +22,89 @@ require 'tulipmania/block'
19
22
  require 'tulipmania/cache'
20
23
  require 'tulipmania/transaction'
21
24
  require 'tulipmania/blockchain'
25
+ require 'tulipmania/pool'
22
26
  require 'tulipmania/exchange'
23
27
  require 'tulipmania/ledger'
24
28
  require 'tulipmania/wallet'
25
29
 
26
30
  require 'tulipmania/node'
31
+ require 'tulipmania/service'
27
32
 
28
-
33
+ require 'tulipmania/tool'
29
34
 
30
35
 
31
36
  module Tulipmania
32
37
 
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' )
38
+ class Configuration
39
+ ## user/node settings
40
+ attr_accessor :address ## single wallet address (for now "clear" name e.g. Anne, Vincent, etc.)
42
41
 
42
+ WALLET_ADDRESSES = ['Anne', 'Vicent', 'Ruben', 'Julia', 'Luuk',
43
+ 'Daisy', 'Max', 'Martijn', 'Naomi', 'Mina',
44
+ 'Isabel'
45
+ ]
43
46
 
44
- PUBLIC_FOLDER = "#{Tulipmania.root}/lib/tulipmania/public"
45
- VIEWS_FOLDER = "#{Tulipmania.root}/lib/tulipmania/views"
47
+ ## system/blockchain settings
48
+ attr_accessor :coinbase
49
+ attr_accessor :mining_reward
50
+ attr_accessor :tulips ## rename to assets/commodities/etc. - why? why not?
46
51
 
47
- puts "[tulipmania] boot - setting public folder to: #{PUBLIC_FOLDER}"
48
- puts "[tulipmania] boot - setting views folder to: #{VIEWS_FOLDER}"
52
+ ## note: add a (†) coinbase / grower marker
53
+ TULIP_GROWERS = ['Dutchgrown†', 'Keukenhof†', 'Flowers†',
54
+ 'Bloom & Blossom†', 'Teleflora†'
55
+ ]
49
56
 
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
57
+ TULIPS = ['Semper Augustus',
58
+ 'Admiral van Eijck',
59
+ 'Admiral of Admirals',
60
+ 'Red Impression',
61
+ 'Bloemendaal Sunset',
62
+ ]
52
63
 
53
- set :static, true # set up static file routing -- check - still needed?
64
+ def initialize
65
+ ## try default setup via ENV variables
66
+ ## pick "random" address if nil (none passed in)
67
+ @address = ENV[ 'TULIPMANIA_NAME'] || rand_address()
54
68
 
69
+ @coinbase = TULIP_GROWERS ## use a different name for coinbase - why? why not?
70
+ ## note: for now is an array (multiple growsers)
55
71
 
56
- set connections: []
72
+ @tulips = TULIPS ## change name to commodities or assets - why? why not?
73
+ @mining_reward = 5
74
+ end
57
75
 
58
76
 
59
- get '/style.css' do
60
- scss :style ## note: converts (pre-processes) style.scss to style.css
61
- end
77
+ def rand_address() WALLET_ADDRESSES[rand( WALLET_ADDRESSES.size )]; end
78
+ def rand_tulip() @tulips[rand( @tulips.size )]; end
79
+ def rand_coinbase() @coinbase[rand( @coinbase.size )]; end
62
80
 
81
+ def coinbase?( address ) ## check/todo: use wallet - why? why not? (for now wallet==address)
82
+ @coinbase.include?( address )
83
+ end
84
+ end # class Configuration
63
85
 
64
- get '/' do
65
- @node = NODE
66
- erb :index
67
- end
68
86
 
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
87
+ ## lets you use
88
+ ## Tulipmania.configure do |config|
89
+ ## config.address = 'Anne'
90
+ ## end
74
91
 
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" }
92
+ def self.configure
93
+ yield( config )
84
94
  end
85
- redirect '/'
86
- end
87
-
88
- post '/mine' do
89
- NODE.on_mine!
90
- redirect '/'
91
- end
92
95
 
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
96
+ def self.config
97
+ @config ||= Configuration.new
112
98
  end
113
- end
114
99
 
115
100
 
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) }
101
+ ## add command line binary (tool) e.g. $ try centralbank -h
102
+ def self.main
103
+ Tool.new.run(ARGV)
120
104
  end
121
- end
122
-
123
-
124
- end # class Service
125
105
  end # module Tulipmania
126
106
 
127
107
 
108
+
128
109
  # say hello
129
110
  puts Tulipmania::Service.banner
@@ -3,13 +3,22 @@
3
3
 
4
4
  class Blockchain
5
5
  extend Forwardable
6
- def_delegators :@chain, :[], :size, :each, :empty?, :last
6
+ def_delegators :@chain, :[], :size, :each, :empty?, :any?, :last
7
7
 
8
8
 
9
9
  def initialize( chain=[] )
10
10
  @chain = chain
11
11
  end
12
12
 
13
+ def timestamp1637
14
+ ## change year to 1637 :-)
15
+ ## note: time (uses signed integer e.g. epoch/unix time starting in 1970 with 0)
16
+ ## todo: add nano/mili-seconds - why? why not? possible?
17
+ now = Time.now.utc.to_datetime
18
+ past = DateTime.new( 1637, now.month, now.mday, now.hour, now.min, now.sec, now.zone )
19
+ past
20
+ end
21
+
13
22
  def <<( txs )
14
23
  ## todo: check if is block or array
15
24
  ## if array (of transactions) - auto-add (build) block
@@ -17,9 +26,9 @@ class Blockchain
17
26
  ## for now just use transactions (keep it simple :-)
18
27
 
19
28
  if @chain.size == 0
20
- block = Block.first( txs )
29
+ block = Block.first( txs, timestamp: timestamp1637 )
21
30
  else
22
- block = Block.next( @chain.last, txs )
31
+ block = Block.next( @chain.last, txs, timestamp: timestamp1637 )
23
32
  end
24
33
  @chain << block
25
34
  end
@@ -6,14 +6,14 @@ class Cache
6
6
  end
7
7
 
8
8
  def write( data )
9
- File.open( @name, 'w' ) do |f| ## use utf8 - why? why not??
9
+ File.open( @name, 'w:utf-8' ) do |f|
10
10
  f.write JSON.pretty_generate( data )
11
11
  end
12
12
  end
13
13
 
14
14
  def read
15
15
  if File.exists?( @name )
16
- data = File.read( @name ) ## use utf8 - why? why not??
16
+ data = File.open( @name, 'r:bom|utf-8' ).read
17
17
  JSON.parse( data )
18
18
  else
19
19
  nil
@@ -5,24 +5,24 @@
5
5
  class Exchange
6
6
  attr_reader :pending, :chain, :ledger
7
7
 
8
- COINBASE = "COINBASE"
9
- MINING_REWARD = 5
10
-
11
8
 
12
9
  def initialize( address )
13
10
  @address = address
14
11
 
15
- @cache = Cache.new( 'data.json' )
12
+ @cache = Cache.new( "data.#{address.downcase}.json" )
16
13
  h = @cache.read
17
14
  if h
18
15
  ## restore blockchain
19
16
  @chain = Blockchain.from_json( h['chain'] )
20
17
  ## restore pending transactions too
21
- @pending = h['transactions'].map { |h_tx| Tx.from_h( h_tx ) }
18
+ @pending = Pool.from_json( h['transactions'] )
22
19
  else
23
20
  @chain = Blockchain.new
24
- @chain << [Tx.new( COINBASE, @address, MINING_REWARD )] # genesis (big bang!) starter block
25
- @pending = []
21
+ @chain << [Tx.new( Tulipmania.config.rand_coinbase,
22
+ @address,
23
+ Tulipmania.config.mining_reward,
24
+ Tulipmania.config.rand_tulip )] # genesis (big bang!) starter block
25
+ @pending = Pool.new
26
26
  end
27
27
 
28
28
  ## update ledger (balances) with confirmed transactions
@@ -32,11 +32,14 @@ class Exchange
32
32
 
33
33
 
34
34
  def mine_block!
35
- add_transaction( Tx.new( COINBASE, @address, MINING_REWARD ))
35
+ add_transaction( Tx.new( Tulipmania.config.rand_coinbase,
36
+ @address,
37
+ Tulipmania.config.mining_reward,
38
+ Tulipmania.config.rand_tulip ))
36
39
 
37
40
  ## add mined (w/ computed/calculated hash) block
38
- @chain << @pending
39
- @pending = []
41
+ @chain << @pending.transactions
42
+ @pending = Pool.new
40
43
 
41
44
  ## update ledger (balances) with new confirmed transactions
42
45
  @ledger = Ledger.new( @chain )
@@ -45,11 +48,11 @@ class Exchange
45
48
  end
46
49
 
47
50
 
48
- def sufficient_funds?( wallet, amount )
51
+ def sufficient_tulips?( wallet, qty, what )
49
52
  ## (convenience) delegate for ledger
50
53
  ## todo/check: use address instead of wallet - why? why not?
51
54
  ## for now single address wallet (that is, wallet==address)
52
- @ledger.sufficient_funds?( wallet, amount )
55
+ @ledger.sufficient_tulips?( wallet, qty, what )
53
56
  end
54
57
 
55
58
 
@@ -76,13 +79,9 @@ class Exchange
76
79
  ## update ledger (balances) with new confirmed transactions
77
80
  @ledger = Ledger.new( @chain )
78
81
 
82
+ ## document - keep only pending (unconfirmed) transaction not yet in blockchain ????
83
+ @pending.update!( @chain.transactions)
79
84
 
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
85
  @cache.write as_json
87
86
  return true
88
87
  else
@@ -94,7 +93,7 @@ class Exchange
94
93
 
95
94
  def as_json
96
95
  { chain: @chain.as_json,
97
- transactions: @pending.map { |tx| tx.to_h }
96
+ transactions: @pending.as_json
98
97
  }
99
98
  end
100
99
 
@@ -107,7 +106,7 @@ private
107
106
 
108
107
  ## todo: use chain.include? to check for include
109
108
  ## avoid loop and create new array for check!!!
110
- (@chain.transactions + @pending).none? { |tx| tx_new.id == tx.id }
109
+ (@chain.transactions + @pending.transactions).none? { |tx| tx_new.id == tx.id }
111
110
  end
112
111
 
113
112
  end ## class Exchange
@@ -9,9 +9,12 @@ class Ledger
9
9
  end
10
10
  end
11
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
12
+ def sufficient_tulips?( wallet, qty, what )
13
+ return true if Tulipmania.config.coinbase?( wallet )
14
+
15
+ @wallets.has_key?( wallet ) &&
16
+ @wallets[wallet].has_key?( what ) &&
17
+ @wallets[wallet][what] - qty >= 0
15
18
  end
16
19
 
17
20
 
@@ -19,10 +22,11 @@ private
19
22
 
20
23
  def apply_transactions( transactions )
21
24
  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
25
+ if sufficient_tulips?(tx.from, tx.qty, tx.what)
26
+ @wallets[tx.from][tx.what] -= tx.qty unless Tulipmania.config.coinbase?( tx.from )
27
+ @wallets[tx.to] ||= {} ## make sure wallet exists (e.g. init with empty hash {})
28
+ @wallets[tx.to][tx.what] ||= 0
29
+ @wallets[tx.to][tx.what] += tx.qty
26
30
  end
27
31
  end
28
32
  end
@@ -1,29 +1,23 @@
1
1
 
2
2
 
3
3
  class Node
4
- attr_reader :id, :peers, :wallet, :bank
4
+ attr_reader :id, :peers, :wallet, :exchange
5
5
 
6
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
7
+ def initialize( address: )
8
+ @id = SecureRandom.uuid
9
+ @peers = []
10
+ @wallet = Wallet.new( address )
11
+ @exchange = Exchange.new @wallet.address
17
12
  end
18
13
 
19
14
 
20
-
21
15
  def on_add_peer( host, port )
22
16
  @peers << [host, port]
23
17
  @peers.uniq!
24
18
  # TODO/FIX: no need to send to every peer, just the new one
25
19
  send_chain_to_peers
26
- @bank.pending.each { |tx| send_transaction_to_peers( tx ) }
20
+ @exchange.pending.each { |tx| send_transaction_to_peers( tx ) }
27
21
  end
28
22
 
29
23
  def on_delete_peer( index )
@@ -31,10 +25,10 @@ class Node
31
25
  end
32
26
 
33
27
 
34
- def on_add_transaction( from, to, amount, id )
28
+ def on_add_transaction( from, to, qty, what, id )
35
29
  ## 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 )
30
+ tx = Tx.new( from, to, qty, what, id )
31
+ if @exchange.sufficient_tulips?( tx.from, tx.qty, tx.what ) && @exchange.add_transaction( tx )
38
32
  send_transaction_to_peers( tx )
39
33
  return true
40
34
  else
@@ -42,9 +36,9 @@ class Node
42
36
  end
43
37
  end
44
38
 
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 )
39
+ def on_send( to, qty, what )
40
+ tx = @wallet.generate_transaction( to, qty, what )
41
+ if @exchange.sufficient_tulips?( tx.from, tx.qty, tx.what ) && @exchange.add_transaction( tx )
48
42
  send_transaction_to_peers( tx )
49
43
  return true
50
44
  else
@@ -54,13 +48,13 @@ class Node
54
48
 
55
49
 
56
50
  def on_mine!
57
- @bank.mine_block!
51
+ @exchange.mine_block!
58
52
  send_chain_to_peers
59
53
  end
60
54
 
61
55
  def on_resolve( data )
62
56
  chain_new = Blockchain.from_json( data )
63
- if @bank.resolve!( chain_new )
57
+ if @exchange.resolve!( chain_new )
64
58
  send_chain_to_peers
65
59
  return true
66
60
  else
@@ -73,7 +67,7 @@ class Node
73
67
  private
74
68
 
75
69
  def send_chain_to_peers
76
- data = JSON.pretty_generate( @bank.as_json ) ## payload in json
70
+ data = JSON.pretty_generate( @exchange.as_json ) ## payload in json
77
71
  @peers.each do |(host, port)|
78
72
  Net::HTTP.post(URI::HTTP.build(host: host, port: port, path: '/resolve'), data )
79
73
  end
@@ -0,0 +1,42 @@
1
+ ####################################
2
+ # pending (unconfirmed) transactions (mem) pool
3
+
4
+ class Pool
5
+ extend Forwardable
6
+ def_delegators :@transactions, :[], :size, :each, :empty?, :any?
7
+
8
+
9
+ def initialize( transactions=[] )
10
+ @transactions = transactions
11
+ end
12
+
13
+ def transactions() @transactions; end
14
+
15
+ def <<( tx )
16
+ @transactions << tx
17
+ end
18
+
19
+
20
+ def update!( txns_confirmed )
21
+ ## find a better name?
22
+ ## remove confirmed transactions from pool
23
+
24
+ ## document - keep only pending transaction not yet (confirmed) in blockchain ????
25
+ @transactions = @transactions.select do |tx_unconfirmed|
26
+ txns_confirmed.none? { |tx_confirmed| tx_confirmed.id == tx_unconfirmed.id }
27
+ end
28
+ end
29
+
30
+
31
+
32
+ def as_json
33
+ @transactions.map { |tx| tx.to_h }
34
+ end
35
+
36
+ def self.from_json( data )
37
+ ## note: assumes data is an array of block records/objects in json
38
+ transactions = data.map { |h| Tx.from_h( h ) }
39
+ self.new( transactions )
40
+ end
41
+
42
+ end # class Pool
@@ -0,0 +1,123 @@
1
+
2
+
3
+ module Tulipmania
4
+
5
+ class Service < Sinatra::Base
6
+
7
+ def self.banner
8
+ "tulipmania/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] on Sinatra/#{Sinatra::VERSION} (#{ENV['RACK_ENV']})"
9
+ end
10
+
11
+
12
+ PUBLIC_FOLDER = "#{Tulipmania.root}/lib/tulipmania/public"
13
+ VIEWS_FOLDER = "#{Tulipmania.root}/lib/tulipmania/views"
14
+
15
+ set :public_folder, PUBLIC_FOLDER # set up the static dir (with images/js/css inside)
16
+ set :views, VIEWS_FOLDER # set up the views dir
17
+
18
+ set :static, true # set up static file routing -- check - still needed?
19
+
20
+
21
+ set connections: []
22
+
23
+
24
+ get '/style.css' do
25
+ scss :style ## note: converts (pre-processes) style.scss to style.css
26
+ end
27
+
28
+
29
+ get '/' do
30
+ @node = node
31
+ erb :index
32
+ end
33
+
34
+ post '/send' do
35
+ node.on_send( params[:to], params[:qty].to_i, params[:what] )
36
+ settings.connections.each { |out| out << "data: added transaction\n\n" }
37
+ redirect '/'
38
+ end
39
+
40
+
41
+ post '/transactions' do
42
+ if node.on_add_transaction(
43
+ params[:from],
44
+ params[:to],
45
+ params[:qty].to_i,
46
+ params[:what],
47
+ params[:id]
48
+ )
49
+ settings.connections.each { |out| out << "data: added transaction\n\n" }
50
+ end
51
+ redirect '/'
52
+ end
53
+
54
+ post '/mine' do
55
+ node.on_mine!
56
+ redirect '/'
57
+ end
58
+
59
+ post '/peers' do
60
+ node.on_add_peer( params[:host], params[:port].to_i )
61
+ redirect '/'
62
+ end
63
+
64
+ post '/peers/:index/delete' do
65
+ node.on_delete_peer( params[:index].to_i )
66
+ redirect '/'
67
+ end
68
+
69
+
70
+
71
+ post '/resolve' do
72
+ data = JSON.parse(request.body.read)
73
+ if data['chain'] && node.on_resolve( data['chain'] )
74
+ status 202 ### 202 Accepted; see httpstatuses.com/202
75
+ settings.connections.each { |out| out << "data: resolved\n\n" }
76
+ else
77
+ status 200 ### 200 OK
78
+ end
79
+ end
80
+
81
+
82
+ get '/events', provides: 'text/event-stream' do
83
+ stream :keep_open do |out|
84
+ settings.connections << out
85
+ out.callback { settings.connections.delete(out) }
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ #########
92
+ ## return network node (built and configured on first use)
93
+ ## fix: do NOT use @@ - use a class level method or something
94
+ def node
95
+ if defined?( @@node )
96
+ @@node
97
+ else
98
+ puts "[debug] tulipmania - build (network) node (address: #{Tulipmania.config.address})"
99
+ @@node = Node.new( address: Tulipmania.config.address )
100
+ @@node
101
+ end
102
+ ####
103
+ ## check why this is a syntax error:
104
+ ## @node ||= do
105
+ ## puts "[debug] tulipmania - build (network) node (address: #{Tulipmania.config.address})"
106
+ ## @node = Node.new( address: Tulipmania.config.address )
107
+ ## end
108
+ end
109
+
110
+
111
+ ############
112
+ ## helpers
113
+
114
+ def fmt_tulips( hash )
115
+ lines = []
116
+ hash.each do |what,qty|
117
+ lines << "#{what} × #{qty}"
118
+ end
119
+ lines.join( ', ' )
120
+ end
121
+
122
+ end # class Service
123
+ end # module Tulipmania
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ module Tulipmania
5
+
6
+ class Tool
7
+
8
+ def run( args )
9
+ opts = {}
10
+
11
+ parser = OptionParser.new do |cmd|
12
+ cmd.banner = "Usage: tulipmania [options]"
13
+
14
+ cmd.separator ""
15
+ cmd.separator " Wallet options:"
16
+
17
+ cmd.on("-n", "--name=NAME", "Address name (default: Anne)") do |name|
18
+ ## use -a or --adr or --address as option flag - why? why not?
19
+ ## note: default now picks a random address from WALLET_ADDRESSES
20
+ opts[:address] = name
21
+ end
22
+
23
+
24
+ cmd.separator ""
25
+ cmd.separator " Server (node) options:"
26
+
27
+ cmd.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") do |host|
28
+ opts[:Host] = host ## note: rack server handler expects :Host
29
+ end
30
+
31
+ cmd.on("-p", "--port PORT", "use PORT (default: 4567)") do |port|
32
+ opts[:Port] = port ## note: rack server handler expects :Post
33
+ end
34
+
35
+ cmd.on("-h", "--help", "Prints this help") do
36
+ puts cmd
37
+ exit
38
+ end
39
+ end
40
+
41
+ parser.parse!( args )
42
+ pp opts
43
+
44
+
45
+ ###################
46
+ ## startup server (via rack interface/handler)
47
+
48
+ app_class = Service ## use app = Service.new -- why? why not?
49
+ host = opts[:Host] || '0.0.0.0'
50
+ port = opts[:Port] || '4567'
51
+
52
+ Tulipmania.configure do |config|
53
+ config.address = opts[:address] || 'Anne'
54
+ end
55
+
56
+ Rack::Handler::WEBrick.run( app_class, Host: host, Port: port ) do |server|
57
+ ## todo: add traps here - why, why not??
58
+ end
59
+
60
+
61
+ end ## method run
62
+
63
+
64
+ end ## class Tool
65
+
66
+ end ## module Tulipmania
@@ -2,21 +2,22 @@
2
2
 
3
3
  class Transaction
4
4
 
5
- attr_reader :from, :to, :amount, :id
5
+ attr_reader :from, :to, :qty, :what, :id
6
6
 
7
- def initialize( from, to, amount, id=SecureRandom.uuid )
7
+ def initialize( from, to, qty, what, id=SecureRandom.uuid )
8
8
  @from = from
9
9
  @to = to
10
- @amount = amount
10
+ @qty = qty
11
+ @what = what # tulip name - change to name or title - why? why not?
11
12
  @id = id
12
13
  end
13
14
 
14
15
  def self.from_h( hash )
15
- self.new *hash.values_at( 'from', 'to', 'amount', 'id' )
16
+ self.new *hash.values_at( 'from', 'to', 'qty', 'what', 'id' )
16
17
  end
17
18
 
18
19
  def to_h
19
- { from: @from, to: @to, amount: @amount, id: @id }
20
+ { from: @from, to: @to, qty: @qty, what: @what, id: @id }
20
21
  end
21
22
 
22
23
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Tulipmania
4
4
 
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
 
7
7
  def self.root
8
8
  "#{File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )}"
@@ -1,14 +1,14 @@
1
1
  <div class="blockchain">
2
2
  <h2>
3
3
  Blockchain<br>
4
- <span><%= @node.bank.chain.size %> blocks</span>
4
+ <span><%= @node.exchange.chain.size %> blocks</span>
5
5
  </h2>
6
6
  <form action="/mine" method="post">
7
7
  <input type="submit" class="button" value="Mine a Block">
8
8
  </form>
9
9
 
10
10
  <div class="blocks">
11
- <% @node.bank.chain.last(10).reverse.each do |block| %>
11
+ <% @node.exchange.chain.last(10).reverse.each do |block| %>
12
12
  <div class="block">
13
13
  <div class="header">
14
14
  <%= block.index %> — <%= block.timestamp %><br>
@@ -20,10 +20,10 @@
20
20
  <%= tx.id[0..2] %>
21
21
  </td>
22
22
  <td>
23
- $<%= tx.amount %>
23
+ <%= tx.from[0..15] %> → <%= tx.to[0..15] %>
24
24
  </td>
25
25
  <td>
26
- <%= tx.from[0..7] %> <%= tx.to[0..7] %>
26
+ <%= tx.what %> × <%= tx.qty %>
27
27
  </td>
28
28
  </tr>
29
29
  <% end %>
@@ -31,4 +31,7 @@
31
31
  </div>
32
32
  <% end %>
33
33
  </div>
34
+ <p>
35
+ †: Grower Transaction - New Tulips on the Market!
36
+ </p>
34
37
  </div>
@@ -3,12 +3,12 @@
3
3
  <table>
4
4
  <tr>
5
5
  <th>Address</th>
6
- <th>Balance</th>
6
+ <th>Tulips</th>
7
7
  </tr>
8
- <% @node.bank.ledger.wallets.each do |address, amount| %>
8
+ <% @node.exchange.ledger.wallets.each do |address, tulips| %>
9
9
  <tr>
10
10
  <td><%= address[0..15] %></td>
11
- <td>$<%= amount %></td>
11
+ <td><%= fmt_tulips( tulips ) %></td>
12
12
  </tr>
13
13
  <% end %>
14
14
  </table>
@@ -1,18 +1,20 @@
1
1
  <div class="pending-transactions">
2
2
  <h2>Pending Transactions</h2>
3
- <% if @node.bank.pending.any? %>
3
+ <% if @node.exchange.pending.any? %>
4
4
  <table>
5
5
  <tr>
6
6
  <th>From</th>
7
7
  <th>To</th>
8
- <th>$</th>
8
+ <th>What</th>
9
+ <th>Qty &nbsp;</th>
9
10
  <th>Id</th>
10
11
  </tr>
11
- <% @node.bank.pending.each do |tx| %>
12
+ <% @node.exchange.pending.each do |tx| %>
12
13
  <tr>
13
14
  <td><%= tx.from[0..15] %></td>
14
15
  <td><%= tx.to[0..15] %></td>
15
- <td><%= tx.amount %></td>
16
+ <td><%= tx.what %></td>
17
+ <td>× <%= tx.qty %></td>
16
18
  <td><%= tx.id[0..2] %></td>
17
19
  </tr>
18
20
  <% end %>
@@ -3,14 +3,38 @@
3
3
  <div class="details">
4
4
  <h2>Address</h2>
5
5
  <div><%= @node.wallet.address %></div>
6
- <h2>Balance</h2>
7
- <div class="balance">$<%= @node.bank.ledger.wallets[@node.wallet.address] || 0 %></div>
6
+ <h2>Tulips</h2>
7
+ <div class="balance"><%= fmt_tulips(@node.exchange.ledger.wallets[@node.wallet.address] || {}) %></div>
8
8
  </div>
9
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>
10
+
11
+
12
+ <div style="display: flex; align-items: center; padding: 4px;">
13
+ <div>
14
+ <label for="to">To</label>
15
+ <input type="text" name="to" id="to" placeholder="address" size="7" />
16
+ </div>
17
+ <div>
18
+ <% Tulipmania.config.tulips.each_with_index do |tulip,i|
19
+ enabled = @node.exchange.ledger.sufficient_tulips?( @node.wallet.address, 1, tulip )
20
+ next unless enabled ## skip tulips w/o balance for now
21
+ %>
22
+ <div style="padding: 2px;">
23
+ <input type="radio" id="what<%= i %>" name="what" value="<%= tulip %>"
24
+ <%= enabled ? '' : 'disabled' %> >
25
+ <label for="what<%= i %>" style="text-align: left;"
26
+ <%= enabled ? '' : 'disabled' %> ><%= tulip %></label>
27
+ </div>
28
+ <% end %>
29
+ </div>
30
+
31
+ <div>
32
+ <label for="qty">Qty</label>
33
+ <input type="text" name="qty" id="qty" placeholder="1,2,3,..." size="3" />
34
+ </div>
35
+ <div style="padding-left: 6px;">
36
+ <input type="submit" class="button" value="Send" />
37
+ </div>
38
+ </div> <!-- inner flex box -->
15
39
  </form>
16
40
  </div>
@@ -1,12 +1,12 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Central Bank Node</title>
4
+ <title>Tulipmania (Anno 1637) Node</title>
5
5
  <link rel="stylesheet" href="style.css">
6
6
  </head>
7
7
  <body>
8
8
 
9
- <h1>Central Bank Node</h1>
9
+ <h1>Tulipmania (Anno 1637) Node</h1>
10
10
 
11
11
  <div class="columns">
12
12
  <div class="left">
@@ -16,11 +16,11 @@ body {
16
16
  display: flex;
17
17
 
18
18
  .left {
19
- width: 66%;
19
+ width: 50%;
20
20
  }
21
21
 
22
22
  .right {
23
- width: 34%;
23
+ width: 50%;
24
24
  }
25
25
  }
26
26
 
@@ -45,7 +45,7 @@ h2 span {
45
45
 
46
46
  label {
47
47
  display: inline-block;
48
- width: 80px;
48
+ // width: 80px;
49
49
  text-align: right;
50
50
  padding-right: 10px;
51
51
  }
@@ -105,7 +105,7 @@ input[type=submit].small {
105
105
  }
106
106
 
107
107
  .balance {
108
- font-size: 30px;
108
+ font-size: 22px;
109
109
  }
110
110
  }
111
111
 
@@ -8,8 +8,8 @@ class Wallet
8
8
  @address = address
9
9
  end
10
10
 
11
- def generate_transaction( to, amount )
12
- Tx.new( address, to, amount )
11
+ def generate_transaction( to, qty, what )
12
+ Tx.new( address, to, qty, what )
13
13
  end
14
14
 
15
15
  end # class Wallet
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tulipmania
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-18 00:00:00.000000000 Z
11
+ date: 2017-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.2'
47
+ version: 1.3.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.2'
54
+ version: 1.3.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rdoc
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -83,21 +83,23 @@ dependencies:
83
83
  description: tulipmania - tulips on the blockchain; learn by example from the real
84
84
  world (anno 1637) - buy! sell! hodl! enjoy the beauty of admiral of admirals, semper
85
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
86
+ peer-to-peer over HTTP; revolutionize the world one block at a time
89
87
  email: ruby-talk@ruby-lang.org
90
- executables: []
88
+ executables:
89
+ - tulipmania
91
90
  extensions: []
92
91
  extra_rdoc_files:
93
92
  - HISTORY.md
93
+ - LICENSE.md
94
94
  - Manifest.txt
95
95
  - README.md
96
96
  files:
97
97
  - HISTORY.md
98
+ - LICENSE.md
98
99
  - Manifest.txt
99
100
  - README.md
100
101
  - Rakefile
102
+ - bin/tulipmania
101
103
  - lib/tulipmania.rb
102
104
  - lib/tulipmania/block.rb
103
105
  - lib/tulipmania/blockchain.rb
@@ -105,6 +107,9 @@ files:
105
107
  - lib/tulipmania/exchange.rb
106
108
  - lib/tulipmania/ledger.rb
107
109
  - lib/tulipmania/node.rb
110
+ - lib/tulipmania/pool.rb
111
+ - lib/tulipmania/service.rb
112
+ - lib/tulipmania/tool.rb
108
113
  - lib/tulipmania/transaction.rb
109
114
  - lib/tulipmania/version.rb
110
115
  - lib/tulipmania/views/_blockchain.erb
@@ -143,7 +148,5 @@ specification_version: 4
143
148
  summary: tulipmania - tulips on the blockchain; learn by example from the real world
144
149
  (anno 1637) - buy! sell! hodl! enjoy the beauty of admiral of admirals, semper augustus,
145
150
  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
151
+ over HTTP; revolutionize the world one block at a time
149
152
  test_files: []