diff --git a/Project.toml b/Project.toml index 3e7de85..fb8e931 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "BitcoinRPC" uuid = "9b85cdd3-b0ee-4013-9538-086350204d42" authors = ["Vikas Negi "] -version = "0.1.1" +version = "0.1.2" [deps] DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" diff --git a/src/analytics.jl b/src/analytics.jl index 80595c1..57e8462 100644 --- a/src/analytics.jl +++ b/src/analytics.jl @@ -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() @@ -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 diff --git a/src/blockchainRPC.jl b/src/blockchainRPC.jl index 9cb2f6b..5470812 100644 --- a/src/blockchainRPC.jl +++ b/src/blockchainRPC.jl @@ -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") @@ -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) @@ -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") @@ -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") @@ -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) @@ -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) @@ -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 @@ -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") @@ -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") @@ -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) @@ -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) diff --git a/src/helpers.jl b/src/helpers.jl index f6debf2..0a2b4ea 100644 --- a/src/helpers.jl +++ b/src/helpers.jl @@ -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 \ No newline at end of file diff --git a/test/test_analytics.jl b/test/test_analytics.jl index d818efa..85c33b4 100644 --- a/test/test_analytics.jl +++ b/test/test_analytics.jl @@ -6,12 +6,18 @@ 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 @@ -19,7 +25,11 @@ 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