Skip to content

Commit

Permalink
Improve robustness (#7)
Browse files Browse the repository at this point in the history
* Refactor fn for block stats

* Extend testing

* Add docstrings for blockchainRPCs

* Update version
  • Loading branch information
vnegi10 authored May 7, 2022
1 parent e2749df commit 4847bcf
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 33 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "BitcoinRPC"
uuid = "9b85cdd3-b0ee-4013-9538-086350204d42"
authors = ["Vikas Negi <[email protected]>"]
version = "0.1.1"
version = "0.1.2"

[deps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Expand Down
30 changes: 17 additions & 13 deletions src/analytics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,22 @@ function collect_block_stats(auth::UserAuth, block_start::Int64, block_end::Int6

@assert 0 block_start < block_end show_block_count(auth) "Invalid block height"

results = Array{Dict}(undef, block_end - block_start + 1)
j = 1

for i = block_start:block_end
if isempty(stats)
results[j] = show_block_stats(auth, hashORheight = i)
delete!(results[j], "feerate_percentiles")
else
results[j] = show_block_stats(auth, hashORheight = i, stats = stats)
end
j += 1
end
results = Dict{String, Any}[]

get_params(i::Int64) = isempty(stats) ? [i] : [i, stats]

for i = block_start:block_end
try
result = post_request(auth, "getblockstats"; params = get_params(i))
delete!(result, "feerate_percentiles")
sato_to_btc!(result)
push!(results, result)
catch e
@info "Ran into error $(e)"
@info "Could not fetch data for block $(i), will continue to next!"
continue
end
end

df_stats = DataFrame()

Expand Down Expand Up @@ -84,7 +88,7 @@ function collect_network_stats(auth::UserAuth, block_start::Int64, block_end::In
df_stats = collect_block_stats(auth, block_start, block_end,
stats = ["height", "time"])

network_hash = [show_network_hashps(auth, height = h) for h = block_start:block_end]
network_hash = [show_network_hashps(auth, height = h) for h in df_stats[!, :height]]

# Based on calculation from https://en.bitcoin.it/wiki/Difficulty
# Network hash rate = D * 2**32 / 600
Expand Down
173 changes: 155 additions & 18 deletions src/blockchainRPC.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
show_best_block_hash(auth::UserAuth)
Returns the hash of the best (tip) block in the most-work fully-validated chain.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Example
```julia-repl
julia> show_best_block_hash(auth)
"00000000000000000004202bf6a4b09fcfde643a4a1d206f18949308bc505011"
```
"""
function show_best_block_hash(auth::UserAuth)
return do_try_catch(auth, "getbestblockhash")
Expand All @@ -13,9 +22,29 @@ end
## https://developer.bitcoin.org/reference/rpc/getblock.html

"""
show_block(auth::UserAuth; blockhash::String, verbosity::Int64 = 0)
show_block(auth::UserAuth; blockhash::String, verbosity::Int64 = 1)
Get block data depending on selected verbosity.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Optional keywords
- `verbosity::Int64` : If verbosity is 0, returns a string that is serialized, hex-encoded data
for block `hash`. If verbosity is 1, returns an object with information
about block `hash`. If verbosity is 2, returns an object with information
about block `hash` and information about each transaction.
# Example
```julia-repl
julia> show_block(auth, blockhash = show_best_block_hash(auth), verbosity = 1)
Dict{String, Any} with 18 entries:
"time" => DateTime("2022-05-07T01:13:07")
"difficulty" => 2.97944e13
"bits" => "17097275"
"previousblockhash" => "00000000000000000007c2e512cf0b3f99cec4334c3499f4d477babc63a1b77b"
"merkleroot" => "d0fda1281cec9719be434acb24a81715f98ac0948a445f1b6b4e967d8781615c"
```
"""
function show_block(auth::UserAuth; blockhash::String, verbosity::Int64 = 0)

Expand All @@ -29,6 +58,21 @@ end
show_blockchain_info(auth::UserAuth)
Returns an object containing various state info regarding blockchain processing.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Example
```julia-repl
julia> show_blockchain_info(auth)
Dict{String, Any} with 13 entries:
"verificationprogress" => 0.999998
"difficulty" => 2.97944e13
"chain" => "main"
"initialblockdownload" => false
"size_on_disk" => 459765806919
...
```
"""
function show_blockchain_info(auth::UserAuth)
return do_try_catch(auth, "getblockchaininfo")
Expand All @@ -41,6 +85,15 @@ end
show_block_count(auth::UserAuth)
Returns the height of the most-work fully-validated chain.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Example
```julia-repl
julia> show_block_count(auth)
735241
```
"""
function show_block_count(auth::UserAuth)
return do_try_catch(auth, "getblockcount")
Expand All @@ -53,6 +106,16 @@ end
show_block_hash(auth::UserAuth; height::Int64 = 0)
Returns hash of block in best-block-chain at height provided.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
- `height::Int64` : Height of the desired block, default is set to 0.
# Example
```julia-repl
julia> show_block_hash(auth, height = 696969)
"0000000000000000000b3e2716da675f99df43134a67fc1987c5590f1c370472"
```
"""
function show_block_hash(auth::UserAuth; height::Int64 = 0)

Expand All @@ -67,9 +130,28 @@ end
"""
show_block_header(auth::UserAuth; blockhash::String, verbose::Bool=true)
If verbose is false, returns a string that is serialized, hex-encoded data for blockheader `hash`.
Returns the contents of the block header.
If verbose is true, returns an Object with information about blockheader `hash`.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
- `blockhash::String` : Hash of the desired block
# Optional keywords
- `verbose::Bool` : If verbose is false, returns a string that is serialized, hex-encoded
data for blockheader `hash`. If verbose is true (default), returns an
object with information about blockheader `hash`.
# Result
```julia-repl
julia> show_block_header(auth, blockhash = show_block_hash(auth, height = 700_000))
Dict{String, Any} with 15 entries:
"time" => DateTime("2021-09-11T04:14:32")
"difficulty" => 1.84152e13
"bits" => "170f48e4"
"previousblockhash" => "0000000000000000000aa3ce000eb559f4143be419108134e0ce71042fc636eb"
"nextblockhash" => "00000000000000000002f39baabb00ffeb47dbdb425d5077baa62c47482b7e92"
...
```
"""
function show_block_header(auth::UserAuth; blockhash::String, verbose::Bool=true)

Expand All @@ -83,25 +165,34 @@ end
show_block_stats(auth::UserAuth; hashORheight::StringOrInt = 0, stats = "")
Compute per block statistics for a given window. All amounts are in satoshis.
"""
function show_block_stats(auth::UserAuth; hashORheight::StringOrInt = 0, stats = "")
result = ""
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
if isempty(stats)
result = do_try_catch(auth, "getblockstats", params = [hashORheight])
else
result = do_try_catch(auth, "getblockstats", params = [hashORheight, stats])
end
# Optional keywords
- `hashORheight::StringOrInt` : Block hash or height
- `stats` : Specific group of stats, e.g. ["avgfee", "utxo_increase"]
all_keys = keys(result) |> collect
# Example
```julia-repl
julia> show_block_stats(auth, hashORheight = 500_000)
Dict{String, Any} with 29 entries:
"avgtxsize" => 388
"time" => DateTime("2017-12-18T18:35:25")
"totalfee" => 3.39352
"utxo_increase" => 1899
"total_out" => 1401737618054
...
```
"""
function show_block_stats(auth::UserAuth; hashORheight::StringOrInt = 0, stats = "")

# Convert Satoshis to BTC
for key in all_keys
if occursin("fee", key)
result[key] /= 1e8
end
end
result = ""

get_params() = isempty(stats) ? [hashORheight] : [hashORheight, stats]

result = do_try_catch(auth, "getblockstats", params = get_params())
sato_to_btc!(result)

return result
end
Expand All @@ -114,6 +205,16 @@ end
Return information about all known tips in the block tree, including the main chain as well
as orphaned branches.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Example
```julia-repl
julia> show_chain_tips(auth)
1-element Vector{Any}:
Dict{String, Any}("height" => 735239, "branchlen" => 0, "status" => "active", "hash" => ...
```
"""
function show_chain_tips(auth::UserAuth)
return do_try_catch(auth, "getchaintips")
Expand Down Expand Up @@ -173,6 +274,15 @@ end
show_difficulty(auth::UserAuth)
Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Example
```julia-repl
julia> show_difficulty(auth)
2.979440758931208e13
```
"""
function show_difficulty(auth::UserAuth)
return do_try_catch(auth, "getdifficulty")
Expand Down Expand Up @@ -224,6 +334,20 @@ end
show_mempool_info(auth::UserAuth)
Returns details on the active state of the TX memory pool.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Example
```julia-repl
julia> show_mempool_info(auth)
Dict{String, Any} with 9 entries:
"unbroadcastcount" => 0
"maxmempool" => 1000000000
"bytes" => 1272046
"loaded" => true
"usage" => 6281104
...
"""
function show_mempool_info(auth::UserAuth)

Expand All @@ -237,6 +361,19 @@ end
show_mempool_raw(auth::UserAuth; verbose::Bool = false, mempool_sequence::Bool = false)
Returns all transaction ids in memory pool as a json array of string transaction ids.
# Arguments
- `auth::UserAuth` : User credentials, e.g. `auth = UserAuth("username", "password", port)`
# Example
```julia-repl
julia> show_mempool_raw(auth)
2225-element Vector{String}:
"46db70c88170e584b6787adc7561a6aa06ddce51f8bcd34bd4576ce0114e55ea"
"5e17a3b3bf10e137050734ac884fb496e5933ebc7401764f62c60b18f58df891"
"3fa233c8bcb8f2002b4997507c36a887c76359719c643a7e93541a7230f84b9c"
...
```
"""
function show_mempool_raw(auth::UserAuth; verbose::Bool = false, mempool_sequence::Bool = false)

Expand Down
14 changes: 14 additions & 0 deletions src/helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,19 @@ function do_try_catch(auth::UserAuth, method::String; params = [])
end
end

return result
end

# Convert Satoshis to BTC
function sato_to_btc!(result)

all_keys = keys(result) |> collect

for key in all_keys
if occursin("fee", key)
result[key] /= 1e8
end
end

return result
end
12 changes: 11 additions & 1 deletion test/test_analytics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,30 @@

df_stats_1 = collect_block_stats(AUTH, 700_000, 700_010)
rows, cols = size(df_stats_1)

@test rows == 11 && cols == 28
@test df_stats_1[!, :avgtxsize][5] == 831
@test df_stats_1[!, :maxtxsize][10] == 46079

df_stats_2 = collect_block_stats(AUTH, 700_000, 700_100,
stats = ["avgfee", "utxo_increase"])
stats = ["avgfee", "utxo_increase"])
rows, cols = size(df_stats_2)

@test rows == 101 && cols == 2
@test df_stats_2[!, :utxo_increase][5] == -129
@test isapprox(df_stats_2[!, :avgfee][10], 3.048e-5; atol = 1e-5)

end

@testset "collect_network_stats" begin

df_stats = collect_network_stats(AUTH, 700_000, 700_100)
rows, cols = size(df_stats)

@test rows == 101 && cols == 4
@test isapprox(df_stats[!, :network_hash][5], 1.305148094815497e20;
atol = 0.5e20)
@test df_stats[!, :time][10] == DateTime(2021,9,11,5,13,25)

end

Expand Down

2 comments on commit 4847bcf

@vnegi10
Copy link
Owner Author

@vnegi10 vnegi10 commented on 4847bcf May 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

  • More robust functions for analytics
  • Minor refactoring
  • Extended testing
  • Updated documentation

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/59851

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.1.2 -m "<description of version>" 4847bcf8acf03fd5ba094454fa136dfda35a2a49
git push origin v0.1.2

Please sign in to comment.