Module: PWN::Blockchain::BTC
- Defined in:
- lib/pwn/blockchain/btc.rb
Overview
Read-only Bitcoin Core intelligence. No wallet, signing or broadcast RPCs.
Defined Under Namespace
Classes: RPCError
Constant Summary collapse
- READ_METHODS =
%w[getblockchaininfo getblockhash getblock getblockheader getrawtransaction gettxout getmempoolinfo validateaddress].freeze
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.chain_status(opts = {}) ⇒ Object
- Supported Method Parameters
Return blockchain synchronization, pruning and active chain status.
-
.get_block_details(opts = {}) ⇒ Object
- Supported Method Parameters
Read a block at a height with selectable Core verbosity.
-
.get_latest_block ⇒ Object
- Supported Method Parameters
Return the legacy JSON-RPC envelope with blockchain status, without AI.
-
.get_transactions(opts = {}) ⇒ Object
- Supported Method Parameters
Return legacy transaction IDs only when the entire explicit height range was scanned.
-
.help ⇒ Object
Display usage for this module.
-
.inspect_outpoint(opts = {}) ⇒ Object
- Supported Method Parameters
Inspect an outpoint in the current UTXO view; null means spent or unknown, not proven spent.
-
.inspect_transaction(opts = {}) ⇒ Object
- Supported Method Parameters
Inspect transaction amounts and proven input fees; unavailable prevouts remain explicit.
-
.mempool_summary(opts = {}) ⇒ Object
- Supported Method Parameters
Read aggregate mempool statistics without enumerating transactions.
-
.scan_address_activity(opts = {}) ⇒ Object
- Supported Method Parameters
Scan address scripts in explicit blocks, not an arbitrary address index or wallet history.
-
.scan_transactions(opts = {}) ⇒ Object
- Supported Method Parameters
Scan a bounded page by UTC block-header dates without assuming monotonic timestamps.
-
.trace_transaction(opts = {}) ⇒ Object
- Supported Method Parameters
Trace only transaction ancestors using input references, never ownership or change heuristics.
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. [email protected]
490 491 492 |
# File 'lib/pwn/blockchain/btc.rb', line 490 public_class_method def self. 'AUTHOR(S): 0day Inc. <[email protected]>' end |
.chain_status(opts = {}) ⇒ Object
- Supported Method Parameters
Return blockchain synchronization, pruning and active chain status. PWN::Blockchain::BTC.chain_status(timeout: 'optional - RPC timeout seconds, 1..300; default 30')
102 103 104 105 |
# File 'lib/pwn/blockchain/btc.rb', line 102 public_class_method def self.chain_status(opts = {}) timeout = opts.key?(:timeout) ? opts[:timeout] : 30 btc_rpc_call(method: 'getblockchaininfo', timeout: timeout)[:result] end |
.get_block_details(opts = {}) ⇒ Object
- Supported Method Parameters
Read a block at a height with selectable Core verbosity. PWN::Blockchain::BTC.get_block_details( height: 'optional - nonnegative integer height; default current tip', verbosity: 'optional - integer 0..3; default 1; 3 requires node undo data', timeout: 'optional - RPC timeout seconds, 1..300; default 30' )
114 115 116 117 118 119 120 121 122 |
# File 'lib/pwn/blockchain/btc.rb', line 114 public_class_method def self.get_block_details(opts = {}) timeout = opts.fetch(:timeout, 30) verbosity = integer_option(value: opts.fetch(:verbosity, 1), name: 'verbosity', range: 0..3) height = opts[:height] height = chain_status(timeout: timeout)[:blocks] if height.nil? integer_option(value: height, name: 'height', range: 0..2_147_483_647) hash = btc_rpc_call(method: 'getblockhash', params: [height], timeout: timeout)[:result] btc_rpc_call(method: 'getblock', params: [hash, verbosity], timeout: timeout)[:result] end |
.get_latest_block ⇒ Object
- Supported Method Parameters
Return the legacy JSON-RPC envelope with blockchain status, without AI. PWN::Blockchain::BTC.get_latest_block
95 96 97 |
# File 'lib/pwn/blockchain/btc.rb', line 95 public_class_method def self.get_latest_block btc_rpc_call(method: 'getblockchaininfo') end |
.get_transactions(opts = {}) ⇒ Object
- Supported Method Parameters
Return legacy transaction IDs only when the entire explicit height range was scanned. PWN::Blockchain::BTC.get_transactions( from: 'required - inclusive UTC date YYYY-MM-DD', to: 'required - inclusive UTC date YYYY-MM-DD', start_height: 'required - first nonnegative integer height', end_height: 'required - final inclusive integer height', max_blocks: 'optional - full-range budget 1..1000; default 100; use scan_transactions for pages', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' )
424 425 426 427 428 429 430 431 432 |
# File 'lib/pwn/blockchain/btc.rb', line 424 public_class_method def self.get_transactions(opts = {}) bounds = scan_bounds(opts) raise ArgumentError, 'Range exceeds max_blocks; use scan_transactions and next_height' if bounds[:page_end] < opts[:end_height] result = scan_transactions(opts) raise RPCError.new(kind: 'incomplete scan; use scan_transactions for evidence'), cause: nil unless result[:complete] result[:transactions].map { |tx| tx[:txid] } end |
.help ⇒ Object
Display usage for this module.
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 |
# File 'lib/pwn/blockchain/btc.rb', line 495 public_class_method def self.help puts "USAGE: # Return the legacy blockchain JSON-RPC envelope without AI analysis. #{self}.get_latest_block # Read chain synchronization and pruning status. #{self}.chain_status(timeout: 'optional - RPC timeout seconds, 1..300; default 30') # Read a block by height with Core verbosity. #{self}.get_block_details( height: 'optional - nonnegative integer height; default current tip', verbosity: 'optional - integer 0..3; default 1; 3 requires undo data', timeout: 'optional - RPC timeout seconds, 1..300; default 30' ) # Inspect exact transaction amounts and fees only when all input values are known. #{self}.inspect_transaction( txid: 'required - 64-character hexadecimal transaction hash', blockhash: 'optional - containing block hash for root lookup without txindex', max_prevouts: 'optional - parent RPC lookup budget 0..1000; default 100', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' ) # Check current UTXO membership; absence means spent or unknown, not proof of spending. #{self}.inspect_outpoint( txid: 'required - 64-character hexadecimal transaction hash', vout: 'required - nonnegative integer output index', include_mempool: 'optional - boolean include mempool effects; default true', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' ) # Read aggregate mempool statistics without enumerating transactions. #{self}.mempool_summary(timeout: 'optional - per-RPC timeout seconds, 1..300; default 30') # Trace ancestor input references, not ownership, change, or later output allocation. #{self}.trace_transaction( txid: 'required - 64-character hexadecimal root transaction hash', blockhash: 'optional - containing block hash for root lookup without txindex', max_depth: 'optional - ancestor depth 0..100; default 3', max_transactions: 'optional - total transaction RPC budget 1..1000; default 100', max_edges: 'optional - evidence edge cap 1..10000; default 1000', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' ) # Scan each explicit height for UTC dates; timestamps are not monotonic. Returns evidence and next_height. #{self}.scan_transactions( from: 'required - inclusive UTC date YYYY-MM-DD', to: 'required - inclusive UTC date YYYY-MM-DD', start_height: 'required - first nonnegative integer height; resume with next_height', end_height: 'required - final inclusive integer height; keep fixed across pages', max_blocks: 'optional - examined blocks per page 1..1000; default 100', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' ) # Return the legacy ID array only for complete explicit height ranges; incomplete scans raise. #{self}.get_transactions( from: 'required - inclusive UTC date YYYY-MM-DD', to: 'required - inclusive UTC date YYYY-MM-DD', start_height: 'required - first nonnegative integer height', end_height: 'required - final inclusive integer height', max_blocks: 'optional - full range budget 1..1000; default 100; larger ranges require scan_transactions', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' ) # Scan address scripts in explicit blocks; no arbitrary address index, balance or wallet history is implied. #{self}.scan_address_activity( address: 'required - Bitcoin address validated for the configured chain', start_height: 'required - first nonnegative integer height; resume with next_height', end_height: 'required - final inclusive integer height; keep fixed across pages', max_blocks: 'optional - examined blocks per page 1..1000; default 100', max_prevouts: 'optional - total parent RPC lookups per page 0..1000; default 100', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' ) Scans return range-scoped completeness, missing evidence and chain anchors. Recheck anchor hashes across pages; no multi-call atomic snapshot is promised. Historical transaction lookups may require txindex; pruned data stays unavailable. Configuration: PWN::Env[:plugins][:blockchain][:bitcoin] with rpc_host, rpc_port, rpc_user, rpc_pass and optional rpc_scheme http or https. HTTPS verifies certificates. HTTP is plaintext; use only a trusted local channel. No prompts, AI, wallet mutation, broadcasting, or implicit full-chain scans. # Return author information. #{self}.authors " end |
.inspect_outpoint(opts = {}) ⇒ Object
- Supported Method Parameters
Inspect an outpoint in the current UTXO view; null means spent or unknown, not proven spent. PWN::Blockchain::BTC.inspect_outpoint( txid: 'required - 64-character transaction hash', vout: 'required - nonnegative integer output index', include_mempool: 'optional - boolean include mempool effects; default true', timeout: 'optional - RPC timeout seconds, 1..300; default 30' )
212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/pwn/blockchain/btc.rb', line 212 public_class_method def self.inspect_outpoint(opts = {}) txid = hash_option(value: opts[:txid], name: 'txid') vout = integer_option(value: opts[:vout], name: 'vout', range: 0..4_294_967_295) include_mempool = opts.fetch(:include_mempool, true) raise ArgumentError, 'include_mempool must be boolean' unless [true, false].include?(include_mempool) result = btc_rpc_call(method: 'gettxout', params: [txid, vout, include_mempool], timeout: opts.fetch(:timeout, 30))[:result] base = { txid: txid, vout: vout, include_mempool: include_mempool, unspent: !result.nil? } return base.merge(status: :spent_or_unknown) if result.nil? base.merge(status: :unspent, value_sats: satoshis(value: result[:value]), evidence: result) end |
.inspect_transaction(opts = {}) ⇒ Object
- Supported Method Parameters
Inspect transaction amounts and proven input fees; unavailable prevouts remain explicit. PWN::Blockchain::BTC.inspect_transaction( txid: 'required - 64-character transaction hash', blockhash: 'optional - containing block hash for nodes without txindex', max_prevouts: 'optional - maximum parent RPC lookups, 0..1000; default 100', timeout: 'optional - RPC timeout seconds, 1..300; default 30' )
194 195 196 197 198 199 200 201 202 |
# File 'lib/pwn/blockchain/btc.rb', line 194 public_class_method def self.inspect_transaction(opts = {}) txid = hash_option(value: opts[:txid], name: 'txid') blockhash = opts[:blockhash] hash_option(value: blockhash, name: 'blockhash') unless blockhash.nil? budget = { remaining: integer_option(value: opts.fetch(:max_prevouts, 100), name: 'max_prevouts', range: 0..1000) } timeout = opts.fetch(:timeout, 30) tx = raw_transaction(txid: txid, blockhash: blockhash, timeout: timeout) transaction_details(transaction: tx, budget: budget, cache: {}, timeout: timeout) end |
.mempool_summary(opts = {}) ⇒ Object
- Supported Method Parameters
Read aggregate mempool statistics without enumerating transactions. PWN::Blockchain::BTC.mempool_summary(timeout: 'optional - RPC timeout seconds, 1..300; default 30')
228 229 230 231 |
# File 'lib/pwn/blockchain/btc.rb', line 228 public_class_method def self.mempool_summary(opts = {}) timeout = opts.key?(:timeout) ? opts[:timeout] : 30 btc_rpc_call(method: 'getmempoolinfo', timeout: timeout)[:result] end |
.scan_address_activity(opts = {}) ⇒ Object
- Supported Method Parameters
Scan address scripts in explicit blocks, not an arbitrary address index or wallet history. PWN::Blockchain::BTC.scan_address_activity( address: 'required - Bitcoin address validated by the configured node', start_height: 'required - first nonnegative integer height; use next_height for continuation', end_height: 'required - final inclusive integer height; keep fixed across pages', max_blocks: 'optional - blocks examined per page, 1..1000; default 100', max_prevouts: 'optional - total parent RPC lookups per page, 0..1000; default 100', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' )
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/pwn/blockchain/btc.rb', line 444 public_class_method def self.scan_address_activity(opts = {}) address = opts[:address] raise ArgumentError, 'address must be an alphanumeric Bitcoin address' unless address.is_a?(String) && address.match?(/\A[a-zA-Z0-9]{14,90}\z/) bounds = scan_bounds(opts) budget = { remaining: integer_option(value: opts.fetch(:max_prevouts, 100), name: 'max_prevouts', range: 0..1000) } timeout = opts.fetch(:timeout, 30) validation = btc_rpc_call(method: 'validateaddress', params: [address], timeout: timeout)[:result] raise ArgumentError, 'address is invalid for the configured chain' unless validation[:isvalid] == true && validation[:scriptPubKey].is_a?(String) state = { activity: [], missing: [], cache: {}, budget: budget, script: validation[:scriptPubKey], timeout: timeout } on_block = ->(block) { address_block(block: block, state: state) } result = scan_page(bounds: bounds, verbosity: 3, timeout: timeout, on_block: on_block) result.merge( address: address, address_index: false, activity: state[:activity], missing_prevouts: state[:missing], complete: result[:complete] && state[:missing].empty? ) end |
.scan_transactions(opts = {}) ⇒ Object
- Supported Method Parameters
Scan a bounded page by UTC block-header dates without assuming monotonic timestamps. PWN::Blockchain::BTC.scan_transactions( from: 'required - inclusive UTC date YYYY-MM-DD', to: 'required - inclusive UTC date YYYY-MM-DD', start_height: 'required - first nonnegative integer height; use next_height for continuation', end_height: 'required - final inclusive integer height; keep fixed across pages', max_blocks: 'optional - blocks examined per page, 1..1000; default 100', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' )
399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/pwn/blockchain/btc.rb', line 399 public_class_method def self.scan_transactions(opts = {}) from = (value: opts[:from]) to = (value: opts[:to]) raise ArgumentError, 'from must not follow to' if from > to bounds = scan_bounds(opts) transactions = [] on_block = lambda do |block| next unless block[:time] >= from && block[:time] < to + 86_400 block[:tx].each { |txid| transactions << { txid: txid, height: block[:height], blockhash: block[:hash], time: block[:time] } } end scan_page(bounds: bounds, verbosity: 1, timeout: opts.fetch(:timeout, 30), on_block: on_block).merge(transactions: transactions) end |
.trace_transaction(opts = {}) ⇒ Object
- Supported Method Parameters
Trace only transaction ancestors using input references, never ownership or change heuristics. PWN::Blockchain::BTC.trace_transaction( txid: 'required - root transaction hash, 64 hexadecimal characters', blockhash: 'optional - containing block hash for root lookup without txindex', max_depth: 'optional - ancestor depth 0..100; default 3', max_transactions: 'optional - total transaction RPC budget 1..1000; default 100', max_edges: 'optional - maximum evidence edges 1..10000; default 1000', timeout: 'optional - per-RPC timeout seconds, 1..300; default 30' )
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/pwn/blockchain/btc.rb', line 243 public_class_method def self.trace_transaction(opts = {}) txid = hash_option(value: opts[:txid], name: 'txid') blockhash = opts[:blockhash] hash_option(value: blockhash, name: 'blockhash') unless blockhash.nil? limits = { depth: integer_option(value: opts.fetch(:max_depth, 3), name: 'max_depth', range: 0..100), transactions: integer_option(value: opts.fetch(:max_transactions, 100), name: 'max_transactions', range: 1..1000), edges: integer_option(value: opts.fetch(:max_edges, 1000), name: 'max_edges', range: 1..10_000) } timeout = opts.fetch(:timeout, 30) state = { queue: [[txid, 0]], scheduled: { txid.downcase => true }, cache: {}, edges: [], reasons: [], missing: [] } until state[:queue].empty? current, depth = state[:queue].shift begin tx = raw_transaction(txid: current, blockhash: current == txid ? blockhash : nil, timeout: timeout) rescue RPCError => e raise unless current != txid && [-5, -8, -1].include?(e.code) && [200, 500].include?(e.http_status) state[:cache][current.downcase] = nil state[:missing] << { txid: current, code: e.code } next end state[:cache][current.downcase] = tx trace_inputs(transaction: tx, depth: depth, state: state, limits: limits) end finish_trace(state: state, txid: txid) end |