From 532419aea3d2877fdbbe7fc90ba8c99a2d0b885c Mon Sep 17 00:00:00 2001 From: Rucknium Date: Sun, 14 Apr 2024 21:30:16 +0000 Subject: [PATCH] Add package skeleton with first function: ping.peers() --- .Rbuildignore | 4 ++ DESCRIPTION | 28 +++++++++ NAMESPACE | 1 + R/ping.R | 141 ++++++++++++++++++++++++++++++++++++++++++++++ man/ping.peers.Rd | 31 ++++++++++ 5 files changed, 205 insertions(+) create mode 100644 .Rbuildignore create mode 100644 DESCRIPTION create mode 100644 NAMESPACE create mode 100644 R/ping.R create mode 100644 man/ping.peers.Rd diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..dd0c0e4 --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,4 @@ +^.*\.Rproj$ +^\.Rproj\.user$ +^\.github$ +README.md diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..34a93ef --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,28 @@ +Package: xmrpeers +Type: Package +Title: Analysis of the Monero Cryptocurrency Peer-to-Peer Node Network +Version: 0.0.1.9000 +Date: 2024-04-14 +Authors@R: + c(person(given = "Rucknium", + role = c("cre", "aut", "cph"), + email = "Rucknium@protonmail.com", + comment = c(ORCID = "0000-0001-5999-8950")) + ) +Description: More about what it does (maybe more than one line) + Use four spaces when indenting paragraphs within the Description. +License: GPL (>= 2) +Encoding: UTF-8 +LazyData: true +Imports: + readr, + stringr, + pingr, + parallelly, + future, + future.apply +Suggests: + testthat (>= 3.0.0), + withr +Config/testthat/edition: 3 +RoxygenNote: 7.3.1 diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..d75f824 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1 @@ +exportPattern("^[[:alpha:]]+") diff --git a/R/ping.R b/R/ping.R new file mode 100644 index 0000000..08beca7 --- /dev/null +++ b/R/ping.R @@ -0,0 +1,141 @@ + + + +#' Ping peer nodes for latency measurement +#' +#' @param bitmonero.dir .bitmonero directory where the monero.log file is. +#' @param output.file Name of the output file. The file will be created in `bitmonero.dir`. +#' @param sleep Number of seconds to sleep between each round of collecting new peer IPs. +#' @param ping.count Number of times to ping each peer. +#' +#' @return No return value. Executes in a loop until interrupted. +#' @export +#' +#' @examples +#' ping.peers() +ping.peers <- function(bitmonero.dir = "~/.bitmonero", output.file = "/monero_peer_pings.csv", sleep = 10, ping.count = 5) { + + bitmonero.dir <- path.expand(bitmonero.dir) + bitmonero.dir <- gsub("/+$", "", bitmonero.dir) + # Remove trailing slash(es) if they exist + + output.file <- paste0(bitmonero.dir, output.file) + + log.file <- paste0(bitmonero.dir, "/bitmonero.log") + + first.file.line <- readr::read_lines(log.file, n_max = 1) + # Get the first file line so we know when the log file rolls over + # to the next file. + # Better to do this than file.info() because file.info() is OS-dependent: + # > What is meant by the three file times depends on the OS and file system. + # > On Windows native file systems ctime is the file creation time + # > (something which is not recorded on most Unix-alike file systems). + # > What is meant by ‘file access’ and hence the ‘last access time’ is system-dependent. + + + + lines.already.read <- 0 + + while (TRUE) { + + check.first.file.line <- readr::read_lines(log.file, n_max = 1) + + if (first.file.line != check.first.file.line) { + first.file.line <- check.first.file.line + lines.already.read <- 0 + } + + if (file.exists(output.file)) { + old.ping.data <- read.csv(output.file, header = FALSE) + old.ip.ports <- paste0(old.ping.data[, 1], ":", old.ping.data[, 2]) + } else { + old.ip.ports <- "" + } + + + tail.file <- readr::read_lines(log.file, skip = lines.already.read) + + ip.lines <- grep("Received NOTIFY_NEW_TRANSACTIONS", tail.file, fixed = TRUE) + + if (length(ip.lines) == 0) { + Sys.sleep(sleep.time) + cat(base::date(), " Peers pinged: 0\n", sep = "") + next + } + + get.peer.ip.port.direction <- function(x) { + x <- stringr::str_extract(x, "\\[[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}:[0-9]+\\s[INCOUT]{3}\\]") + x <- stringr::str_extract(x, "[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}:[0-9]+\\s[INCOUT]{3}") + x <- strsplit(x, "(:)|(\\s)") + x <- as.data.frame(matrix(unlist(x), ncol = 3, byrow = TRUE), stringsAsFactors = FALSE) + colnames(x) <- c("ip", "port", "direction") + x + } + + + peers <- get.peer.ip.port.direction(tail.file[ip.lines]) + peers <- unique(peers) + peers <- peers[! paste0(peers$ip, ":", peers$port) %in% old.ip.ports, , drop = FALSE] + + if (nrow(peers) == 0) { + Sys.sleep(sleep.time) + cat(base::date(), " Peers pinged: 0\n", sep = "") + next + } + + get.ping.data <- function(x) { + ip <- x[[1]] + port <- x[[2]] + direction <- x[[3]] + if (paste0(ip, ":", port) %in% old.ip.ports) { + return("") + } + pings <- pingr::ping_port(ip, port = port, count = ping.count) + if (all(is.na(pings))) { + # This may happen if it is an incoming connection and the peer's port is closed + pings <- pingr::ping(ip, count = ping.count) + } + paste(ip, port, direction, paste(pings, collapse = ","), sep = ",") + } + + + if (nrow(peers) * ping.count > 5) { + + n.workers <- min(c(floor(nrow(peers) * ping.count / 5), parallelly::availableCores()*4)) + options(parallelly.maxWorkers.localhost = 4) # This means number of CPU cores times 4 + # Most time in thread is waiting for ping to return, so can have + # high number of workers + + future::plan(future::multisession(workers = n.workers)) + + ping.data <- future.apply::future_apply(peers, MARGIN = 1, get.ping.data, future.seed = TRUE) + + future::plan(future::sequential) + # Shut down workers + + } else { + ping.data <- apply(peers, MARGIN = 1, get.ping.data) + } + + ping.data <- unname(ping.data) + + ping.data <- ping.data[ping.data != ""] + + if (length(ping.data) > 0) { + cat(ping.data, file = output.file, sep = "\n", append = TRUE) + } + + lines.already.read <- n.lines.file + + Sys.sleep(sleep.time) + + cat(base::date(), " Peers pinged: ", length(ping.data), "\n", sep = "") + + } + + +} + + + + diff --git a/man/ping.peers.Rd b/man/ping.peers.Rd new file mode 100644 index 0000000..bf48689 --- /dev/null +++ b/man/ping.peers.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ping.R +\name{ping.peers} +\alias{ping.peers} +\title{Ping peer nodes for latency measurement} +\usage{ +ping.peers( + bitmonero.dir = "~/.bitmonero", + output.file = "/monero_peer_pings.csv", + sleep = 10, + ping.count = 5 +) +} +\arguments{ +\item{bitmonero.dir}{.bitmonero directory where the monero.log file is.} + +\item{output.file}{Name of the output file. The file will be created in `bitmonero.dir`.} + +\item{sleep}{Number of seconds to sleep between each round of collecting new peer IPs.} + +\item{ping.count}{Number of times to ping each peer.} +} +\value{ +No return value. Executes in a loop until interrupted. +} +\description{ +Ping peer nodes for latency measurement +} +\examples{ +ping.peers() +}