rubocoin 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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rubocoind +178 -0
- data/bin/setup +8 -0
- data/lib/rubocoin/blockchain/blockchain.rb +195 -0
- data/lib/rubocoin/core_extensions/hash_ext.rb +37 -0
- data/lib/rubocoin/core_extensions/json_ext.rb +12 -0
- data/lib/rubocoin/text/print_utils.rb +50 -0
- data/lib/rubocoin/version.rb +3 -0
- data/lib/rubocoin.rb +17 -0
- data/rubocoin.gemspec +27 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 23ae9a1bfaaa6ec0518f256d1649f9cba77918ea
|
4
|
+
data.tar.gz: 858b1365052751d13e841dee809c60a120653493
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 12ca76dcbbb042f28d7e30af56562304afbaa998d440b8de92d411d0628ddf4369a320802e3556f31e75f29ba391aaec806607aef798e2a3735471132924d741
|
7
|
+
data.tar.gz: e639fafe47e172fc48219b75e03fe5ed22f68d4a2bab778f1075e8b6337c83d78fdf14bf091cd3ebd907c5a25a3fafbf224b5f8449b887e302811ed15d6c8c7c
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rubocoin (0.1.0)
|
5
|
+
colorize
|
6
|
+
sinatra
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
colorize (0.8.1)
|
12
|
+
diff-lcs (1.3)
|
13
|
+
mustermann (1.0.1)
|
14
|
+
rack (2.0.3)
|
15
|
+
rack-protection (2.0.0)
|
16
|
+
rack
|
17
|
+
rake (10.5.0)
|
18
|
+
rspec (3.7.0)
|
19
|
+
rspec-core (~> 3.7.0)
|
20
|
+
rspec-expectations (~> 3.7.0)
|
21
|
+
rspec-mocks (~> 3.7.0)
|
22
|
+
rspec-core (3.7.0)
|
23
|
+
rspec-support (~> 3.7.0)
|
24
|
+
rspec-expectations (3.7.0)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.7.0)
|
27
|
+
rspec-mocks (3.7.0)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.7.0)
|
30
|
+
rspec-support (3.7.0)
|
31
|
+
sinatra (2.0.0)
|
32
|
+
mustermann (~> 1.0)
|
33
|
+
rack (~> 2.0)
|
34
|
+
rack-protection (= 2.0.0)
|
35
|
+
tilt (~> 2.0)
|
36
|
+
tilt (2.0.8)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
bundler (~> 1.16)
|
43
|
+
rake (~> 10.0)
|
44
|
+
rspec (~> 3.0)
|
45
|
+
rubocoin!
|
46
|
+
|
47
|
+
BUNDLED WITH
|
48
|
+
1.16.0
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Carter Brainerd
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# RuboCoin
|
2
|
+
|
3
|
+
RuboCoin is my attempt of a cryptocurrency. I created it mainly just to learn more about blockchain technology.
|
4
|
+
This gem is the full node you can run on your machine to mine rubocoin and handle transactions
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
You have 2 ways to get the rubocoin node (`rubocoind`). You can grab the source code
|
10
|
+
or you can download the gem from (RubyGems)[https://rubygems.org].
|
11
|
+
|
12
|
+
#### Installing from source
|
13
|
+
|
14
|
+
```
|
15
|
+
$ git clone https://github.com/cbrnrd/RuboCoin
|
16
|
+
$ bundle install
|
17
|
+
$ bundle exec bin/rubocoind
|
18
|
+
```
|
19
|
+
|
20
|
+
#### Installing via `gem`
|
21
|
+
|
22
|
+
```
|
23
|
+
$ gem install rubocoin
|
24
|
+
$ rubocoind
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
All you have to do is open port 44856 and run `rubocoind`.
|
30
|
+
|
31
|
+
More command line options are coming in the near future.
|
32
|
+
|
33
|
+
## Development
|
34
|
+
|
35
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
36
|
+
|
37
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cbrnrd/RuboCoin.
|
42
|
+
|
43
|
+
## License
|
44
|
+
|
45
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rubocoin"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/rubocoind
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
lib = File.expand_path("../../lib", __FILE__)
|
3
|
+
unless $LOAD_PATH.include?(lib)
|
4
|
+
$LOAD_PATH.unshift(lib)
|
5
|
+
require 'rubocoin'
|
6
|
+
end
|
7
|
+
|
8
|
+
def return_invalid_json
|
9
|
+
{ :message => 'ERROR: Please supply valid JSON' }.to_json
|
10
|
+
end
|
11
|
+
|
12
|
+
class Rubocoind < Sinatra::Base
|
13
|
+
|
14
|
+
set :environment, :production
|
15
|
+
set :port, 44856
|
16
|
+
set :logging, true
|
17
|
+
|
18
|
+
# Create the blockchain
|
19
|
+
blockchain = Blockchain.new
|
20
|
+
|
21
|
+
pid = File.open('/tmp/rubocoind.pid', 'w') do |f|
|
22
|
+
f.print(Process.pid)
|
23
|
+
end
|
24
|
+
|
25
|
+
node_id = SecureRandom.uuid.delete('-')
|
26
|
+
|
27
|
+
# Node addresses always start in 'R'
|
28
|
+
node_id[0] = 'R'
|
29
|
+
|
30
|
+
|
31
|
+
# Lets mine!
|
32
|
+
get '/mine' do
|
33
|
+
last_block = blockchain.last_block
|
34
|
+
last_proof = last_block[:proof]
|
35
|
+
proof = blockchain.proof_of_work(last_proof)
|
36
|
+
|
37
|
+
# Distribute reward for finding the proof
|
38
|
+
blockchain.new_transaction('network', node_id, blockchain.calculate_reward_amount)
|
39
|
+
|
40
|
+
# Add a new block to the chain
|
41
|
+
block = blockchain.new_block(proof)
|
42
|
+
|
43
|
+
res = {
|
44
|
+
message: "New block forged",
|
45
|
+
index: block[:index],
|
46
|
+
transactions: block[:transactions],
|
47
|
+
proof: block[:proof],
|
48
|
+
previous_hash: block[:previous_hash]
|
49
|
+
}
|
50
|
+
|
51
|
+
status 200
|
52
|
+
res.to_json
|
53
|
+
end
|
54
|
+
|
55
|
+
# Make a new transaction and add it to the blockchain
|
56
|
+
post '/transactions/new' do
|
57
|
+
req_body = request.body.read
|
58
|
+
|
59
|
+
unless JSON.is_valid?(req_body)
|
60
|
+
status 400
|
61
|
+
return return_invalid_json
|
62
|
+
end
|
63
|
+
|
64
|
+
values = JSON.parse(req_body)
|
65
|
+
|
66
|
+
required = ['amount', 'sender', 'recipient']
|
67
|
+
|
68
|
+
unless required.all? { |e| values.key?(e) }
|
69
|
+
status 400
|
70
|
+
return { :message => 'ERROR: Missing values' }.to_json
|
71
|
+
end
|
72
|
+
|
73
|
+
# Create a new transaction
|
74
|
+
index = blockchain.new_transaction(values['sender'], values['recipient'],
|
75
|
+
values['amount'], values['others'])
|
76
|
+
|
77
|
+
res = { message: "Transaction successful. It will be added to the blockchain at block #{index}" }
|
78
|
+
status 201
|
79
|
+
res.to_json
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return the current blockchain
|
83
|
+
get '/chain' do
|
84
|
+
res = {
|
85
|
+
chain: blockchain.chain,
|
86
|
+
length: blockchain.chain.size
|
87
|
+
}
|
88
|
+
res.to_json
|
89
|
+
end
|
90
|
+
|
91
|
+
post '/register_node' do
|
92
|
+
request.body.rewind
|
93
|
+
req_body = request.body.read
|
94
|
+
|
95
|
+
# Check if the json sent to us is valid
|
96
|
+
unless JSON.is_valid?(req_body)
|
97
|
+
status 400
|
98
|
+
return return_invalid_json
|
99
|
+
end
|
100
|
+
|
101
|
+
values = JSON.parse(req_body)
|
102
|
+
|
103
|
+
nodes = values[:nodes]
|
104
|
+
|
105
|
+
# Check if a list was even sent to us
|
106
|
+
if nodes.nil?
|
107
|
+
status 400
|
108
|
+
return 'ERROR: Please supply a valid node list'
|
109
|
+
end
|
110
|
+
|
111
|
+
nodes.each do |node|
|
112
|
+
blockchain.register_node(node)
|
113
|
+
end
|
114
|
+
|
115
|
+
res = { message: "Nodes have been added successfully", total_nodes: blockchain.nodes }
|
116
|
+
|
117
|
+
status 201
|
118
|
+
res.to_json
|
119
|
+
end
|
120
|
+
|
121
|
+
get '/resolve_node' do
|
122
|
+
replaced = blockchain.resolve_conflicts
|
123
|
+
|
124
|
+
if replaced
|
125
|
+
res = { message: 'Chain successfully replaced', new_chain: blockchain.chain }
|
126
|
+
else
|
127
|
+
res = { message: 'Our chain is authoritative', chain: blockchain.chain }
|
128
|
+
end
|
129
|
+
|
130
|
+
status 200
|
131
|
+
return res.to_json
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
daemonize = false
|
137
|
+
|
138
|
+
OptionParser.new do |opts|
|
139
|
+
|
140
|
+
opts.banner = "Usage #{File.basename($PROGRAM_NAME) [options]}"
|
141
|
+
|
142
|
+
opts.on('-d', '--daemon', 'Run the node in the background') do
|
143
|
+
daemonize = true
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on('-k', '--kill-daemon', 'Kill the rubocoind service if it\'s running') do
|
147
|
+
unless File.file?('/tmp/rubocoind.pid')
|
148
|
+
puts 'The rubocoin daemon doesn\'t seem to be running'
|
149
|
+
exit 0
|
150
|
+
end
|
151
|
+
pid = File.read('/tmp/rubocoind.pid')
|
152
|
+
# Check validity of pid file (no code injection for you)
|
153
|
+
if pid ~= /^[A-Z+$]/i
|
154
|
+
puts 'PID file is not in the correct format'
|
155
|
+
exit 0
|
156
|
+
end
|
157
|
+
system("kill #{pid}")
|
158
|
+
end
|
159
|
+
|
160
|
+
opts.on('--version', 'Show the current version and exit') do
|
161
|
+
puts Rubocoin::Version
|
162
|
+
exit 0
|
163
|
+
end
|
164
|
+
|
165
|
+
opts.on_tail('-h', '--help', 'SHow this help message and exit') do
|
166
|
+
puts opts
|
167
|
+
exit 0
|
168
|
+
end
|
169
|
+
end.parse!
|
170
|
+
|
171
|
+
if daemonize
|
172
|
+
fork do
|
173
|
+
Rubocoind.run!
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# TODO add cli opts
|
178
|
+
Rubocoind.run!
|
data/bin/setup
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
require 'digest'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
# This class defines what our blockchain will look like as well as contain
|
8
|
+
# functoins for interacting with the chain.
|
9
|
+
class Blockchain
|
10
|
+
|
11
|
+
# The current chain being used
|
12
|
+
# @return [Array] A list of blocks
|
13
|
+
attr_accessor :chain
|
14
|
+
|
15
|
+
# We use a {Chain} instead of an {Array} here because we don't want any
|
16
|
+
# duplicate blocks in our chain. A {Set} takes care of this for us
|
17
|
+
# @return [Set] A duplicate-free list of other nodes currently registered with this node
|
18
|
+
attr_accessor :nodes
|
19
|
+
|
20
|
+
# The difficulty of this node. It's base value is 4 (meaning '0'*4)
|
21
|
+
# Each 10,000 blocks mined, the difficulty will increment by 1 (meaning '0'*5)
|
22
|
+
attr_reader :difficulty
|
23
|
+
|
24
|
+
# Initialize the chain and other variables as well
|
25
|
+
# as create the 'Genesis' block
|
26
|
+
def initialize
|
27
|
+
@current_transactions = []
|
28
|
+
@chain = []
|
29
|
+
@nodes = Set.new
|
30
|
+
@difficulty = 4
|
31
|
+
@difficulty_increment_counter = 0 # When this reaches 10000, increment difficulty by 1
|
32
|
+
|
33
|
+
# Generate the genesis block
|
34
|
+
new_block(100, 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
# The reward halfs each time the difficulty increases
|
38
|
+
def calculate_reward_amount
|
39
|
+
initial_reward = 100
|
40
|
+
(@difficulty - 3).times do
|
41
|
+
initial_reward /= 2
|
42
|
+
end
|
43
|
+
return initial_reward
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Register a new node
|
48
|
+
#
|
49
|
+
def register_node(addr)
|
50
|
+
url = URI.parse(addr)
|
51
|
+
@nodes.add("#{url.host}:#{url.port}")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return the most recent block in our chain
|
55
|
+
def last_block
|
56
|
+
@chain[-1]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Check whether a chain is valid
|
60
|
+
def valid_chain(chain)
|
61
|
+
last_block = chain[0]
|
62
|
+
index = 1
|
63
|
+
|
64
|
+
while index < chain.size do
|
65
|
+
block = chain[index]
|
66
|
+
|
67
|
+
# Check previous block hash is correct
|
68
|
+
if block[:previous_hash] != hash(last_block)
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Check Proof of Work
|
73
|
+
unless valid_proof(last_block[:proof], block[:proof])
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
last_block = block
|
78
|
+
index += 1
|
79
|
+
end
|
80
|
+
true # If we got here, all blocks are valid; therefore the chain is valid
|
81
|
+
end
|
82
|
+
|
83
|
+
def resolve_conflicts
|
84
|
+
neighbour_nodes = @nodes
|
85
|
+
new_chain = nil
|
86
|
+
|
87
|
+
# Only chains longer than ours
|
88
|
+
max_len = @chain.size
|
89
|
+
|
90
|
+
# Fetch and confirm each chain from the network
|
91
|
+
neighbour_nodes.each do |node|
|
92
|
+
response = JSON.parse(open("http://#{node}/chain").read)
|
93
|
+
response.deep_symbolize_keys!
|
94
|
+
|
95
|
+
if response[:chain]
|
96
|
+
length = response[:length]
|
97
|
+
chain = response[:chain]
|
98
|
+
|
99
|
+
# Check if the length is longer and the chain is valid
|
100
|
+
if length > max_len || valid_chain(chain)
|
101
|
+
max_len = length
|
102
|
+
new_chain = chain
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Replace our chain if we discovered a new, valid chain longer than ours
|
108
|
+
if new_chain
|
109
|
+
@chain = new_chain
|
110
|
+
return true
|
111
|
+
end
|
112
|
+
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
|
116
|
+
def new_block(proof, previous_hash=nil)
|
117
|
+
# Define the block structure
|
118
|
+
block =
|
119
|
+
{
|
120
|
+
:index => @chain.size + 1,
|
121
|
+
:timestamp => Time.now,
|
122
|
+
:transactions => @current_transactions,
|
123
|
+
:proof => proof,
|
124
|
+
:previous_hash => previous_hash || hash(last_block)
|
125
|
+
}
|
126
|
+
|
127
|
+
# Clear transactions
|
128
|
+
@current_transactions = []
|
129
|
+
info_with_timestamp('Transaction list cleared due to new block formation')
|
130
|
+
|
131
|
+
# Append the block to the chain
|
132
|
+
@chain << block
|
133
|
+
|
134
|
+
print_new("New block added:")
|
135
|
+
puts '{'
|
136
|
+
puts "\t index: #{block[:index]}"
|
137
|
+
puts "\t timestamp: #{block[:timestamp]}"
|
138
|
+
puts "\t transactions: #{block[:transactions]}"
|
139
|
+
puts "\t proof: #{block[:proof]}"
|
140
|
+
puts "\t previous_hash: #{block[:previous_hash]}"
|
141
|
+
puts '}'
|
142
|
+
|
143
|
+
@difficulty_increment_counter += 1
|
144
|
+
|
145
|
+
info("Difficulty will increase after #{1000-@difficulty_increment_counter} more blocks")
|
146
|
+
|
147
|
+
if @difficulty_increment_counter >= 0x2710
|
148
|
+
debug_with_timestamp("Difficulty increased to #{@difficulty + 1}!")
|
149
|
+
@difficulty += 1
|
150
|
+
@difficulty_increment_counter = 0 # Reset the counter
|
151
|
+
end
|
152
|
+
|
153
|
+
block
|
154
|
+
end
|
155
|
+
|
156
|
+
def new_transaction(sender, recipient, amount, others=nil)
|
157
|
+
unless others.nil?
|
158
|
+
@current_transactions <<
|
159
|
+
{
|
160
|
+
:sender => sender,
|
161
|
+
:recipient => recipient,
|
162
|
+
:amount => amount,
|
163
|
+
:others => others
|
164
|
+
}
|
165
|
+
else
|
166
|
+
@current_transactions <<
|
167
|
+
{
|
168
|
+
:sender => sender,
|
169
|
+
:recipient => recipient,
|
170
|
+
:amount => amount
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
last_block[:index] + 1
|
175
|
+
end
|
176
|
+
|
177
|
+
def hash(last_block)
|
178
|
+
block_string = last_block.sort.to_h.to_json
|
179
|
+
return Digest::SHA256.hexdigest(block_string)
|
180
|
+
end
|
181
|
+
|
182
|
+
def proof_of_work(last_proof)
|
183
|
+
proof = -1
|
184
|
+
proof += 1 until valid_proof(last_proof, proof)
|
185
|
+
info("Valid proof found! #{proof}")
|
186
|
+
return proof
|
187
|
+
end
|
188
|
+
|
189
|
+
def valid_proof(last_proof, proof)
|
190
|
+
guess = "#{last_proof}#{proof}"
|
191
|
+
guess_hash = Digest::SHA256.hexdigest(guess)
|
192
|
+
return guess_hash[0..(@difficulty - 1)] == ('0' * @difficulty)
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Hash monkey patches
|
2
|
+
class Hash
|
3
|
+
def symbolize_keys!
|
4
|
+
transform_keys!{ |key| key.to_sym rescue key }
|
5
|
+
end
|
6
|
+
|
7
|
+
def deep_symbolize_keys!
|
8
|
+
deep_transform_keys!{ |key| key.to_sym rescue key }
|
9
|
+
end
|
10
|
+
|
11
|
+
def transform_keys!
|
12
|
+
return enum_for(:transform_keys!) unless block_given?
|
13
|
+
keys.each do |key|
|
14
|
+
self[yield(key)] = delete(key)
|
15
|
+
end
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def deep_transform_keys!(&block)
|
20
|
+
_deep_transform_keys_in_object!(self, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def _deep_transform_keys_in_object!(object, &block)
|
24
|
+
case object
|
25
|
+
when Hash
|
26
|
+
object.keys.each do |key|
|
27
|
+
value = object.delete(key)
|
28
|
+
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
|
29
|
+
end
|
30
|
+
object
|
31
|
+
when Array
|
32
|
+
object.map! {|e| _deep_transform_keys_in_object!(e, &block)}
|
33
|
+
else
|
34
|
+
object
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
# Prints +msg+ to the given descriptor in the form of "[INFO] - msg"
|
4
|
+
def info(msg = '', descriptor = $stdout)
|
5
|
+
descriptor.puts("[#{'INFO'.blue}] - #{msg}")
|
6
|
+
end
|
7
|
+
|
8
|
+
# @see #info
|
9
|
+
# Prints an informational message with a timestamp prepended to it
|
10
|
+
# Example: 2005-03-19 15:10:26,618 - [INFO] - msg
|
11
|
+
def info_with_timestamp(msg = '', descriptor = $stdout)
|
12
|
+
descriptor.puts("#{Time.now} - [#{'INFO'.blue}] - #{msg}")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Prints +msg+ to the given descriptor in the form of "[ERROR] - msg"
|
16
|
+
def error(msg = '', descriptor = $stdout)
|
17
|
+
descriptor.puts("[#{'ERROR'.red}] - #{msg}")
|
18
|
+
end
|
19
|
+
|
20
|
+
# @see #error
|
21
|
+
# Prints an error message with a timestamp prepended to it
|
22
|
+
# Example: 2005-03-19 15:10:26,618 - [ERROR] - msg
|
23
|
+
def error_with_timestamp(msg = '', descriptor = $stdout)
|
24
|
+
descriptor.puts("#{Time.now} - [#{'ERROR'.blue}] - #{msg}")
|
25
|
+
end
|
26
|
+
|
27
|
+
# Prints +msg+ to the given descriptor in the form of "[DEBUG] - msg"
|
28
|
+
def debug(msg = '', descriptor = $stdout)
|
29
|
+
descriptor.puts("[#{'DEBUG'.yellow}] - #{msg}")
|
30
|
+
end
|
31
|
+
|
32
|
+
# @see #debug
|
33
|
+
# Prints an debug message with a timestamp prepended to it
|
34
|
+
# Example: 2005-03-19 15:10:26,618 - [DEBUG] - msg
|
35
|
+
def debug_with_timestamp(msg = '', descriptor = $stdout)
|
36
|
+
descriptor.puts("#{Time.now} - [#{'DEBUG'.yellow}] - #{msg}")
|
37
|
+
end
|
38
|
+
|
39
|
+
# Prints +msg+ to the given descriptor in the form of "[NEW] - msg"
|
40
|
+
def print_new(msg = '', descriptor = $stdout)
|
41
|
+
descriptor.puts("[#{'NEW'.green}] - #{msg}")
|
42
|
+
end
|
43
|
+
|
44
|
+
# @see #new
|
45
|
+
# Prints a 'NEW' message with a timestamp prepended to it.
|
46
|
+
# Usually used for new blocks being formed
|
47
|
+
# Example: 2005-03-19 15:10:26,618 - [NEW] - msg
|
48
|
+
def new_with_timestamp(msg = '', descriptor = $stdout)
|
49
|
+
descriptor.puts("#{Time.now} - [#{'NEW'.green}] - #{msg}")
|
50
|
+
end
|
data/lib/rubocoin.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Gems
|
2
|
+
require 'sinatra'
|
3
|
+
require 'json'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
require 'rubocoin/version'
|
8
|
+
|
9
|
+
# The lifeblood
|
10
|
+
require 'rubocoin/blockchain/blockchain'
|
11
|
+
|
12
|
+
# stdlib extensions
|
13
|
+
require 'rubocoin/core_extensions/json_ext'
|
14
|
+
require 'rubocoin/core_extensions/hash_ext'
|
15
|
+
|
16
|
+
# Useful output stuff
|
17
|
+
require 'rubocoin/text/print_utils'
|
data/rubocoin.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "rubocoin"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rubocoin"
|
8
|
+
spec.version = Rubocoin::VERSION
|
9
|
+
spec.authors = ["cbrnrd"]
|
10
|
+
spec.email = ["cbawsome77@gmail.com"]
|
11
|
+
spec.executables = %w[rubocoind]
|
12
|
+
|
13
|
+
spec.summary = ""
|
14
|
+
spec.description = ""
|
15
|
+
spec.homepage = ""
|
16
|
+
spec.license = "MIT"
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
spec.add_runtime_dependency "sinatra"
|
26
|
+
spec.add_runtime_dependency "colorize"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubocoin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- cbrnrd
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: colorize
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: ''
|
84
|
+
email:
|
85
|
+
- cbawsome77@gmail.com
|
86
|
+
executables:
|
87
|
+
- rubocoind
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Gemfile
|
95
|
+
- Gemfile.lock
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bin/console
|
100
|
+
- bin/rubocoind
|
101
|
+
- bin/setup
|
102
|
+
- lib/rubocoin.rb
|
103
|
+
- lib/rubocoin/blockchain/blockchain.rb
|
104
|
+
- lib/rubocoin/core_extensions/hash_ext.rb
|
105
|
+
- lib/rubocoin/core_extensions/json_ext.rb
|
106
|
+
- lib/rubocoin/text/print_utils.rb
|
107
|
+
- lib/rubocoin/version.rb
|
108
|
+
- rubocoin.gemspec
|
109
|
+
homepage: ''
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.6.13
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: ''
|
133
|
+
test_files: []
|