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 +4 -4
- data/LICENSE.md +116 -0
- data/Manifest.txt +5 -0
- data/README.md +55 -7
- data/Rakefile +2 -2
- data/bin/tulipmania +17 -0
- data/lib/tulipmania.rb +55 -74
- data/lib/tulipmania/blockchain.rb +12 -3
- data/lib/tulipmania/cache.rb +2 -2
- data/lib/tulipmania/exchange.rb +19 -20
- data/lib/tulipmania/ledger.rb +11 -7
- data/lib/tulipmania/node.rb +16 -22
- data/lib/tulipmania/pool.rb +42 -0
- data/lib/tulipmania/service.rb +123 -0
- data/lib/tulipmania/tool.rb +66 -0
- data/lib/tulipmania/transaction.rb +6 -5
- data/lib/tulipmania/version.rb +1 -1
- data/lib/tulipmania/views/_blockchain.erb +7 -4
- data/lib/tulipmania/views/_ledger.erb +3 -3
- data/lib/tulipmania/views/_pending_transactions.erb +6 -4
- data/lib/tulipmania/views/_wallet.erb +31 -7
- data/lib/tulipmania/views/index.erb +2 -2
- data/lib/tulipmania/views/style.scss +4 -4
- data/lib/tulipmania/wallet.rb +2 -2
- metadata +14 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9527e2c2fe08d3e412be319aa0602d2059d9b1b7
|
|
4
|
+
data.tar.gz: 8a206a9f8bc796e3293bc3f3bf4c4b9e45115b5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e9d26f3ac11a9600eefc7ab2bc543103c07afa2a47f036f4fe03b2de80c421c205488b23781feb859bf8ef7aab032a0edf5405e5d6569f056c6ff29c008fddb
|
|
7
|
+
data.tar.gz: 3d3b9bf1f770fcac4f0f45507f711bba029a47bfa5e0e40256b9b4eecb46cf09cef8d8642bf0a4a47b08f715eeed98f8c29310ed3a2d64948208ed5abce17bbb
|
data/LICENSE.md
ADDED
|
@@ -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/>
|
data/Manifest.txt
CHANGED
|
@@ -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
|
-
|
|
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
|
+

|
|
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
|
|
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
|
|
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
|
|
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.
|
|
23
|
+
['blockchain-lite', '>=1.3.1'],
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
self.licenses = ['Public Domain']
|
data/bin/tulipmania
ADDED
|
@@ -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
|
data/lib/tulipmania.rb
CHANGED
|
@@ -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
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
52
|
+
## note: add a (†) coinbase / grower marker
|
|
53
|
+
TULIP_GROWERS = ['Dutchgrown†', 'Keukenhof†', 'Flowers†',
|
|
54
|
+
'Bloom & Blossom†', 'Teleflora†'
|
|
55
|
+
]
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
TULIPS = ['Semper Augustus',
|
|
58
|
+
'Admiral van Eijck',
|
|
59
|
+
'Admiral of Admirals',
|
|
60
|
+
'Red Impression',
|
|
61
|
+
'Bloemendaal Sunset',
|
|
62
|
+
]
|
|
52
63
|
|
|
53
|
-
|
|
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
|
-
|
|
72
|
+
@tulips = TULIPS ## change name to commodities or assets - why? why not?
|
|
73
|
+
@mining_reward = 5
|
|
74
|
+
end
|
|
57
75
|
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
end
|
|
87
|
+
## lets you use
|
|
88
|
+
## Tulipmania.configure do |config|
|
|
89
|
+
## config.address = 'Anne'
|
|
90
|
+
## end
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
data/lib/tulipmania/cache.rb
CHANGED
|
@@ -6,14 +6,14 @@ class Cache
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def write( data )
|
|
9
|
-
File.open( @name, 'w' ) do |f|
|
|
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.
|
|
16
|
+
data = File.open( @name, 'r:bom|utf-8' ).read
|
|
17
17
|
JSON.parse( data )
|
|
18
18
|
else
|
|
19
19
|
nil
|
data/lib/tulipmania/exchange.rb
CHANGED
|
@@ -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(
|
|
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']
|
|
18
|
+
@pending = Pool.from_json( h['transactions'] )
|
|
22
19
|
else
|
|
23
20
|
@chain = Blockchain.new
|
|
24
|
-
@chain << [Tx.new(
|
|
25
|
-
|
|
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(
|
|
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
|
|
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.
|
|
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.
|
|
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
|
data/lib/tulipmania/ledger.rb
CHANGED
|
@@ -9,9 +9,12 @@ class Ledger
|
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
return true if
|
|
14
|
-
|
|
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
|
|
23
|
-
@wallets[tx.from] -= tx.
|
|
24
|
-
@wallets[tx.to] ||=
|
|
25
|
-
@wallets[tx.to]
|
|
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
|
data/lib/tulipmania/node.rb
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
3
|
class Node
|
|
4
|
-
attr_reader :id, :peers, :wallet, :
|
|
4
|
+
attr_reader :id, :peers, :wallet, :exchange
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
@
|
|
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,
|
|
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,
|
|
37
|
-
if @
|
|
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,
|
|
46
|
-
tx = @wallet.generate_transaction( to,
|
|
47
|
-
if @
|
|
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
|
-
@
|
|
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 @
|
|
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( @
|
|
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, :
|
|
5
|
+
attr_reader :from, :to, :qty, :what, :id
|
|
6
6
|
|
|
7
|
-
def initialize( from, to,
|
|
7
|
+
def initialize( from, to, qty, what, id=SecureRandom.uuid )
|
|
8
8
|
@from = from
|
|
9
9
|
@to = to
|
|
10
|
-
@
|
|
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', '
|
|
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,
|
|
20
|
+
{ from: @from, to: @to, qty: @qty, what: @what, id: @id }
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
|
data/lib/tulipmania/version.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<div class="blockchain">
|
|
2
2
|
<h2>
|
|
3
3
|
Blockchain<br>
|
|
4
|
-
<span><%= @node.
|
|
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.
|
|
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
|
-
|
|
23
|
+
<%= tx.from[0..15] %> → <%= tx.to[0..15] %>
|
|
24
24
|
</td>
|
|
25
25
|
<td>
|
|
26
|
-
|
|
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>
|
|
6
|
+
<th>Tulips</th>
|
|
7
7
|
</tr>
|
|
8
|
-
<% @node.
|
|
8
|
+
<% @node.exchange.ledger.wallets.each do |address, tulips| %>
|
|
9
9
|
<tr>
|
|
10
10
|
<td><%= address[0..15] %></td>
|
|
11
|
-
<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.
|
|
3
|
+
<% if @node.exchange.pending.any? %>
|
|
4
4
|
<table>
|
|
5
5
|
<tr>
|
|
6
6
|
<th>From</th>
|
|
7
7
|
<th>To</th>
|
|
8
|
-
<th
|
|
8
|
+
<th>What</th>
|
|
9
|
+
<th>Qty </th>
|
|
9
10
|
<th>Id</th>
|
|
10
11
|
</tr>
|
|
11
|
-
<% @node.
|
|
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.
|
|
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>
|
|
7
|
-
<div class="balance"
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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>
|
|
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>
|
|
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:
|
|
19
|
+
width: 50%;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.right {
|
|
23
|
-
width:
|
|
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:
|
|
108
|
+
font-size: 22px;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
data/lib/tulipmania/wallet.rb
CHANGED
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.
|
|
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-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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: []
|