Skip to content

Instantly share code, notes, and snippets.

Last active April 21, 2018 22:40
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
local loaded, prailude = pcall(require,"prailude")
if not loaded then
error("'prailude' package is required for RaiBlocks packet dissassembly. Please install it using luarocks for " .. _VERSION)
local blake2b_hash = prailude.util.blake2b.hash
local unpack_balance = prailude.util.unpack_balance
local unpack_account = prailude.util.unpack_account
local stohex = prailude.util.to_hex
local TCP_state = {}
local packet_state = {}
local packet_bulkpull_state = {}
local packet_bulkpull_count = {}
local packet_frontier_state = {}
local function packet_endpoints_str(pinfo, reverse)
if not reverse then
return ("%s:%i->%s:%i"):format(pinfo.src, pinfo.src_port, pinfo.dst, pinfo.dst_port)
return ("%s:%i->%s:%i"):format(pinfo.dst, pinfo.dst_port, pinfo.src, pinfo.src_port)
local function raw_balance_to_xrb(raw)
local balance = Balance.unpack(raw, "Mxrb")
if balance then
return ("%s XRB"):format(tostring(balance))
return err
local function hash_hashable(str)
return blake2b_hash(str, 32)
local rai_proto = Proto("raiblocks", "RaiBlocks", "RaiBlocks Network Protocol")
local block_proto = Proto("raiblocks.block", "Block", "RaiBlocks Block")
-- setup protocol fields.
rai_proto.fields = {}
local fds = rai_proto.fields
local msg_types = {
local block_types = {
local header = {
greeting ="Magic number", "raiblocks.magic", ftypes.STRING),
version_max ="max version", "raiblocks.version_max", ftypes.UINT8, nil, base.DEC),
version_current ="current version", "raiblocks.version_using", ftypes.UINT8, nil, base.DEC),
version_min ="min version", "raiblocks.version_min", ftypes.UINT8, nil, base.DEC),
message_type ="message type", "raiblocks.message_type", ftypes.UINT8, msg_types, base.DEC),
extensions="extensions", "raiblocks.extensions", ftypes.UINT16, nil, base.HEX),
for k, f in pairs(header) do
fds.keepalive_peer="peer", "raiblocks.keepalive.peer", ftypes.STRING)
local bfds = {
block_type ="type", "raiblocks.block.type", ftypes.UINT8, block_types, base.DEC),
block_raw ="raw", "raiblocks.block.raw", ftypes.BYTES),
block_previous ="previous", "raiblocks.block.previous", ftypes.STRING),
block_hash ="hash", "raiblocks.block.hash", ftypes.STRING),
block_balance ="balance", "raiblocks.block.balance", ftypes.STRING),
block_destination ="destination", "raiblocks.block.destination", ftypes.STRING),
block_account ="account", "raiblocks.block.account", ftypes.STRING),
block_source ="source block", "raiblocks.block.source", ftypes.STRING),
block_representative ="representative", "raiblocks.block.representative",ftypes.STRING),
block_work ="work", "", ftypes.BYTES),
block_signature ="signature", "raiblocks.block.signature", ftypes.BYTES)
block_proto.fields = bfds
fds.vote_account ="voting account", "", ftypes.STRING)
fds.vote_signature ="vote signature", "", ftypes.BYTES)
fds.vote_sequence ="vote sequence", "", ftypes.UINT64)
fds.frontier_req_start ="frontier start account","raiblocks.frontier_req.start", ftypes.STRING)
fds.frontier_req_age ="frontier age", "raiblocks.frontier_req.age", ftypes.UINT32)
fds.frontier_req_count ="frontier count", "raiblocks.frontier_req.count", ftypes.UINT32)
local frontier_proto = Proto("", "Frontier", "RaiBlocks Frontier")
local ffds = {
account ="frontier account (raw)", "",ftypes.STRING),
block_hash ="frontier block hash", "",ftypes.STRING)
frontier_proto.fields = ffds
fds.bulk_pull_start_account ="bulk pull start account", "raiblocks.bulk_pull.account", ftypes.STRING)
fds.bulk_pull_end_block_hash ="bulk pull end block hash", "raiblocks.bulk_pull.block_hash",ftypes.STRING)
local block_size = {
send = 152,
receive = 136,
open = 168,
change = 136,
invalid = 0,
function message_block_dissector(buf, pinfo, root, btype_buf, block_info)
local fds = bfds
local blockt = block_types[btype_buf:uint()]
local tree = root:add(block_proto, ("Block %s(%s)"):format(block_info or "", blockt))
tree:add(fds.block_type, btype_buf)
if blockt == "send" then
tree:add(fds.block_raw, buf(0, block_size.send))
tree:add(fds.block_previous, buf(0, 32), stohex(buf:raw(0, 32)))
tree:add(fds.block_destination, buf(32, 32), unpack_account(buf:raw(32, 32)))
tree:add(fds.block_balance, buf(64, 16), raw_balance_to_xrb(buf:raw(64, 16)))
tree:add(fds.block_signature, buf(80, 64))
tree:add(fds.block_work, buf(144, 8))
tree:add(fds.block_hash, stohex(hash_hashable(buf:raw(0, 80))))
elseif blockt == "receive" then
tree:add(fds.block_raw, buf(0, block_size.receive))
tree:add(fds.block_previous, buf(0, 32), stohex(buf:raw(0, 32)))
tree:add(fds.block_source, buf(32, 32), stohex(buf:raw(32, 32)))
tree:add(fds.block_signature, buf(64, 64))
tree:add(fds.block_work, buf(128, 8))
tree:add(fds.block_hash, stohex(hash_hashable(buf:raw(0, 64))))
elseif blockt == "open" then
tree:add(fds.block_raw, buf(0,
tree:add(fds.block_source, buf(0, 32), stohex(buf:raw(0, 32)))
tree:add(fds.block_representative, buf(32, 32), unpack_account(buf:raw(32, 32)))
tree:add(fds.block_account, buf(64, 32), unpack_account(buf:raw(64, 32)))
tree:add(fds.block_signature, buf(96, 64))
tree:add(fds.block_work, buf(160, 8))
tree:add(fds.block_hash, stohex(hash_hashable(buf:raw(0, 96))))
elseif blockt == "change" then
tree:add(fds.block_raw, buf(0, block_size.change))
tree:add(fds.block_previous, buf(0, 32), stohex(buf:raw(0, 32)))
tree:add(fds.block_representative, buf(32, 32), unpack_account(buf:raw(32, 32)))
tree:add(fds.block_signature, buf(64, 64))
tree:add(fds.block_work, buf(128, 8))
tree:add(fds.block_hash, stohex(hash_hashable(buf:raw(0, 64))))
-- packet dissector
function rai_proto.dissector(buf, pinfo, root)
pinfo.cols.protocol = "raiblocks"
local tree = root:add(rai_proto)
local conn_id = packet_endpoints_str(pinfo)
local tcp_state = TCP_state[conn_id]
if tcp_state == "frontier" or packet_state[pinfo.number] == "frontier" then
packet_state[pinfo.number] = "frontier"
tree:add(header.message_type, 10000) --custom "frontier_req" response messat_type
local len = buf:len()
local entries = math.floor(len/64)
local leftovers=math.fmod(len, 64)
if leftovers > 0 then
--need more data to have a clean break for the next packet
pinfo.desegment_len = 64 - leftovers
return nil
local frontier, acct
for offset=0, 64*entries-1, 64 do
acct = unpack_account(buf:raw(offset, 32))
frontier = tree:add(frontier_proto, "Frontier ".. acct)
frontier:add(ffds.account, buf(offset, 32), stohex(buf:raw(offset, 32)))
frontier:add(ffds.block_hash, buf(offset+32, 32), stohex(buf:raw(offset+32, 32)))
--print("frontier entries:", entries, "leftovers:", leftovers)
elseif tcp_state=="bulk_pull" or packet_state[pinfo.number] == "bulk_pull" then
local bulkpull_count = bulkpull_start_count or 0
packet_state[pinfo.number] = "bulk_pull"
local more_blocks = true
local offset = 0
local block_len, block_type, btype_buf
tree:add(header.message_type, 10001) --custom "bulk" response message_type
while offset < len do
btype_buf = buf(offset,1)
block_type = block_types[btype_buf:uint()]
offset = offset+1
if not block_type then
return nil --weird?..
if block_type == "not_a_block" then
block_len = block_size[block_type]
if not block_len then
error("invalid block type")
elseif len - offset < block_size[block_type] then
local leftovers = block_size[block_type] - (len - offset)
pinfo.desegment_len = block_size[block_type] - (len - offset)
return nil
message_block_dissector(buf(offset):tvb(), pinfo, tree, btype_buf)
offset = offset + block_len
local magic
local len = buf:len()
if len >=2 then
magic = buf(0, 2):string()
if magic == "RA" or magic == "RB" or magic == "RC" then
local btype_buf = buf(7,1)
tree:add(header.greeting, buf(0, 2))
tree:add(header.version_max, buf(2, 1))
tree:add(header.version_current, buf(3, 1))
tree:add(header.version_min, buf(4, 1))
tree:add(header.message_type, buf(5, 1))
tree:add(header.extensions, buf(6, 1))
local msgt = msg_types[buf(5,1):uint()]
local blocktype = block_types[btype_buf:uint()]
if msgt == "keepalive" then
local blen = len - 8;
local ips = blen/18
for i=0, blen-1, 18 do
tree:add(fds.keepalive_peer, buf(8+i,18),
("%s:%i"):format(tostring(buf(8+i,16):ipv6()), buf(8+i+16,2):le_uint()))
elseif msgt == "publish" then
message_block_dissector(buf(8):tvb(), pinfo, tree, btype_buf)
elseif msgt == "confirm_req" then
message_block_dissector(buf(8):tvb(), pinfo, tree, btype_buf)
elseif msgt == "confirm_ack" then
tree:add(fds.vote_account, buf(8, 32), unpack_account(buf:raw(8, 32)))
tree:add(fds.vote_signature, buf(40, 64))
tree:add(fds.vote_sequence, buf(104, 8), buf(104,8):le_uint64())
message_block_dissector(buf(112):tvb(), pinfo, tree, btype_buf)
elseif msgt == "bulk_pull" then
tree:add(fds.bulk_pull_start_account,buf(8,32), unpack_account(buf:raw(8, 32)))
tree:add(fds.bulk_pull_end_block_hash,buf(40,32), stohex(buf:raw(40, 32)))
TCP_state[packet_endpoints_str(pinfo, "reverse")]="bulk_pull"
elseif msgt == "frontier_req" then
tree:add(fds.frontier_req_start, buf(8, 32), unpack_account(buf:raw(8, 32)))
tree:add(fds.frontier_req_age, buf(40, 4))
tree:add(fds.frontier_req_count, buf(44, 4))
TCP_state[packet_endpoints_str(pinfo, "reverse")]="frontier"
DissectorTable.get("udp.port"):add(7075, rai_proto)
DissectorTable.get("tcp.port"):add(7075, rai_proto)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment