centralbank 0.1.0

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