tulipmania 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []