pickynode-bchd 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: 377298cc35a7243d785a916969de201cbfbf471b
4
+ data.tar.gz: 7ecacb4c91391adac2c04736a2c714ecee9bc3b0
5
+ SHA512:
6
+ metadata.gz: d42292b1ff9c695261aa76a01342914519df5e44a59ece7ae9f4e5ae0c0ac2579fe55b749338407de3f51bb2938ca4cbd08f83785f12fa9e85dd0ab07d72a642
7
+ data.tar.gz: 72fa31e5363fe323744b172980b4cb517eb3e3acd621f19b3c2624cb7f6678476fa01f1ced4168309cc6ceb3e128042b836c348c560e0548a5086bc661e91770
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ ClassLength:
2
+ Enabled: false
3
+ Metrics/BlockLength:
4
+ ExcludedMethods: ['describe', 'context']
5
+ Style/IndentHeredoc:
6
+ Enabled: false
7
+ Style/NumericPredicate:
8
+ Enabled: false
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ pickynode-bchd
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.4
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.2.7"
4
+ - "2.3.4"
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ ruby '>= 2.0'
5
+
6
+ gem 'awesome_print', '~> 1.7'
7
+ gem 'rake', '~> 12.0'
8
+ gem 'rspec', '~> 3.6'
9
+ gem 'rubocop', '~> 0.48'
10
+ gem 'simplecov', '~> 0.14', require: false, group: :test
11
+ gem 'trollop', '~> 2.1'
data/Gemfile.lock ADDED
@@ -0,0 +1,58 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ ast (2.3.0)
5
+ awesome_print (1.7.0)
6
+ diff-lcs (1.3)
7
+ docile (1.1.5)
8
+ json (2.1.0)
9
+ parser (2.4.0.0)
10
+ ast (~> 2.2)
11
+ powerpack (0.1.1)
12
+ rainbow (2.2.2)
13
+ rake
14
+ rake (12.0.0)
15
+ rspec (3.6.0)
16
+ rspec-core (~> 3.6.0)
17
+ rspec-expectations (~> 3.6.0)
18
+ rspec-mocks (~> 3.6.0)
19
+ rspec-core (3.6.0)
20
+ rspec-support (~> 3.6.0)
21
+ rspec-expectations (3.6.0)
22
+ diff-lcs (>= 1.2.0, < 2.0)
23
+ rspec-support (~> 3.6.0)
24
+ rspec-mocks (3.6.0)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.6.0)
27
+ rspec-support (3.6.0)
28
+ rubocop (0.48.1)
29
+ parser (>= 2.3.3.1, < 3.0)
30
+ powerpack (~> 0.1)
31
+ rainbow (>= 1.99.1, < 3.0)
32
+ ruby-progressbar (~> 1.7)
33
+ unicode-display_width (~> 1.0, >= 1.0.1)
34
+ ruby-progressbar (1.8.1)
35
+ simplecov (0.14.1)
36
+ docile (~> 1.1.0)
37
+ json (>= 1.8, < 3)
38
+ simplecov-html (~> 0.10.0)
39
+ simplecov-html (0.10.1)
40
+ trollop (2.1.2)
41
+ unicode-display_width (1.2.1)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ awesome_print (~> 1.7)
48
+ rake (~> 12.0)
49
+ rspec (~> 3.6)
50
+ rubocop (~> 0.48)
51
+ simplecov (~> 0.14)
52
+ trollop (~> 2.1)
53
+
54
+ RUBY VERSION
55
+ ruby 2.3.4p301
56
+
57
+ BUNDLED WITH
58
+ 1.14.6
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Josh Ellithorpe
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ [![Gem Version](https://badge.fury.io/rb/pickynode-bchd.svg)](https://badge.fury.io/rb/pickynode-bchd) [![Build Status](https://travis-ci.org/zquestz/pickynode-bchd.svg)](https://travis-ci.org/zquestz/pickynode-bchd) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
2
+ # pickynode-bchd
3
+
4
+ Some people are picky about the bitcoin cash nodes they connect to with bchd.
5
+
6
+ ### Requirements:
7
+
8
+ You need a working bchd node on your machine. The `bchctl` command should be functional.
9
+
10
+ ### Installation:
11
+
12
+ ```
13
+ gem install pickynode-bchd
14
+ ```
15
+
16
+ ### Usage:
17
+
18
+ Display list of currently connected nodes:
19
+
20
+ ```
21
+ pickynode-bchd
22
+ ```
23
+
24
+ Add node type:
25
+ ```
26
+ pickynode-bchd --add=USER_AGENT_FILTER
27
+ ```
28
+
29
+ Remove node type:
30
+ ```
31
+ pickynode-bchd --remove=USER_AGENT_FILTER
32
+ ```
33
+
34
+ Connect to node type:
35
+ ```
36
+ pickynode-bchd --connect=USER_AGENT_FILTER
37
+ ```
38
+
39
+ Disconnect from node type:
40
+
41
+ ```
42
+ pickynode-bchd --disconnect=USER_AGENT_FILTER
43
+ ```
44
+
45
+ ### Help:
46
+
47
+ ```
48
+ pickynode-bchd v0.1.0
49
+ Options:
50
+ -i, --info Local node info
51
+ -d, --debug Debug mode
52
+ -o, --output Output commands
53
+ -a, --add=<s> Add node type
54
+ -r, --remove=<s> Remove node type
55
+ -c, --connect=<s> Connect to node type
56
+ -s, --disconnect=<s> Disconnect from node type
57
+ -l, --limit=<i> Limit number of nodes to add/connect
58
+ -v, --version Print version and exit
59
+ -h, --help Show this message
60
+ ```
61
+
62
+ The --add and --connect commands pull data from blockchair.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ # frozen_string_literal: true
3
+
4
+ require File.join('bundler', 'gem_tasks')
5
+ require File.join('rspec', 'core', 'rake_task')
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task default: :spec
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'pickynode_bchd'
5
+
6
+ opts = Trollop.options do
7
+ version "pickynode-bchd v#{PickynodeBCHD::VERSION}"
8
+ opt :info, 'Local node info'
9
+ opt :debug, 'Debug mode'
10
+ opt :output, 'Output commands'
11
+ opt :add, 'Add node type', type: :string
12
+ opt :remove, 'Remove node type', type: :string
13
+ opt :connect, 'Connect to node type', type: :string
14
+ opt :disconnect, 'Disconnect from node type', type: :string
15
+ opt :limit, 'Limit number of nodes to add/connect', type: :integer
16
+ end
17
+
18
+ Trollop.die :limit, 'must be positive' if opts[:limit] && opts[:limit] <= 0
19
+ PickynodeBCHD.new(opts).run
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'awesome_print'
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'trollop'
7
+ require 'uri'
8
+
9
+ # Allows you to easily add/remove/connect/disconnect nodes
10
+ # based on User Agent.
11
+ class PickynodeBCHD
12
+ VERSION = '0.1.0'
13
+
14
+ def initialize(opts = {})
15
+ @opts = opts
16
+ end
17
+
18
+ def add(filter, limit = nil)
19
+ return unless filter
20
+
21
+ validate_limit(limit)
22
+
23
+ blockchair_addr_types
24
+ .select { |_, v| v.include?(filter) }
25
+ .each_with_index do |(k, _), i|
26
+ break if limit == i
27
+ run_cmd(%(bchctl addnode "#{k}" add))
28
+ end
29
+ end
30
+
31
+ def remove(filter, limit = nil)
32
+ return unless filter
33
+
34
+ validate_limit(limit)
35
+
36
+ addr_types
37
+ .select { |_, v| v.include?(filter) }
38
+ .each_with_index do |(k, _), i|
39
+ break if limit == i
40
+ run_cmd(%(bchctl node remove "#{k}"))
41
+ end
42
+ end
43
+
44
+ def connect(filter, limit = nil)
45
+ return unless filter
46
+
47
+ validate_limit(limit)
48
+
49
+ blockchair_addr_types
50
+ .select { |_, v| v.include?(filter) }
51
+ .each_with_index do |(k, _), i|
52
+ break if limit == i
53
+ run_cmd(%(bchctl node connect "#{k}"))
54
+ end
55
+ end
56
+
57
+ def disconnect(filter, limit = nil)
58
+ return unless filter
59
+
60
+ validate_limit(limit)
61
+
62
+ addr_types
63
+ .select { |_, v| v.include?(filter) }
64
+ .each_with_index do |(k, _), i|
65
+ break if limit == i
66
+ run_cmd(%(bchctl node disconnect "#{k}"))
67
+ end
68
+ end
69
+
70
+ def display
71
+ ap addr_types
72
+ end
73
+
74
+ def info
75
+ ap getinfo
76
+ end
77
+
78
+ def run
79
+ add(@opts[:add], @opts[:limit])
80
+ connect(@opts[:connect], @opts[:limit])
81
+
82
+ remove(@opts[:remove], @opts[:limit])
83
+ disconnect(@opts[:disconnect], @opts[:limit])
84
+
85
+ display_info
86
+ end
87
+
88
+ def clear_cache
89
+ @addr_types = nil
90
+ @blockchair_addr_types = nil
91
+ end
92
+
93
+ private
94
+
95
+ def display_info
96
+ info if @opts[:info]
97
+ display if @opts.values.select { |v| v }.empty?
98
+ end
99
+
100
+ def run_cmd(cmd)
101
+ puts "Running #{cmd}" if @opts[:output] || @opts[:debug]
102
+ `#{cmd}` unless @opts[:debug]
103
+ end
104
+
105
+ def addr_types
106
+ return @addr_types if @addr_types
107
+ nodes = getpeerinfo
108
+ parsed_nodes = JSON.parse(nodes)
109
+ @addr_types = parsed_nodes.map do |n|
110
+ [n['addr'], n['subver']]
111
+ end.to_h
112
+ rescue JSON::ParserError
113
+ {}
114
+ end
115
+
116
+ def blockchair_addr_types
117
+ return @blockchair_addr_types if @blockchair_addr_types
118
+ parsed_nodelist = JSON.parse(blockchair_snapshot)
119
+ @blockchair_addr_types = parsed_nodelist['data']['nodes'].map do |k, v|
120
+ [k, v['version']]
121
+ end.to_h
122
+ rescue JSON::ParserError
123
+ {}
124
+ end
125
+
126
+ def blockchair_snapshot
127
+ Net::HTTP.get(URI.parse('https://api.blockchair.com/bitcoin-cash/nodes'))
128
+ end
129
+
130
+ def getinfo
131
+ JSON.parse(`bchctl getinfo`)
132
+ rescue JSON::ParserError
133
+ {}
134
+ end
135
+
136
+ def getpeerinfo
137
+ `bchctl getpeerinfo`
138
+ end
139
+
140
+ def validate_limit(limit)
141
+ return unless limit
142
+ raise 'Limit must be greater than 0' unless limit > 0
143
+ end
144
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path(File.join('..', 'lib', 'pickynode_bchd'), __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'pickynode-bchd'
7
+ s.version = PickynodeBCHD::VERSION
8
+ s.summary = 'Manage connections to your bchd node'
9
+ s.description = "Some people are picky about the \
10
+ bitcoin cash nodes they connect to with bchd."
11
+ s.authors = ['Josh Ellithorpe']
12
+ s.email = 'quest@mac.com'
13
+ s.homepage = 'http://github.com/zquestz/pickynode-bchd'
14
+ s.license = 'MIT'
15
+ s.executables << 'pickynode-bchd'
16
+ s.files = `git ls-files`.split("\n")
17
+ s.require_paths = ['lib']
18
+ s.required_ruby_version = '>= 2.0'
19
+
20
+ s.add_dependency 'awesome_print', '~> 1.7'
21
+ s.add_dependency 'trollop', '~> 2.1'
22
+
23
+ s.add_development_dependency 'rspec', '~> 3.6'
24
+ s.add_development_dependency 'rake', '~> 12.0'
25
+ s.add_development_dependency 'rubocop', '~> 0.48'
26
+ s.add_development_dependency 'simplecov', '~> 0.14'
27
+ end
data/spec/mocks.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ BLOCKCHAIR_SNAPSHOT = <<-HEREDOC
4
+ {
5
+ "data": {
6
+ "nodes": {
7
+ "88.99.199.87:8333": {
8
+ "version": "\/BitcoinUnlimited:1.0.2(EB16; AD12)\/",
9
+ "country": "US",
10
+ "height": 555417,
11
+ "flags": 37
12
+ },
13
+ "[2a01:e34:ee3a:5730:21f:5bff:fec5:e356]:8333": {
14
+ "version": "\/Bitcoin ABC:0.18.2(EB32.0)\/",
15
+ "country": "US",
16
+ "height": 555416,
17
+ "flags": 37
18
+ }
19
+ }
20
+ }
21
+ }
22
+ HEREDOC
23
+
24
+ NODE_INFO = <<-HEREDOC
25
+ {
26
+ "version": 120000,
27
+ "protocolversion": 70013,
28
+ "blocks": 310554,
29
+ "timeoffset": 0,
30
+ "connections": 41,
31
+ "proxy": "",
32
+ "difficulty": 17336316978.50783,
33
+ "testnet": false,
34
+ "relayfee": 0.00001,
35
+ "errors": ""
36
+ }
37
+ HEREDOC
38
+
39
+ PEER_INFO = <<-HEREDOC
40
+ [
41
+ {
42
+ "id": 10,
43
+ "addr": "131.114.88.218:33422",
44
+ "addrlocal": "67.188.11.253:8333",
45
+ "services": "00000001",
46
+ "servicesStr": "SFNodeNetwork",
47
+ "relaytxes": true,
48
+ "lastsend": 1541490162,
49
+ "lastrecv": 1541490162,
50
+ "bytessent": 1587,
51
+ "bytesrecv": 3258,
52
+ "conntime": 1541486322,
53
+ "timeoffset": 0,
54
+ "pingtime": 125312,
55
+ "version": 70013,
56
+ "subver": "/FirstClient/",
57
+ "inbound": false,
58
+ "startingheight": 555420,
59
+ "currentheight": 555420,
60
+ "banscore": 0,
61
+ "whitelisted": false,
62
+ "feefilter": 18,
63
+ "syncnode": false
64
+ },
65
+ {
66
+ "id": 12,
67
+ "addr": "[2a01:e34:ee3a:5730:21f:5bff:fec5:e356]:8333",
68
+ "addrlocal": "[2a01:e34:ee3a:5730:21f:5bff:fec5:e356]:8333",
69
+ "services": "00000053",
70
+ "servicesStr": "SFNodeNetwork|SFNodeBloom|SFNodeXthin|SFNodeBitcoinCash",
71
+ "relaytxes": true,
72
+ "lastsend": 1541490073,
73
+ "lastrecv": 1541490140,
74
+ "bytessent": 2163,
75
+ "bytesrecv": 6515,
76
+ "conntime": 1541486352,
77
+ "timeoffset": 0,
78
+ "pingtime": 180911,
79
+ "version": 80003,
80
+ "subver": "/SecondClient/",
81
+ "inbound": false,
82
+ "startingheight": 555420,
83
+ "currentheight": 555420,
84
+ "banscore": 0,
85
+ "whitelisted": false,
86
+ "feefilter": 0,
87
+ "syncnode": false
88
+ }
89
+ ]
90
+ HEREDOC
@@ -0,0 +1,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_helper'
4
+ require_relative 'mocks'
5
+
6
+ describe PickynodeBCHD do
7
+ # Debug mode makes sure we don't execute real commands.
8
+ let(:opts) do
9
+ { debug: true }
10
+ end
11
+
12
+ # IPv6 addresses are long, easier to refer by let.
13
+ let(:ipv6_ip) { '[2a01:e34:ee3a:5730:21f:5bff:fec5:e356]:8333' }
14
+
15
+ # The currently connected nodes for the specs.
16
+ let(:node_hash) do
17
+ { '131.114.88.218:33422' => '/FirstClient/',
18
+ ipv6_ip => '/SecondClient/' }
19
+ end
20
+
21
+ # String to simulate a json error.
22
+ let(:json_error) { 'An error occurred.' }
23
+
24
+ # Parsed information from getinfo.
25
+ let(:parsed_node_info) { JSON.parse(NODE_INFO) }
26
+
27
+ subject { PickynodeBCHD.new(opts) }
28
+
29
+ describe '.add' do
30
+ it 'should add nodes based on user agent' do
31
+ expect(subject).to receive(:blockchair_snapshot).once
32
+ .and_return(BLOCKCHAIR_SNAPSHOT)
33
+ expect(subject).to receive(:run_cmd)
34
+ .with(%(bchctl addnode "#{ipv6_ip}" add))
35
+ subject.add('ABC')
36
+ expect(subject).to receive(:run_cmd)
37
+ .with('bchctl addnode "88.99.199.87:8333" add')
38
+ subject.add('Unlimited')
39
+ end
40
+
41
+ it 'should return if the filter is falsy' do
42
+ expect(subject).to_not receive(:blockchair_snapshot)
43
+ expect(subject).to_not receive(:run_cmd)
44
+ subject.add(false)
45
+ subject.add(nil)
46
+ end
47
+
48
+ it 'should recover gracefully if json is malformed' do
49
+ expect(subject).to receive(:blockchair_snapshot).once
50
+ .and_return(json_error)
51
+ expect(subject).to_not receive(:run_cmd)
52
+ subject.add('Anything')
53
+ end
54
+
55
+ it 'should raise an error if the limit is <= 0' do
56
+ expect { subject.add('Anything', 0) }
57
+ .to raise_error('Limit must be greater than 0')
58
+ expect { subject.add('Anything', -5) }
59
+ .to raise_error('Limit must be greater than 0')
60
+ end
61
+
62
+ context 'with a limit' do
63
+ it 'should respect a limit parameter of 1' do
64
+ expect(subject).to receive(:blockchair_snapshot).once
65
+ .and_return(BLOCKCHAIR_SNAPSHOT)
66
+ expect(subject).to receive(:run_cmd)
67
+ .with('bchctl addnode "88.99.199.87:8333" add')
68
+ subject.add('i', 1)
69
+ end
70
+
71
+ it 'should respect a limit parameter greater than 1' do
72
+ expect(subject).to receive(:blockchair_snapshot).once
73
+ .and_return(BLOCKCHAIR_SNAPSHOT)
74
+ expect(subject).to receive(:run_cmd)
75
+ .with('bchctl addnode "88.99.199.87:8333" add')
76
+ expect(subject).to receive(:run_cmd)
77
+ .with(%(bchctl addnode "#{ipv6_ip}" add))
78
+ subject.add('i', 2)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '.remove' do
84
+ it 'should remove nodes based on user agent' do
85
+ expect(subject).to receive(:`).once
86
+ .and_return(PEER_INFO)
87
+ expect(subject).to receive(:run_cmd)
88
+ .with(%(bchctl node remove "#{ipv6_ip}"))
89
+ subject.remove('SecondClient')
90
+ expect(subject).to receive(:run_cmd)
91
+ .with('bchctl node remove "131.114.88.218:33422"')
92
+ subject.remove('FirstClient')
93
+ end
94
+
95
+ it 'should return if the filter is falsy' do
96
+ expect(subject).to_not receive(:getpeerinfo)
97
+ expect(subject).to_not receive(:run_cmd)
98
+ subject.remove(false)
99
+ subject.remove(nil)
100
+ end
101
+
102
+ it 'should recover gracefully if json is malformed' do
103
+ expect(subject).to receive(:`).once
104
+ .and_return(json_error)
105
+ expect(subject).to_not receive(:run_cmd)
106
+ subject.remove('Anything')
107
+ end
108
+ end
109
+
110
+ describe '.connect' do
111
+ it 'should connect to nodes based on user agent' do
112
+ expect(subject).to receive(:blockchair_snapshot).once
113
+ .and_return(BLOCKCHAIR_SNAPSHOT)
114
+ expect(subject).to receive(:run_cmd)
115
+ .with('bchctl node connect "88.99.199.87:8333"')
116
+ subject.connect('Unlimited')
117
+ expect(subject).to receive(:run_cmd)
118
+ .with(%(bchctl node connect "#{ipv6_ip}"))
119
+ subject.connect('ABC')
120
+ end
121
+
122
+ it 'should return if the filter is falsy' do
123
+ expect(subject).to_not receive(:blockchair_snapshot)
124
+ expect(subject).to_not receive(:run_cmd)
125
+ subject.connect(false)
126
+ subject.connect(nil)
127
+ end
128
+
129
+ it 'should recover gracefully if json is malformed' do
130
+ expect(subject).to receive(:blockchair_snapshot).once
131
+ .and_return(json_error)
132
+ expect(subject).to_not receive(:run_cmd)
133
+ subject.connect('Anything')
134
+ end
135
+
136
+ it 'should raise an error if the limit is <= 0' do
137
+ expect { subject.connect('Anything', 0) }
138
+ .to raise_error('Limit must be greater than 0')
139
+ expect { subject.connect('Anything', -5) }
140
+ .to raise_error('Limit must be greater than 0')
141
+ end
142
+
143
+ context 'with a limit' do
144
+ it 'should respect a limit parameter of 1' do
145
+ expect(subject).to receive(:blockchair_snapshot).once
146
+ .and_return(BLOCKCHAIR_SNAPSHOT)
147
+ expect(subject).to receive(:run_cmd)
148
+ .with('bchctl node connect "88.99.199.87:8333"')
149
+ subject.connect('i', 1)
150
+ end
151
+
152
+ it 'should respect a limit parameter greater than 1' do
153
+ expect(subject).to receive(:blockchair_snapshot).once
154
+ .and_return(BLOCKCHAIR_SNAPSHOT)
155
+ expect(subject).to receive(:run_cmd)
156
+ .with('bchctl node connect "88.99.199.87:8333"')
157
+ expect(subject).to receive(:run_cmd)
158
+ .with(%(bchctl node connect "#{ipv6_ip}"))
159
+ subject.connect('i', 2)
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '.disconnect' do
165
+ it 'should disconnect nodes based on user agent' do
166
+ expect(subject).to receive(:`).once
167
+ .and_return(PEER_INFO)
168
+ expect(subject).to receive(:run_cmd)
169
+ .with(%(bchctl node disconnect "#{ipv6_ip}"))
170
+ subject.disconnect('SecondClient')
171
+ expect(subject).to receive(:run_cmd)
172
+ .with('bchctl node disconnect "131.114.88.218:33422"')
173
+ subject.disconnect('FirstClient')
174
+ end
175
+
176
+ it 'should return if the filter is falsy' do
177
+ expect(subject).to_not receive(:getpeerinfo)
178
+ expect(subject).to_not receive(:run_cmd)
179
+ subject.disconnect(false)
180
+ subject.disconnect(nil)
181
+ end
182
+
183
+ it 'should recover gracefully if json is malformed' do
184
+ expect(subject).to receive(:`).once
185
+ .and_return(json_error)
186
+ expect(subject).to_not receive(:run_cmd)
187
+ subject.disconnect('Anything')
188
+ end
189
+ end
190
+
191
+ describe '.display' do
192
+ it 'should display connected nodes' do
193
+ expect(subject).to receive(:`).once
194
+ .and_return(PEER_INFO)
195
+ expect(subject).to receive(:ap).with(node_hash).and_return(node_hash)
196
+ expect(subject.display).to eq(node_hash)
197
+ end
198
+
199
+ it 'should recover gracefully if json is malformed' do
200
+ expect(subject).to receive(:`).once
201
+ .and_return(json_error)
202
+ expect(subject).to receive(:ap).with({}).and_return({})
203
+ expect(subject.display).to eq({})
204
+ end
205
+ end
206
+
207
+ describe '.info' do
208
+ it 'should display local node info' do
209
+ expect(subject).to receive(:`).once
210
+ .and_return(NODE_INFO)
211
+ expect(subject).to receive(:ap).with(parsed_node_info)
212
+ .and_return(parsed_node_info)
213
+ expect(subject.info).to eq(parsed_node_info)
214
+ end
215
+
216
+ it 'should recover gracefully if json is malformed' do
217
+ expect(subject).to receive(:`).once
218
+ .and_return(json_error)
219
+ expect(subject).to receive(:ap).with({}).and_return({})
220
+ expect(subject.info).to eq({})
221
+ end
222
+ end
223
+
224
+ describe '.run' do
225
+ context 'with opts' do
226
+ let(:opts) do
227
+ {
228
+ debug: true,
229
+ info: true,
230
+ add: 'Wanted',
231
+ connect: 'Now',
232
+ remove: 'Nefarious',
233
+ disconnect: 'Fools',
234
+ limit: 1
235
+ }
236
+ end
237
+
238
+ it 'should call add, connect, remove, disconnect and info' do
239
+ expect(subject).to receive(:blockchair_snapshot).once
240
+ .and_return(BLOCKCHAIR_SNAPSHOT)
241
+ expect(subject).to receive(:getpeerinfo).once
242
+ .and_return(PEER_INFO)
243
+ expect(subject).to receive(:add).with(opts[:add], opts[:limit])
244
+ .and_call_original
245
+ expect(subject).to receive(:connect).with(opts[:connect], opts[:limit])
246
+ .and_call_original
247
+ expect(subject).to receive(:remove).with(opts[:remove], opts[:limit])
248
+ .and_call_original
249
+ expect(subject).to receive(:disconnect)
250
+ .with(opts[:disconnect], opts[:limit])
251
+ .and_call_original
252
+ expect(subject).to receive(:info)
253
+ expect(subject).to_not receive(:display)
254
+ subject.run
255
+ end
256
+
257
+ it 'should recover gracefully if json is malformed' do
258
+ expect(subject).to receive(:blockchair_snapshot).at_least(1)
259
+ .and_return(json_error)
260
+ expect(subject).to receive(:getpeerinfo).at_least(1)
261
+ .and_return(json_error)
262
+ expect(subject).to receive(:`)
263
+ .and_return(json_error)
264
+ expect(subject).to receive(:info)
265
+ .and_call_original
266
+ expect(subject).to receive(:ap).with({})
267
+ expect(subject).to_not receive(:run_cmd)
268
+ subject.run
269
+ end
270
+ end
271
+
272
+ context 'without opts' do
273
+ let(:opts) do
274
+ {}
275
+ end
276
+
277
+ it 'should call display' do
278
+ expect(subject).to receive(:`).once
279
+ .and_return(PEER_INFO)
280
+ expect(subject).to receive(:ap).with(node_hash).and_return(node_hash)
281
+ expect(subject).to receive(:display).and_call_original
282
+ subject.run
283
+ end
284
+
285
+ it 'should recover gracefully if json is malformed' do
286
+ expect(subject).to receive(:`).once
287
+ .and_return(json_error)
288
+ expect(subject).to_not receive(:run_cmd)
289
+ expect(subject).to_not receive(:info)
290
+ expect(subject).to receive(:ap).with({}).and_return({})
291
+ subject.run
292
+ end
293
+ end
294
+ end
295
+
296
+ describe 'clear_cache' do
297
+ it 'should clear the bitnodes cache' do
298
+ expect(subject).to receive(:blockchair_snapshot).once
299
+ .and_return(BLOCKCHAIR_SNAPSHOT)
300
+ expect(subject).to receive(:run_cmd)
301
+ .with(%(bchctl addnode "#{ipv6_ip}" add))
302
+ expect(subject).to receive(:run_cmd)
303
+ .with('bchctl addnode "88.99.199.87:8333" add')
304
+ subject.add('ABC')
305
+ subject.add('Unlimited')
306
+ subject.clear_cache
307
+ expect(subject).to receive(:blockchair_snapshot).once
308
+ .and_return(BLOCKCHAIR_SNAPSHOT)
309
+ expect(subject).to receive(:run_cmd)
310
+ .with(%(bchctl addnode "#{ipv6_ip}" add))
311
+ subject.add('ABC')
312
+ end
313
+
314
+ it 'should clear the getpeerinfo cache' do
315
+ expect(subject).to receive(:getpeerinfo).once
316
+ .and_return(PEER_INFO)
317
+ expect(subject).to receive(:run_cmd)
318
+ .with('bchctl node remove "131.114.88.218:33422"')
319
+ expect(subject).to receive(:run_cmd)
320
+ .with(%(bchctl node remove "#{ipv6_ip}"))
321
+ subject.remove('FirstClient')
322
+ subject.remove('SecondClient')
323
+ subject.clear_cache
324
+ expect(subject).to receive(:getpeerinfo).once
325
+ .and_return(PEER_INFO)
326
+ expect(subject).to receive(:run_cmd)
327
+ .with('bchctl node remove "131.114.88.218:33422"')
328
+ subject.remove('FirstClient')
329
+ end
330
+ end
331
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_helper'
4
+
5
+ describe 'Rubocop' do
6
+ it 'should pass with no offenses detected' do
7
+ expect(`rubocop`).to include('no offenses detected')
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.join('bundler', 'setup')
4
+
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ add_filter '/spec/'
8
+ end
9
+
10
+ require 'rspec'
11
+ require 'pickynode_bchd'
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pickynode-bchd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh Ellithorpe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: awesome_print
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: trollop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
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.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '12.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '12.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.48'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.48'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.14'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.14'
97
+ description: Some people are picky about the bitcoin cash nodes they connect to with
98
+ bchd.
99
+ email: quest@mac.com
100
+ executables:
101
+ - pickynode-bchd
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rubocop.yml"
107
+ - ".ruby-gemset"
108
+ - ".ruby-version"
109
+ - ".travis.yml"
110
+ - Gemfile
111
+ - Gemfile.lock
112
+ - LICENSE
113
+ - README.md
114
+ - Rakefile
115
+ - bin/pickynode-bchd
116
+ - lib/pickynode_bchd.rb
117
+ - pickynode_bchd.gemspec
118
+ - spec/mocks.rb
119
+ - spec/pickynode_spec.rb
120
+ - spec/rubocop_spec.rb
121
+ - spec/spec_helper.rb
122
+ homepage: http://github.com/zquestz/pickynode-bchd
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '2.0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.6.11
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Manage connections to your bchd node
146
+ test_files: []