#' Print likely newborn nodes to the console #' #' @description When a node connects to our own node, it could be a newborn #' node that is syncing from the genesis block. `newborn.nodes` queries the #' local Monero node about node connections with a `get_peers` call and prints #' information about likely newborn nodes to the console. The unrestricted RPC #' port must be reachable by R. This function is an infinite loop. `ctrl + c` #' to interrupt the function and print all IP addresses of likely newborn nodes #' recorded so far. Each peer will only be printed once during a specific run of #' `newborn.nodes`, even if the node disconnects and reconnects later. #' #' @param unrestricted.rpc.url URL and port of the `monerod` unrestricted RPC. #' Default is `http://127.0.0.1:18081` #' @param poll.time How often, in seconds, to check for a newborn node #' connection. Default is 30 seconds. #' @param sync.height.lag Criteria to consider a node as "newborn". Nodes that #' have a height greater than current network height minus `sync.height.lag` #' will not be considered a newborn node. Default is 3 months. #' @param avg_upload.limit Criteria to consider a node as "newborn". The #' `avg_upload` from the `get_peers` call must be greater than or equal to #' `avg_upload.limit` OR `current_upload` must be greater than or equal to #' `current_upload.limit` to consider the node as "newborn". Default is 10 for #' both limits. Sometimes a node gets stuck at a low height, so it isn't #' actually new or syncing. This criteria makes sure that the peer is actually #' actively syncing data. #' @param current_upload.limit Criteria to consider a node as "newborn". #' #' @return #' NULL (invisible) #' @export #' #' @examples #' \dontrun{ #' newborn.nodes() #' } newborn.nodes <- function(unrestricted.rpc.url = "http://127.0.0.1:18081", poll.time = 30, sync.height.lag = 30 * 24 * 90, avg_upload.limit = 10, current_upload.limit = 10) { json.post <- RJSONIO::toJSON( list( jsonrpc = "2.0", id = "0", method = "get_connections", params = "" ) ) already.seen.newborn.address <- c() already.seen.newborn.peer_id <- c() on.exit({ session.duration <- Sys.time() - session.start.time cat(paste0(format(Sys.time(), "%Y-%m-%d %T"), " ", length(already.seen.newborn.address), " likely newborn nodes seen during this session that lasted ", round(session.duration, 1), " ", units(session.duration), ": \n", paste0(already.seen.newborn.address, collapse = " "), "\n")) }) session.start.time <- Sys.time() cat(paste0(format(session.start.time, "%Y-%m-%d %T"), " Start polling monerod for newborn nodes...\n")) while (TRUE) { peers <- RJSONIO::fromJSON( RCurl::postForm(paste0(unrestricted.rpc.url, "/json_rpc"), .opts = list( userpwd = "", postfields = json.post, httpheader = c('Content-Type' = 'application/json', Accept = 'application/json') ) ), asText = TRUE ) if(length(peers$result$connections) == 0 ) { cat("Unexpected results from node's RPC response. Check that monerod's unrestricted RPC port is at ", unrestricted.rpc.url, "\n") Sys.sleep(poll.time) next } peers <- peers$result$connections peer.heights <- sapply(peers, FUN = function(x) {x$height}) peer.address <- sapply(peers, FUN = function(x) {x$address}) peer.peer_id <- sapply(peers, FUN = function(x) {x$peer_id}) peer.avg_upload <- sapply(peers, FUN = function(x) {x$avg_upload}) peer.current_upload <- sapply(peers, FUN = function(x) {x$current_upload}) consensus.height <- floor(quantile(peer.heights, prob = 0.9)) new.nodes <- which( peer.heights > 1 & (peer.heights <= consensus.height - sync.height.lag) & (! peer.address %in% already.seen.newborn.address) & (! peer.peer_id %in% already.seen.newborn.peer_id) & (peer.avg_upload > avg_upload.limit | peer.current_upload > current_upload.limit) ) for (i in new.nodes) { cat( paste0(format(Sys.time(), "%Y-%m-%d %T"), " ", "Likely newborn node: ", formatC(peers[[i]]$address, width = 21), ", ", # "Peer ID: ", peers[[i]]$peer_id, ", ", # "Connection ID: ", peers[[i]]$connection_id, ", ", "Height: ", formatC(peers[[i]]$height, width = 7, format = "d"), "/", consensus.height, ", ", ifelse(peers[[i]]$pruning_seed > 0, " Pruned", "Unpruned"), ", ", ifelse(peers[[i]]$incoming, "Incoming connection", "Outgoing connection"), ", ", "avg_upload: ", formatC(peers[[i]]$avg_upload, width = 4), # ", ", "current_upload: ", formatC(peers[[i]]$current_upload, width = 4), # ", ", "send_count: ", formatC(peers[[i]]$send_count, width = 8, format = "d"), "\n" ) ) already.seen.newborn.address <- c(already.seen.newborn.address, peers[[i]]$address) already.seen.newborn.peer_id <- c(already.seen.newborn.peer_id, peers[[i]]$peer_id) } Sys.sleep(poll.time) } return(invisible(NULL)) }