centralbank 0.1.0

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