R/bitcoind.R

#' @name rbitcoind
#' @title R interface to bitcoin daemon
#' @docType package
#' @author Jan Gorecki
#' @description You need to have to bitcoin node installed, blockchain is not necessary. Unit tests runs on \emph{regtest} network which uses private blockchain and doesn't need to be synchronized.
#' @seealso \link{bitcoind}, \link{bitcoind.rpc}
#' @references \url{https://bitcoin.org/en/glossary/regression-test-mode}
#' @aliases rbtcd
#' @examples \dontrun{
#' # examples assumes your daemon is already running on regtest
#' 
#' # low level calls to json-rpc
#' bitcoind.rpc(user = "username",
#'              password = "password",
#'              port = "18332",
#'              method = "getinfo")
#' 
#' # bitcoin daemon object
#' btcd = bitcoind$new(rpcuser = "username", 
#'                     rpcpassword = "password", 
#'                     regtest = TRUE)
#' btcd$getinfo()
#' print(btcd)
#' }
NULL

#' @title bitcoind class
#' @docType class
#' @format An R6 class object.
#' @name bitcoind
#' @details Initiate object to manage all RPC methods.
#' @aliases btcd
#' @seealso \link{bitcoind.rpc}
bitcoind <- R6Class(
    classname = "bitcoind",
    public = list(
        rpcconnect = character(),
        port = character(),
        rpcuser = character(),
        rpcport = character(),
        datadir = character(),
        connect = character(),
        addnode = character(),
        listen = logical(),
        testnet = logical(),
        regtest = logical(),
        network = character(),
        pid = character(), # file name
        initialize = function(rpcconnect="127.0.0.1", port, rpcuser, rpcpassword, rpcport, datadir="~/.bitcoin", listen, connect=NULL, addnode=NULL, testnet=FALSE, regtest=FALSE, pid="bitcoind.pid"){
            stopifnot(is.character(rpcuser), is.character(rpcpassword))
            private$rpcpassword = rpcpassword; rm(rpcpassword)
            self$rpcuser = rpcuser
            self$rpcconnect = rpcconnect
            self$testnet = testnet
            self$regtest = regtest
            net = c("test"[self$testnet], "regtest"[self$regtest])
            self$network = if(length(net)==0L) "main" else if(length(net)>1L) stop("Cannot use both testnet and regtest modes for single daemon instance at the same time.") else net
            self$port = if(missing(port)) defaultports()[self$network,"connect"] else port
            self$rpcport = if(missing(rpcport)) defaultports()[self$network,"rpc"] else rpcport
            self$connect = connect
            self$addnode = addnode
            self$datadir = datadir
            self$pid = pid
            self$listen = if(missing(listen)) as.integer(!length(self$connect)) else listen
            if(self$is.running()){
                if(!identical(self$network, self$get_network())) stop(paste0("Target daemon is already running on ",self$get_network()," network. Adjust daemon's bitcoin.conf or args to bitcoind$new: ", 
                                                                             switch(self$get_network(),
                                                                                    "main" = "do not use `testnet` or `regtest` TRUE",
                                                                                    "test" = "`testnet=TRUE`",
                                                                                    "regtest" = "`regtest=TRUE`"),
                                                                             "."),
                                                                      call. = FALSE)
            }
            invisible(self)
        },
        is.localhost = function() self$rpcconnect %in% c("127.0.0.1","localhost"),
        is.running =  function() all(c("result","error","id") %in% names(tryCatch(bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getinfo"), error = function(e) NULL))),
        get_network = function() self$getblockchaininfo()$chain,
        get_subdir = function() switch(self$network, "main"=NULL, "test"=getOption("testnet.subdir","testnet3"), "regtest"="regtest"),
        status = function(getinfo = FALSE){
            df = data.frame(rpcconnect = self$rpcconnect, rpcport = self$rpcport, network = self$network, datadir = self$datadir, pid = self$read_pid())
            if(getinfo){
                info = if(self$is.running()) self$getinfo() else info = list(blocks = NA_integer_, balance = NA_real_, connections = NA_integer_)
                df = cbind(df, data.frame(height = info$blocks, balance = info$balance, connections = info$connections))
            }
            use.data.table(df)
        },
        print = function(getinfo = FALSE){
            cat("<bitcoind>\n")
            cat("  instance: ", self$rpcconnect, ":", self$rpcport, "\n", sep="")
            cat("  network: ", self$network, "\n", sep="")
            cat("  datadir: ", self$datadir,"\n", sep="")
            cat("  pid: ", self$read_pid(), "\n", sep="")
            if(getinfo){
                info = self$getinfo()
                cat("  height: ", info$blocks, "\n", sep="")
                cat("  balance: ", info$balance, "\n", sep="")
                cat("  connections: ", info$connections, "\n", sep="")
            }
        },
        # localhost only
        read_pid = function() if(self$is.localhost() && file.exists(pid_path <- paste(c(self$datadir, self$get_subdir(), self$pid), collapse="/"))) readLines(pid_path,warn=FALSE) else NA_character_,
        # localhost only system calls
        pgrep = function(){
            cmd = "pgrep bitcoind"
            if(!self$is.localhost()) stop(run_localhost_msg("pgrep",cmd), call. = FALSE)
            message(cmd)
            system(cmd, intern = TRUE)
        },
        run = function(wait = 3){
            if(!(was.running <- self$is.running())){
                if(self$is.localhost()){
                    dir = path.expand(self$datadir)
                    if(!dir.exists(dir)) dir.create(dir, recursive = TRUE)
                    message(private$run_cmd(mask=TRUE))
                    system(private$run_cmd(mask=FALSE))
                    Sys.sleep(wait)
                }
                else {
                    if(!self$is.localhost()) stop(run_localhost_msg("run",private$run_cmd(mask=TRUE), call. = FALSE))
                }
            }
            if(self$is.running() && !identical(self$network, self$get_network()))
                stop(paste0("Target daemon is ","already "[was.running],"running on ",self$get_network()," network. Adjust daemon's bitcoin.conf or args to bitcoind$new: ", 
                            switch(self$get_network(),
                                   "main" = "do not use TRUE for `testnet` or `regtest`",
                                   "test" = "`testnet=TRUE`",
                                   "regtest" = "`regtest=TRUE`"),
                            "."),
                     call. = FALSE)
            invisible(self)
        },
        term = function(){
            cmd = "pkill --signal SIGTERM bitcoind"
            if(!self$is.localhost()) stop(run_localhost_msg("term",cmd), call. = FALSE)
            message(cmd)
            system(cmd)
        },
        kill = function(){
            cmd = "pkill --signal SIGKILL bitcoind"
            if(!self$is.localhost()) stop(run_localhost_msg("kill",cmd), call. = FALSE)
            message(cmd)
            system(cmd)
        },
        # json-rpc
        rpc = function(method, params) if(!missing(params)) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = method, params = params)$result else bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = method)$result,
        decoderawtransaction = function(hex) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "decoderawtransaction", params = list(hex))$result,
        decodescript = function(hex) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "decodescript", params = list(hex))$result,
        generate = function(numblocks) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "generate", params = list(numblocks))$result,
        getaccountaddress = function(account) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getaccountaddress", params = list(account))$result,
        getaccount = function(bitcoinaddress) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getaccount", params = list(bitcoinaddress))$result,
        getaddressesbyaccount = function(account) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getaddressesbyaccount", params = list(account))$result,
        getbalance = function(account) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getbalance", params = list(account))$result,
        getbestblockhash = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getbestblockhash")$result,
        getblock = function(hash, verbose = TRUE) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getblock", params = list(hash, verbose))$result,
        getblockchaininfo = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getblockchaininfo")$result,
        getblockcount = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getblockcount")$result,
        getblockhash = function(index) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getblockhash", params = list(index))$result,
        getchaintips = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getchaintips")$result,
        getconnectioncount = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getconnectioncount")$result,
        getinfo = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getinfo")$result,
        getmempoolinfo = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getmempoolinfo")$result,
        getnetworkinfo = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getnetworkinfo")$result,
        getnewaddress = function(account = "") bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getnewaddress", params = list(account))$result,
        getpeerinfo = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getpeerinfo")$result,
        getrawmempool = function(verbose = FALSE) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getrawmempool", params = list(verbose))$result,
        getrawtransaction = function(txid, verbose = 0) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getrawtransaction", params = list(txid, verbose))$result,
        getreceivedbyaccount = function(account, minconf = 1L) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getreceivedbyaccount", params = list(account, minconf))$result,
        getreceivedbyaddress = function(bitcoinaddress, minconf = 1L) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getreceivedbyaddress", params = list(bitcoinaddress, minconf))$result,
        gettransaction = function(txid, includeWatchonly = FALSE) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "gettransaction", params = list(txid, includeWatchonly))$result,
        getunconfirmedbalance = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getunconfirmedbalance")$result,
        getwalletinfo = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "getwalletinfo")$result,
        help = function(command) if(missing(command)) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "help")$result else bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "help", params = list(command))$result,
        listaccounts = function(minconf = 1L) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "listaccounts", params = list(minconf))$result,
        listaddressgroupings = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "listaddressgroupings")$result,
        listreceivedbyaccount = function(minconf = 1L, includeempty = FALSE) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "listreceivedbyaccount", params = list(minconf, includeempty))$result,
        listsinceblock = function(blockhash, target.confirmations = 1L, includeWatchonly = FALSE) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "listsinceblock", params = list(blockhash, target.confirmations, includeWatchonly))$result,
        listtransactions = function(account, count = 10L, from = 0L) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "listtransactions", params = list(account, count, from))$result,
        listunspent = function(miconf = 1L, maxconf = 9999999L, addresses) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "listunspent", params = list(miconf, maxconf, addresses))$result,
        sendtoaddress = function(bitcoinaddress, amount, comment = "", comment.to = "", subtractfeefromamount = FALSE) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "sendtoaddress", params = list(bitcoinaddress, amount, comment, comment.to, subtractfeefromamount))$result,
        stop = function() bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "stop")$result,
        validateaddress = function(bitcoinaddress) bitcoind.rpc(connect=self$rpcconnect, user=self$rpcuser, password=private$rpcpassword, port=self$rpcport, method = "validateaddress", params = list(bitcoinaddress))$result
    ),
    private = list(
        run_cmd = function(mask=TRUE){
            no_value_args = c("server", "daemon", "listen"[as.logical(self$listen)], "regtest"[as.logical(self$regtest)], "testnet"[as.logical(self$testnet)])
            value_args = c("port" = self$port,
                           "rpcuser" = self$rpcuser,
                           "rpcpassword" = if(mask) "***" else private$rpcpassword, 
                           "rpcport" = self$rpcport,
                           "datadir" = if(mask || !self$is.localhost()) self$datadir else path.expand(self$datadir),
                           "pid" = self$pid,
                           "connect" = if(length(self$connect)) self$connect,
                           "addnode" = if(length(self$addnode)) self$addnode)
            paste(
                "bitcoind",
                paste0("-",no_value_args,collapse=" "),
                paste0("-",names(value_args),"=",value_args,collapse=" ")
            )
        },
        rpcpassword = character()
    )
)
jangorecki/rbitcoind documentation built on May 18, 2019, 12:25 p.m.