pickynode-bchd 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: 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: []