blockchain-node 0.0.4 → 0.0.5
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 +4 -4
- data/README-RPC-ETH.md +72 -0
- data/lib/blockchain-node.rb +11 -7
- data/lib/blockchain-node/client.rb +5 -3
- data/lib/blockchain-node/model.rb +4 -0
- data/lib/blockchain-node/model/base.rb +14 -0
- data/lib/blockchain-node/model/bitcoin.rb +31 -0
- data/lib/blockchain-node/model/ethereum.rb +69 -0
- data/lib/blockchain-node/request.rb +1 -0
- data/lib/blockchain-node/version.rb +1 -1
- data/spec/lib/blockchain-node_spec.rb +27 -8
- data/spec/lib/model/ethereum_spec.rb +40 -0
- data/spec/support/webmock.rb +20 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2eb2210404685d0f9387472304b7192c65e1dabf3a28379fd87e627274641c14
|
4
|
+
data.tar.gz: efbe1c81188317edd1056607b6711cc5491c2af673a61214cca5f42417ee853f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af08f01a79818ede3f831ded4542fcbda935a0b58805b74043485121b5abb032b94c111ace586990a4ebd7c405c9ce36b68f99db06c731f1abed1075b7f85e1c
|
7
|
+
data.tar.gz: 902f98131a4ff58824c0c35affee28fa2a5ea399884499e982a1215f046fc39adeb779000da5a4f8ef1c789272e28ab182b2708c7856ac38e078b3ad94eb7ce8
|
data/README-RPC-ETH.md
CHANGED
@@ -1,6 +1,78 @@
|
|
1
1
|
# Ethereum RPC Methods Calls
|
2
2
|
|
3
3
|
|
4
|
+
## Personal
|
5
|
+
|
6
|
+
The personal API manages private keys in the key store.
|
7
|
+
|
8
|
+
### personal_listAccounts
|
9
|
+
|
10
|
+
Returns all the Ethereum account addresses of all keys in the key store.
|
11
|
+
|
12
|
+
|
13
|
+
#### Example
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
client.personal_listAccounts
|
17
|
+
=> ["0x5e97870f263700f46aa00d967821199b9bc5a120", "0x3d80b31a78c30fc628f20b2c89d7ddbf6e53cedc"]
|
18
|
+
```
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
### personal_newAccount
|
23
|
+
|
24
|
+
Generates a new private key and stores it in the key store directory.
|
25
|
+
The key file is encrypted with the given passphrase.
|
26
|
+
Returns the address of the new account.
|
27
|
+
|
28
|
+
#### Parameters
|
29
|
+
|
30
|
+
| Name | Optional | Description |
|
31
|
+
| :----------: | --------- | ------------------------------------------ |
|
32
|
+
| password | yes | The password to use to encrypt the wallet |
|
33
|
+
|
34
|
+
#### Response
|
35
|
+
|
36
|
+
The address of the new account.
|
37
|
+
|
38
|
+
#### Example
|
39
|
+
|
40
|
+
``` ruby
|
41
|
+
client.personal_newAccount('SecurePassword')
|
42
|
+
=> {:response=>"0x4e6f002a07a7e5f74fdaaa6e730557782405fa05"}
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
## Blockchain Methods
|
47
|
+
|
48
|
+
### eth_getBalance
|
49
|
+
|
50
|
+
Returns the balance of the account of given address at a given block.
|
51
|
+
|
52
|
+
#### Parameters
|
53
|
+
|
54
|
+
| Name | Optional | Description |
|
55
|
+
| :----------: | --------- | ------------------------------------------ |
|
56
|
+
| address | no | The address to check for balance. |
|
57
|
+
| block | no | QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending" |
|
58
|
+
|
59
|
+
#### Response
|
60
|
+
|
61
|
+
`QUANTITY` - integer of the current balance in wei in hex.
|
62
|
+
|
63
|
+
|
64
|
+
#### Example
|
65
|
+
|
66
|
+
``` ruby
|
67
|
+
client.eth_getBalance('0x5e97870f263700f46aa00d967821199b9bc5a120', 'latest')
|
68
|
+
=> {:response=>"0x4e6f002a07a7e5f74fdaaa6e730557782405fa05"}
|
69
|
+
```
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
## Reference: ALL ETH / WEB METHODS
|
75
|
+
|
4
76
|
```
|
5
77
|
|
6
78
|
web3_clientVersion
|
data/lib/blockchain-node.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
require_relative 'blockchain-node/client'
|
2
|
+
require_relative 'blockchain-node/configuration'
|
3
|
+
require_relative 'blockchain-node/errors'
|
4
|
+
require_relative 'blockchain-node/request'
|
5
|
+
require_relative 'blockchain-node/version'
|
6
|
+
|
7
|
+
require_relative 'blockchain-node/model'
|
8
|
+
require_relative 'blockchain-node/model/base'
|
9
|
+
require_relative 'blockchain-node/model/bitcoin'
|
10
|
+
require_relative 'blockchain-node/model/ethereum'
|
7
11
|
|
12
|
+
module BlockchainNode
|
8
13
|
def self.configure
|
9
14
|
yield Configuration if block_given?
|
10
15
|
end
|
@@ -16,5 +21,4 @@ module BlockchainNode
|
|
16
21
|
end
|
17
22
|
config
|
18
23
|
end
|
19
|
-
|
20
24
|
end
|
@@ -4,12 +4,13 @@ module BlockchainNode
|
|
4
4
|
@@_auth_token
|
5
5
|
AuthToken = Struct.new(:token, :expires_at)
|
6
6
|
|
7
|
+
Details = Struct.new(:id, :blockchain, :network, :status, :height)
|
8
|
+
|
7
9
|
attr_accessor :node_id
|
8
10
|
attr_accessor :configuration
|
9
11
|
|
10
12
|
def initialize(node_id = nil)
|
11
13
|
@node_id = node_id
|
12
|
-
# allow a different configuration per client instance
|
13
14
|
@configuration = BlockchainNode::Configuration.new
|
14
15
|
end
|
15
16
|
|
@@ -19,7 +20,8 @@ module BlockchainNode
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def details
|
22
|
-
request.get(path: nodes_path, auth_token: auth_token)
|
23
|
+
r = request.get(path: nodes_path, auth_token: auth_token)
|
24
|
+
Details.new(r["id"], r["blockchain"], r["network"], r["status"], Integer(r["height"] || 0))
|
23
25
|
end
|
24
26
|
|
25
27
|
def auth_token
|
@@ -37,7 +39,7 @@ module BlockchainNode
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def auth_token_expired?
|
40
|
-
@@_auth_token.nil? || @@_auth_token.expires_at < Time.now.utc
|
42
|
+
@@_auth_token.nil? || @@_auth_token.expires_at < Time.now.utc + 30
|
41
43
|
end
|
42
44
|
|
43
45
|
def get_new_auth_token
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module BlockchainNode
|
2
|
+
module Model
|
3
|
+
class Bitcoin < Base
|
4
|
+
|
5
|
+
def blockchain
|
6
|
+
'bitcoin'
|
7
|
+
end
|
8
|
+
|
9
|
+
def wallet_info
|
10
|
+
@client.getwalletinfo
|
11
|
+
end
|
12
|
+
|
13
|
+
def total_balance
|
14
|
+
@client.getbalance
|
15
|
+
end
|
16
|
+
|
17
|
+
def account_balances(account = "")
|
18
|
+
@client.listaddressgroupings
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_address(account = "")
|
22
|
+
@client.getnewaddress(account)
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_transaction(address, amount)
|
26
|
+
@client.sendtoaddress(address, amount)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module BlockchainNode
|
2
|
+
module Model
|
3
|
+
class Ethereum < Base
|
4
|
+
|
5
|
+
DECIMALS_18 = 1000000000000000000.0
|
6
|
+
|
7
|
+
def blockchain
|
8
|
+
'ethereum'
|
9
|
+
end
|
10
|
+
|
11
|
+
def accounts
|
12
|
+
@client.personal_listAccounts
|
13
|
+
end
|
14
|
+
|
15
|
+
def balanceOf(account)
|
16
|
+
resp = @client.eth_getBalance(account, 'latest')
|
17
|
+
hex_to_int(resp[:response]) / DECIMALS_18
|
18
|
+
end
|
19
|
+
|
20
|
+
def unlock(account, password, seconds = 30)
|
21
|
+
@client.personal_unlockAccount(account, password, seconds)
|
22
|
+
end
|
23
|
+
|
24
|
+
# sends a transaction. Returns the transaction ID
|
25
|
+
def send(from, to, ether)
|
26
|
+
wei_to_send = (ether * DECIMALS_18).round
|
27
|
+
value = '0x' + wei_to_send.to_s(16)
|
28
|
+
tx = { from: from, to: to, value: value }
|
29
|
+
@client.eth_sendTransaction(tx)[:response]
|
30
|
+
end
|
31
|
+
|
32
|
+
def unlock_and_send(password, from, to, ether)
|
33
|
+
unlock(from, password, 10)
|
34
|
+
send(from, to, ether)
|
35
|
+
end
|
36
|
+
|
37
|
+
def highest_block
|
38
|
+
hex_to_int(@client.eth_blockNumber[:response])
|
39
|
+
end
|
40
|
+
|
41
|
+
def transactions_for_account(account, startBlock = nil, endBlock = nil)
|
42
|
+
endBlock = highest_block if endBlock.nil?
|
43
|
+
startBlock = endBlock - 1000 if startBlock.nil?
|
44
|
+
account.downcase!
|
45
|
+
|
46
|
+
found_transactions = []
|
47
|
+
|
48
|
+
(startBlock..endBlock).each do |block|
|
49
|
+
response = @client.eth_getBlockByNumber(int_to_hex(block), true)
|
50
|
+
found_transactions += response["transactions"].select{ |t| t["from"].try(:downcase) == account || t["to"].try(:downcase) == account }
|
51
|
+
end
|
52
|
+
|
53
|
+
found_transactions
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def hex_to_int(hex)
|
59
|
+
Integer(hex)
|
60
|
+
end
|
61
|
+
|
62
|
+
def int_to_hex(int)
|
63
|
+
'0x' + int.to_s(16)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
@@ -33,18 +33,37 @@ describe BlockchainNode::Configuration do
|
|
33
33
|
|
34
34
|
describe "oauth tokens" do
|
35
35
|
it "requests a new token if 1 is expired" do
|
36
|
+
Timecop.travel(Time.new(2019,1,10,12,0,0).utc)
|
37
|
+
|
36
38
|
expect_any_instance_of(BlockchainNode::Request).to receive(:process_request).exactly(3).times.and_return(
|
37
39
|
{
|
38
|
-
"access_token" => "
|
39
|
-
"expires_in" => 7200, "created_at" => Time.
|
40
|
-
}
|
40
|
+
"access_token" => "token1",
|
41
|
+
"expires_in" => 7200, "created_at" => Time.new(2019,1,10,12,0,0).utc.to_i
|
42
|
+
},
|
43
|
+
{
|
44
|
+
"access_token" => "token2",
|
45
|
+
"expires_in" => 7200, "created_at" => Time.new(2019,1,10,14,0,0).utc.to_i
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"access_token" => "token3",
|
49
|
+
"expires_in" => 7200, "created_at" => Time.new(2019,1,10,15,59,45).utc.to_i
|
50
|
+
},
|
41
51
|
)
|
42
52
|
client = BlockchainNode::Client.new(NODE_ID)
|
43
|
-
client.auth_token
|
44
|
-
|
45
|
-
|
46
|
-
Timecop.travel(Time.
|
47
|
-
client.auth_token
|
53
|
+
client.auth_token # should get an initial auth token
|
54
|
+
expect(client.auth_token).to eq("token1")
|
55
|
+
|
56
|
+
Timecop.travel(Time.new(2019,1,10,14,0,0))
|
57
|
+
client.auth_token # should get a new auth token
|
58
|
+
expect(client.auth_token).to eq("token2")
|
59
|
+
|
60
|
+
Timecop.travel(Time.new(2019,1,10,15,59,15))
|
61
|
+
client.auth_token # should NOT get a new token
|
62
|
+
expect(client.auth_token).to eq("token2")
|
63
|
+
|
64
|
+
Timecop.travel(Time.new(2019,1,10,15,59,45))
|
65
|
+
client.auth_token # should get a new auth token
|
66
|
+
expect(client.auth_token).to eq("token3")
|
48
67
|
end
|
49
68
|
end
|
50
69
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BlockchainNode::Model::Ethereum do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
BlockchainNode.configure do |config|
|
7
|
+
config.client_id = CLIENT_ID
|
8
|
+
config.client_secret = CLIENT_SECRET
|
9
|
+
config.request_options = { }
|
10
|
+
end
|
11
|
+
|
12
|
+
@client = BlockchainNode::Client.new(NODE_ID)
|
13
|
+
@model = BlockchainNode::Model::Ethereum.new(@client)
|
14
|
+
end
|
15
|
+
|
16
|
+
before :each do
|
17
|
+
stub_oauth
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns a list of accounts" do
|
21
|
+
stub_ethereum_list_accounts
|
22
|
+
expect(@model.accounts.first).to eq(ETHEREUM_ACCOUNT)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns a balance of account" do
|
26
|
+
stub_ethereum_balance_of
|
27
|
+
expect(@model.balanceOf(ETHEREUM_ACCOUNT)).to eq(10)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns the highest block" do
|
31
|
+
stub_ethereum_highest_block
|
32
|
+
expect(@model.highest_block).to eq(4972496)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns sends" do
|
36
|
+
stub_ethereum_send
|
37
|
+
expect(@model.send("ACCOUNT1", "ACCOUNT2", 1.0)).to eq(ETHEREUM_TRANSACTION)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/spec/support/webmock.rb
CHANGED
@@ -41,3 +41,23 @@ def api_method_body(method, *params)
|
|
41
41
|
}.to_json
|
42
42
|
end
|
43
43
|
|
44
|
+
# ethereum mocks
|
45
|
+
ETHEREUM_ACCOUNT = "0x829bd824b016326a401d083b33d092293333a830"
|
46
|
+
ETHEREUM_TRANSACTION = "0x2e9a95887534709e23ba62a54679cfb073b819847be1fd039c1517b851354923"
|
47
|
+
def stub_ethereum_list_accounts
|
48
|
+
stub_request(:post, "#{BlockchainNode::Request::DEFAULT_BASE_URL}/api/nodes/#{NODE_ID}").
|
49
|
+
to_return(status: 200, body: "[\"#{ETHEREUM_ACCOUNT}\"]" )
|
50
|
+
end
|
51
|
+
def stub_ethereum_balance_of
|
52
|
+
stub_request(:post, "#{BlockchainNode::Request::DEFAULT_BASE_URL}/api/nodes/#{NODE_ID}").
|
53
|
+
to_return(status: 200, body: "0x8ac7230489e80000" )
|
54
|
+
end
|
55
|
+
def stub_ethereum_highest_block
|
56
|
+
stub_request(:post, "#{BlockchainNode::Request::DEFAULT_BASE_URL}/api/nodes/#{NODE_ID}").
|
57
|
+
to_return(status: 200, body: "0x4bdfd0" )
|
58
|
+
end
|
59
|
+
def stub_ethereum_send
|
60
|
+
stub_request(:post, "#{BlockchainNode::Request::DEFAULT_BASE_URL}/api/nodes/#{NODE_ID}").
|
61
|
+
with(body: '{"method":"eth_sendTransaction","parameters":[{"from":"ACCOUNT1","to":"ACCOUNT2","value":"0xde0b6b3a7640000"}]}').
|
62
|
+
to_return(status: 200, body: ETHEREUM_TRANSACTION )
|
63
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blockchain-node
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Pestritto
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -96,9 +96,14 @@ files:
|
|
96
96
|
- lib/blockchain-node/client.rb
|
97
97
|
- lib/blockchain-node/configuration.rb
|
98
98
|
- lib/blockchain-node/errors.rb
|
99
|
+
- lib/blockchain-node/model.rb
|
100
|
+
- lib/blockchain-node/model/base.rb
|
101
|
+
- lib/blockchain-node/model/bitcoin.rb
|
102
|
+
- lib/blockchain-node/model/ethereum.rb
|
99
103
|
- lib/blockchain-node/request.rb
|
100
104
|
- lib/blockchain-node/version.rb
|
101
105
|
- spec/lib/blockchain-node_spec.rb
|
106
|
+
- spec/lib/model/ethereum_spec.rb
|
102
107
|
- spec/spec_helper.rb
|
103
108
|
- spec/support/webmock.rb
|
104
109
|
homepage: https://blockchainnode.io
|
@@ -123,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
128
|
version: '0'
|
124
129
|
requirements: []
|
125
130
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.7.
|
131
|
+
rubygems_version: 2.7.9
|
127
132
|
signing_key:
|
128
133
|
specification_version: 4
|
129
134
|
summary: BlockchainNode Ruby Client
|