packages feed

haskoin-store-1.9.0: src/Haskoin/Store/Fleet/lua/read.lua

-- What a store reads from the shared mempool of its fleet: readScript in
-- Haskoin.Store.Fleet.Redis. pool.lua writes what this reads.
--
-- ARGV[1] is what to read, ARGV[2] the prefix of the keys, ARGV[3] the size
-- of a transaction id in bytes, and ARGV[4] the fleet tip that the store
-- answers from, or ''. Every answer is {'raced'} if the tip is another one:
-- the pool goes with the tip, and laid over the chain of another block it
-- would give a transaction twice or not at all. Otherwise it is {'ok', ...}.
--
-- 'sums': addresses. For each, its seven sums separated by spaces, in the
-- order of the summary, or '' if the pool has nothing for it.
--
-- 'txs': an address, the time and id of the transaction to start at or two
-- '', and how many. Transactions of the address from there, newest first:
-- an id and a time for each.
--
-- 'outs': an address, the time and output to start at or two '', and how
-- many. Outputs paid to the address from there, newest first: for each the
-- output, its time, its amount and script separated by a space, and '1' if
-- the pool spends it or '0'.
--
-- 'spent': an address and outputs. A string with '1' for each output of the
-- address that the pool spends and '0' for the others.
--
-- 'bodies': transactions, of the pool or not. For each: its body or '',
-- '1' if it can be replaced by fee or '0', the number of its outputs that
-- the pool spends, and for each of those the output, the transaction that
-- spends it and the number of its input that does.
--
-- 'spenders': outputs. For each, the transaction that spends it and the
-- number of its input that does, or two ''.
--
-- 'page': an offset and how many, 0 for all. Transactions of the pool from
-- the newest, an id and a time for each.
--
-- 'ids': some bytes. The transactions whose id starts with them.
local what, prefix, tsize = ARGV[1], ARGV[2], tonumber(ARGV[3])
local osize = tsize + 4
if (redis.call('GET', prefix .. 'fleet:tip') or '') ~= ARGV[4] then
  return {'raced'}
end
local answer = {'ok'}

local function split(s, n)
  local items = {}
  for i = 1, #s, n do
    items[#items + 1] = string.sub(s, i, i + n - 1)
  end
  return items
end

-- whether one string of bytes sorts before another, as Redis sorts the
-- members of a sorted set; the comparison of Lua goes by the locale
local function before(a, b)
  local n = math.min(#a, #b)
  for i = 1, n do
    local x, y = string.byte(a, i), string.byte(b, i)
    if x ~= y then
      return x < y
    end
  end
  return #a < #b
end

-- the position in a sorted set, ordered newest first, of the member with a
-- time and a name, or where it would be
local function position(zkey, time, member)
  if time == '' then
    return 0
  end
  local score = -tonumber(time)
  local at = redis.call('ZCOUNT', zkey, '-inf', '(' .. string.format('%.0f', score))
  for _, m in ipairs(redis.call('ZRANGEBYSCORE', zkey, score, score)) do
    if before(m, member) then
      at = at + 1
    end
  end
  return at
end

if what == 'sums' then
  for i = 5, #ARGV do
    local s = redis.call('HMGET', prefix .. 'pool:asum:' .. ARGV[i],
      'txs', 'paid', 'paidCount', 'spent', 'spentCount', 'both', 'bothCount')
    if s[1] then
      for j = 1, 7 do
        s[j] = s[j] or '0'
      end
      answer[#answer + 1] = table.concat(s, ' ')
    else
      answer[#answer + 1] = ''
    end
  end
elseif what == 'txs' then
  local zkey = prefix .. 'pool:atx:' .. ARGV[5]
  local at = position(zkey, ARGV[6], ARGV[7])
  local rows = redis.call('ZRANGE', zkey, at, at + tonumber(ARGV[8]) - 1, 'WITHSCORES')
  for i = 1, #rows, 2 do
    answer[#answer + 1] = rows[i]
    answer[#answer + 1] = string.format('%.0f', -tonumber(rows[i + 1]))
  end
elseif what == 'outs' then
  local a = ARGV[5]
  local zkey = prefix .. 'pool:aout:' .. a
  local at = position(zkey, ARGV[6], ARGV[7])
  local rows = redis.call('ZRANGE', zkey, at, at + tonumber(ARGV[8]) - 1, 'WITHSCORES')
  for i = 1, #rows, 2 do
    answer[#answer + 1] = rows[i]
    answer[#answer + 1] = string.format('%.0f', -tonumber(rows[i + 1]))
    answer[#answer + 1] = redis.call('HGET', prefix .. 'pool:aval:' .. a, rows[i])
    answer[#answer + 1] = tostring(redis.call('HEXISTS', prefix .. 'pool:aspent:' .. a, rows[i]))
  end
elseif what == 'spent' then
  local flags = {}
  for i, o in ipairs(split(ARGV[6], osize)) do
    flags[i] = tostring(redis.call('HEXISTS', prefix .. 'pool:aspent:' .. ARGV[5], o))
  end
  answer[2] = table.concat(flags)
elseif what == 'bodies' then
  for _, t in ipairs(split(ARGV[5], tsize)) do
    answer[#answer + 1] = redis.call('HGET', prefix .. 'pool:body', t) or ''
    answer[#answer + 1] = tostring(redis.call('SISMEMBER', prefix .. 'pool:rbf', t))
    -- what spends its outputs: the inputs of the transactions of the pool
    -- that spend from it
    local spent = {}
    for _, kid in ipairs(redis.call('SMEMBERS', prefix .. 'pool:kids:' .. t)) do
      for i, input in ipairs(split(redis.call('HGET', prefix .. 'pool:spends', kid), osize)) do
        if string.sub(input, 1, tsize) == t then
          spent[#spent + 1] = input
          spent[#spent + 1] = kid
          spent[#spent + 1] = tostring(i - 1)
        end
      end
    end
    answer[#answer + 1] = tostring(#spent / 3)
    for _, item in ipairs(spent) do
      answer[#answer + 1] = item
    end
  end
elseif what == 'spenders' then
  for _, o in ipairs(split(ARGV[5], osize)) do
    local s = redis.call('HGET', prefix .. 'pool:spender', o)
    local index = ''
    if s then
      for i, input in ipairs(split(redis.call('HGET', prefix .. 'pool:spends', s), osize)) do
        if input == o then
          index = tostring(i - 1)
        end
      end
    end
    answer[#answer + 1] = s or ''
    answer[#answer + 1] = index
  end
elseif what == 'page' then
  local offset, many = tonumber(ARGV[5]), tonumber(ARGV[6])
  local last = -1
  if many > 0 then
    last = offset + many - 1
  end
  local rows = redis.call('ZREVRANGE', prefix .. 'pool:time', offset, last, 'WITHSCORES')
  for i = 1, #rows, 2 do
    answer[#answer + 1] = rows[i]
    answer[#answer + 1] = string.format('%.0f', tonumber(rows[i + 1]))
  end
elseif what == 'ids' then
  local found = redis.call('ZRANGEBYLEX', prefix .. 'pool:ids',
    '[' .. ARGV[5], '(' .. ARGV[5] .. string.rep(string.char(255), tsize))
  for _, t in ipairs(found) do
    answer[#answer + 1] = t
  end
end
return answer