2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
library(data.table)
|
|
|
|
library(RSQLite)
|
|
|
|
library(DBI)
|
|
|
|
# NOTE: Also need lubridate package installed, but not loading it due to
|
|
|
|
# it masking functions
|
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
# WARNING: This code assumes that pre-fork bitcoin has been spent every day since the fork,
|
|
|
|
# which is true up to March 31, 2022. If this code runs on later data, then will
|
|
|
|
# have to pre-fill data.frames with dates
|
|
|
|
|
2022-04-09 14:38:00 +00:00
|
|
|
bch.data.dir <- ""
|
|
|
|
btc.data.dir <- ""
|
|
|
|
# Input data directory here, with trailing "/"
|
|
|
|
|
|
|
|
|
|
|
|
con.bch <- DBI::dbConnect(RSQLite::SQLite(), paste0(bch.data.dir, "tx-graph-node-indices.db"))
|
|
|
|
con.btc <- DBI::dbConnect(RSQLite::SQLite(), paste0(btc.data.dir, "tx-graph-node-indices.db"))
|
|
|
|
|
|
|
|
pre.fork.edgelist <- DBI::dbGetQuery(con.bch,
|
|
|
|
"SELECT origin_index, destination_index FROM edgelist_intermediate_2 WHERE block_height <= 478558")
|
|
|
|
# 478558 is last block height that BCH and BTC share a block
|
|
|
|
|
|
|
|
pre.fork.utxo.set <- setdiff(pre.fork.edgelist$destination_index, pre.fork.edgelist$origin_index)
|
|
|
|
|
|
|
|
DBI::dbWriteTable(con.bch, "pre_fork_utxo_set",
|
|
|
|
data.frame(destination_index = pre.fork.utxo.set, stringsAsFactors = FALSE))
|
2022-04-29 20:44:29 +00:00
|
|
|
DBI::dbWriteTable(con.btc, "pre_fork_utxo_set",
|
|
|
|
data.frame(destination_index = pre.fork.utxo.set, stringsAsFactors = FALSE))
|
|
|
|
# Need to do this operation for both the BCH and BTC databases
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
pre.fork.utxo.set.value <- DBI::dbGetQuery(con.bch,
|
|
|
|
'SELECT destination_index, value FROM edgelist_intermediate_2 WHERE destination_index IN (SELECT destination_index FROM pre_fork_utxo_set)')
|
|
|
|
setDT(pre.fork.utxo.set.value)
|
|
|
|
pre.fork.bitcoin.supply <- 50 * length(0:209999) + 25 * length(210000:419999) + 12.5 * length(420000:478558)
|
|
|
|
pre.fork.utxo.set.value[, sum(value)] / pre.fork.bitcoin.supply
|
|
|
|
# [1] 0.99984
|
|
|
|
pre.fork.bitcoin.supply - pre.fork.utxo.set.value[, sum(value)]
|
|
|
|
# [1] 2637.559
|
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
duplicated.destination_index <-
|
|
|
|
unlist(pre.fork.utxo.set.value[duplicated(destination_index), .(destination_index)])
|
|
|
|
|
|
|
|
pre.fork.utxo.set.value <- pre.fork.utxo.set.value[ ! destination_index %in% duplicated.destination_index, ]
|
2022-04-09 14:38:00 +00:00
|
|
|
# Removes the transactions that are coinbases of blocks 91722, 91812, 91842, 91880
|
|
|
|
# Since they are duplicated transaction hashes. See:
|
|
|
|
# https://bitcoin.stackexchange.com/questions/40444/what-happens-when-two-txids-collide
|
|
|
|
# https://github.com/bitcoin/bitcoin/commit/ab91bf39b7c11e9c86bb2043c24f0f377f1cf514
|
|
|
|
|
|
|
|
excluded.duplicate.tx.hashes.output.count <- 4
|
|
|
|
excluded.duplicate.tx.hashes.value <- 50 * 4
|
|
|
|
|
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
bch.spent.status <- DBI::dbGetQuery(con.bch,
|
|
|
|
'SELECT origin_index, block_height FROM edgelist_intermediate_1 WHERE origin_index IN (SELECT destination_index FROM pre_fork_utxo_set)')
|
|
|
|
colnames(bch.spent.status) <- c("destination_index", "bch.spent.block_height")
|
|
|
|
setDT(bch.spent.status)
|
|
|
|
|
2022-04-09 14:38:00 +00:00
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
btc.spent.status <- DBI::dbGetQuery(con.btc,
|
|
|
|
'SELECT origin_index, block_height FROM edgelist_intermediate_1 WHERE origin_index IN (SELECT destination_index FROM pre_fork_utxo_set)')
|
|
|
|
colnames(btc.spent.status) <- c("destination_index", "btc.spent.block_height")
|
|
|
|
setDT(btc.spent.status)
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
spent.status <- merge(pre.fork.utxo.set.value, bch.spent.status, all.x = TRUE)
|
|
|
|
rm(pre.fork.utxo.set.value, bch.spent.status)
|
|
|
|
spent.status <- merge(spent.status, btc.spent.status, all.x = TRUE)
|
|
|
|
rm(btc.spent.status)
|
|
|
|
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
bch.block.times <- readRDS(paste0(bch.data.dir, "block_times.rds"))
|
|
|
|
|
|
|
|
bch.block.times[, block_time := as.POSIXct(block_time, origin = "1970-01-01", tz = "GMT")]
|
2022-04-29 20:44:29 +00:00
|
|
|
colnames(bch.block.times) <- c("bch.spent.block_height", "bch.block_time")
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
spent.status <- merge(spent.status, bch.block.times, all = TRUE, by = "bch.spent.block_height")
|
|
|
|
# Note that due to all = TRUE this will get all blocks,
|
|
|
|
# even if there are no target spent outputs within the block
|
|
|
|
|
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
btc.block.times <- readRDS(paste0(btc.data.dir, "block_times.rds"))
|
2022-04-09 14:38:00 +00:00
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
btc.block.times[, block_time := as.POSIXct(block_time, origin = "1970-01-01", tz = "GMT")]
|
|
|
|
colnames(btc.block.times) <- c("btc.spent.block_height", "btc.block_time")
|
2022-04-09 14:38:00 +00:00
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
spent.status <- merge(spent.status, btc.block.times, all = TRUE, by = "btc.spent.block_height")
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
|
2022-05-20 15:20:30 +00:00
|
|
|
write.csv(spent.status, file = paste0(bch.data.dir, "pre-fork-BTC-BCH-spent_status.csv"), row.names = FALSE)
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
spent.status[, bch.block_time.date := lubridate::date(bch.block_time)]
|
|
|
|
spent.status[, btc.block_time.date := lubridate::date(btc.block_time)]
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-04-29 20:44:29 +00:00
|
|
|
# Column format below is:
|
|
|
|
# {BTC spent status}{BCH spent status}.to.{BTC spent status}{BCH spent status}
|
|
|
|
# u = unspent; s = spent
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
spent.status[, ff.to.tf := as.Date(ifelse(
|
2022-04-29 20:44:29 +00:00
|
|
|
ifelse(is.na(btc.block_time.date), Inf, btc.block_time.date) < ifelse(is.na(bch.block_time.date), Inf, bch.block_time.date),
|
|
|
|
btc.block_time.date, rep(NA, .N)), origin = "1970-01-01")]
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
spent.status[, ff.to.ft := as.Date(ifelse(
|
2022-04-29 20:44:29 +00:00
|
|
|
ifelse(is.na(bch.block_time.date), Inf, bch.block_time.date) < ifelse(is.na(btc.block_time.date), Inf, btc.block_time.date),
|
|
|
|
bch.block_time.date, rep(NA, .N)), origin = "1970-01-01")]
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
spent.status[, ff.to.tt := as.Date(ifelse(
|
2022-04-29 20:44:29 +00:00
|
|
|
ifelse(is.na(btc.block_time.date), Inf, btc.block_time.date) == ifelse(is.na(bch.block_time.date), Inf, bch.block_time.date),
|
|
|
|
btc.block_time.date, rep(NA, .N)), origin = "1970-01-01")]
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
spent.status[, tf.to.tt := as.Date(ifelse(
|
|
|
|
(! is.na(ff.to.tf)) &
|
2022-04-29 20:44:29 +00:00
|
|
|
ifelse(is.na(bch.block_time.date), Inf, bch.block_time.date) > ifelse(is.na(btc.block_time.date), Inf, btc.block_time.date),
|
|
|
|
bch.block_time.date, rep(NA, .N)), origin = "1970-01-01")]
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
spent.status[, ft.to.tt := as.Date(ifelse(
|
|
|
|
(! is.na(ff.to.ft)) &
|
2022-04-29 20:44:29 +00:00
|
|
|
ifelse(is.na(btc.block_time.date), Inf, btc.block_time.date) > ifelse(is.na(bch.block_time.date), Inf, bch.block_time.date),
|
|
|
|
btc.block_time.date, rep(NA, .N)), origin = "1970-01-01")]
|
|
|
|
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
ff.to.tf <- spent.status[ (! is.na(ff.to.tf)),
|
|
|
|
.(value.ff.to.tf = sum(value, na.rm = TRUE), outputs.ff.to.tf = .N), by = ff.to.tf]
|
|
|
|
names(ff.to.tf)[1] <- "block_time.date"
|
2022-04-29 20:44:29 +00:00
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
ff.to.ft <- spent.status[ (! is.na(ff.to.ft)),
|
|
|
|
.(value.ff.to.ft = sum(value, na.rm = TRUE), outputs.ff.to.ft = .N), by = ff.to.ft]
|
|
|
|
names(ff.to.ft)[1] <- "block_time.date"
|
2022-04-29 20:44:29 +00:00
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
ff.to.tt <- spent.status[ (! is.na(ff.to.tt)),
|
|
|
|
.(value.ff.to.tt = sum(value, na.rm = TRUE), outputs.ff.to.tt = .N), by = ff.to.tt]
|
|
|
|
names(ff.to.tt)[1] <- "block_time.date"
|
2022-04-29 20:44:29 +00:00
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
tf.to.tt <- spent.status[ (! is.na(tf.to.tt)),
|
|
|
|
.(value.tf.to.tt = sum(value, na.rm = TRUE), outputs.tf.to.tt = .N), by = tf.to.tt]
|
|
|
|
names(tf.to.tt)[1] <- "block_time.date"
|
2022-04-29 20:44:29 +00:00
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
ft.to.tt <- spent.status[ (! is.na(ft.to.tt)),
|
|
|
|
.(value.ft.to.tt = sum(value, na.rm = TRUE), outputs.ft.to.tt = .N), by = ft.to.tt]
|
|
|
|
names(ft.to.tt)[1] <- "block_time.date"
|
2022-04-29 20:44:29 +00:00
|
|
|
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
state.trans.by.day <-
|
2022-04-29 20:44:29 +00:00
|
|
|
data.table(block_time.date = sort(unique(lubridate::date(c(spent.status$bch.block_time, spent.status$btc.block_time)))))
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
state.trans.by.day <- merge(state.trans.by.day, ff.to.tf, all = TRUE)
|
|
|
|
state.trans.by.day <- merge(state.trans.by.day, ff.to.ft, all = TRUE)
|
|
|
|
state.trans.by.day <- merge(state.trans.by.day, ff.to.tt, all = TRUE)
|
|
|
|
state.trans.by.day <- merge(state.trans.by.day, tf.to.tt, all = TRUE)
|
|
|
|
state.trans.by.day <- merge(state.trans.by.day, ft.to.tt, all = TRUE)
|
2022-04-29 20:44:29 +00:00
|
|
|
|
|
|
|
|
2022-05-12 02:10:07 +00:00
|
|
|
state.trans.by.day[is.na(state.trans.by.day)] <- 0
|
2022-04-29 20:44:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
spent.status.by.day <-
|
|
|
|
data.table(block_time.date = sort(unique(lubridate::date(c(spent.status$bch.block_time, spent.status$btc.block_time)))),
|
|
|
|
value.btc.unspent.bch.unspent = NA_real_,
|
|
|
|
outputs.btc.unspent.bch.unspent = NA_integer_,
|
|
|
|
value.btc.spent.bch.unspent = NA_real_,
|
|
|
|
outputs.btc.spent.bch.unspent = NA_integer_,
|
|
|
|
value.btc.unspent.bch.spent = NA_real_,
|
|
|
|
outputs.btc.unspent.bch.spent = NA_integer_,
|
|
|
|
value.btc.spent.bch.spent = NA_real_,
|
|
|
|
outputs.btc.spent.bch.spent = NA_integer_)
|
|
|
|
|
|
|
|
for (day.i in spent.status.by.day$block_time.date) {
|
|
|
|
|
|
|
|
spent.status[, btc.spent := (btc.block_time.date <= day.i) & (! is.na(btc.block_time.date))]
|
|
|
|
spent.status[, bch.spent := (bch.block_time.date <= day.i) & (! is.na(bch.block_time.date))]
|
|
|
|
|
|
|
|
spent.status.by.day$value.btc.unspent.bch.unspent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ (! btc.spent) & (! bch.spent), sum(value, na.rm = TRUE)]
|
|
|
|
|
|
|
|
spent.status.by.day$outputs.btc.unspent.bch.unspent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ (! btc.spent) & (! bch.spent), .N]
|
|
|
|
|
|
|
|
|
|
|
|
spent.status.by.day$value.btc.spent.bch.unspent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ ( btc.spent) & (! bch.spent), sum(value, na.rm = TRUE)]
|
|
|
|
|
|
|
|
spent.status.by.day$outputs.btc.spent.bch.unspent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ ( btc.spent) & (! bch.spent), .N]
|
|
|
|
|
|
|
|
|
|
|
|
spent.status.by.day$value.btc.unspent.bch.spent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ (! btc.spent) & ( bch.spent), sum(value, na.rm = TRUE)]
|
|
|
|
|
|
|
|
spent.status.by.day$outputs.btc.unspent.bch.spent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ (! btc.spent) & ( bch.spent), .N]
|
|
|
|
|
|
|
|
|
|
|
|
spent.status.by.day$value.btc.spent.bch.spent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ ( btc.spent) & ( bch.spent), sum(value, na.rm = TRUE)]
|
|
|
|
|
|
|
|
spent.status.by.day$outputs.btc.spent.bch.spent[spent.status.by.day$block_time.date == day.i] <-
|
|
|
|
spent.status[ ( btc.spent) & ( bch.spent), .N]
|
|
|
|
|
|
|
|
cat(base::date(), which(spent.status.by.day$block_time.date == day.i),
|
|
|
|
"of", nrow(spent.status.by.day),"\n")
|
|
|
|
|
|
|
|
}
|
|
|
|
# Fairly inefficient implementation, but gets the job done
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Data validity check below
|
|
|
|
|
|
|
|
value.row.sum.check <- rowSums(spent.status.by.day[, .(
|
|
|
|
value.btc.unspent.bch.unspent, value.btc.spent.bch.unspent,
|
|
|
|
value.btc.unspent.bch.spent, value.btc.spent.bch.spent)
|
|
|
|
])
|
|
|
|
|
|
|
|
stopifnot(max(value.row.sum.check) - min(value.row.sum.check) < 0.000001)
|
|
|
|
# Some small error allowed for floating point arithmetic inaccuracy
|
|
|
|
|
|
|
|
outputs.row.sum.check <- rowSums(spent.status.by.day[, .(
|
|
|
|
outputs.btc.unspent.bch.unspent, outputs.btc.spent.bch.unspent,
|
|
|
|
outputs.btc.unspent.bch.spent, outputs.btc.spent.bch.spent)
|
|
|
|
])
|
|
|
|
|
|
|
|
stopifnot(max(outputs.row.sum.check) - min(outputs.row.sum.check) == 0)
|
|
|
|
|
|
|
|
|
|
|
|
saveRDS(spent.status.by.day, file = paste0(bch.data.dir, "spent_status_by_day.rds"))
|
2022-05-12 02:10:07 +00:00
|
|
|
saveRDS(state.trans.by.day, file = paste0(bch.data.dir, "state_trans_by_day.rds"))
|
|
|
|
|
|
|
|
write.csv(spent.status.by.day, file = paste0(bch.data.dir, "spent_status_by_day.csv"), row.names = FALSE)
|
|
|
|
write.csv(state.trans.by.day, file = paste0(bch.data.dir, "state_trans_by_day.csv"), row.names = FALSE)
|
2022-04-09 14:38:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|