Skip to content

Instantly share code, notes, and snippets.

@kalloc
Created April 2, 2024 23:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kalloc/6d0aa8610c89d1a1be8e528eb2e73242 to your computer and use it in GitHub Desktop.
Save kalloc/6d0aa8610c89d1a1be8e528eb2e73242 to your computer and use it in GitHub Desktop.
protoc-gen -decode parser
import sys
import json
import re
import base58 as bs58
from lark import Lark, Transformer
grammar = """
?start: item+
?item: pair
| object
| logs
| string
value: ESCAPED_STRING -> escaped
| SIGNED_NUMBER -> number
| string -> token
| "true" -> true
| "false" -> false
| "null" -> null
string: /[\w\d]+/
object: string "{" item* "}"
pair: string ":" value
logs: "Logs" ":" value
%import common.ESCAPED_STRING
%import common.SIGNED_NUMBER
%import common.WS
%ignore WS
"""
class ParseToDict(Transformer):
def start(self, items):
return self._merge_dicts(items)
def pair(self, items):
key, value = items
return {key: value}
def object(self, items):
key, *values = items
return {key: self._merge_dicts(values)}
def logs(self, items):
return {"Logs": items[0].decode()}
def string(self, s):
(s,) = s
return str(s)
token = string
def escaped(self, s):
s = s[0][1:-1]
return s.encode("utf8").decode("unicode-escape").encode("latin1")
def number(self, n):
(n,) = n
return int(n)
def true(self, _):
return True
def false(self, _):
return False
def null(self, _):
return None
def _merge_dicts(self, dict_list):
merged = {}
for dictionary in dict_list:
for key, value in dictionary.items():
if key in merged and isinstance(merged[key], list):
merged[key].append(value)
elif key in merged:
merged[key] = [merged[key], value]
else:
merged[key] = value
return merged
parser = Lark(grammar, start='start', parser='lalr', transformer=ParseToDict())
def parse_input(input_text):
tree = parser.parse(input_text)
return tree
if __name__ == '__main__':
if len(sys.argv) > 1:
with open(sys.argv[1], 'r') as file:
input_data = file.read()
else:
input_data = sys.stdin.read()
result = parse_input(input_data)
def encode_bytes(val):
if isinstance(val, bytes):
return bs58.b58encode(val).decode()
return val
print(json.dumps(result, indent=2, default=encode_bytes))
@kalloc
Copy link
Author

kalloc commented Apr 2, 2024

Test file:

~$ cat /tmp/3.bin
Header {
  Slot: 257138911
  Hash: "\210\027`\350\027\337!@\244\242\302\332\001\007\344\304n48\256\375\310\267\354\254%\3758,\020\0343"
  ParentSlot: 257138910
  Height: 237553627
  Timestamp: 1711709203
  ParentHash: "\177\204o)\252%\347\253\3041\244\345\3571\0079l8!\305\315\003r\205\230\230\312\357\320H7\265"
}
Rewards {
  Address: "p\036X\214.\357M\272\245\316\027\225\200\307>[\357\201\364\324\327\306T\260e\317<q\022S\334("
  Amount: 8387882
  PostBalance: 1996063329
  RewardType: Fee
}
Transactions {
  Signature: "L\305v`\332\026 l\341\023\010\336\303\343\025\271@\033\265u\317\003\322\245\364\347\307-\003\020\322\2305g\255\204\021Q\rk\332\337\313\027\246\"\244\022\177\236\017\232\224\330\2321\247\325,\021\371{&\000"
  Status {
    Success: true
  }
  Header {
    Fee: 5000
    FeePayer: "~\340V\263`\202\234\021\266m\240\215]\026\337U\324I\266\214\337E\024J\360\352\204\023QD\343C"
    RecentBlockhash: "I\035c\323\327\352\262\322\313e\016&b\347\016\374\334\206\234:\336\037\354\326\244\032\031\022\201i\005\353"
    Signer: "~\340V\263`\202\234\021\266m\240\215]\026\337U\324I\266\214\337E\024J\360\352\204\023QD\343C"
    Signatures: "L\305v`\332\026 l\341\023\010\336\303\343\025\271@\033\265u\317\003\322\245\364\347\307-\003\020\322\2305g\255\204\021Q\rk\332\337\313\027\246\"\244\022\177\236\017\232\224\330\2321\247\325,\021\371{&\000"
    Accounts {
      Address: "~\340V\263`\202\234\021\266m\240\215]\026\337U\324I\266\214\337E\024J\360\352\204\023QD\343C"
      IsSigner: true
      IsWritable: true
    }
    Accounts {
      Address: "z\017\201Z\247w\373\352a\3663\254\371\264?\032\325\236\375+>\207\344\246\363+\234\310\274#\037\237"
      IsWritable: true
    }
    Accounts {
      Address: "\007aH\0355tt\273|Mv$\353\323\275\263\3305^s\321\020C\374\r\243S\200\000\000\000\000"
    }
  }
  TotalBalanceUpdates {
    PreBalance: 77341150529
    PostBalance: 77341145529
  }
  TotalBalanceUpdates {
    PreBalance: 99348543240
    PostBalance: 99348543240
    AccountIndex: 1
  }
  TotalBalanceUpdates {
    PreBalance: 1
    PostBalance: 1
    AccountIndex: 2
  }
  Instructions {
    ProgramAccountIndex: 2
    Data: "\014\000\000\000\275\240S\017\000\000\000\000\037\001\037\001\036\001\035\001\034\001\033\001\032\001\031\001\030\001\027\001\026\001\025\001\024\001\023\001\022\001\021\001\020\001\017\001\016\001\r\001\014\001\013\001\n\001\t\001\010\001\007\001\006\001\005\001\004\001\003\001\002\001\001W\375\026n\252Q\221l3d\224\372k&\'\365\221\360f\270P\232g\345^~\006&)\350\t8\001\371\233\006f\000\000\000\000"
    AccountIndexes: 1
    AccountIndexes: 0
  }
  Logs: "Program Vote111111111111111111111111111111111111111 invoke [1]"
  Logs: "Program Vote111111111111111111111111111111111111111 success"
}

Usage example:

pypy3 1.py /tmp/3.bin  | jq
{
  "Header": {
    "Slot": 257138911,
    "Hash": "AAF88m22TksJF46PNsZvTrNYCf6CF8Fb98nfV79Xx48z",
    "ParentSlot": 257138910,
    "Height": 237553627,
    "Timestamp": 1711709203,
    "ParentHash": "9amuP6USzrtc6DTwdVTNptJcL9BJ4NBZYrPoTdSaUKQG"
  },
  "Rewards": {
    "Address": "8YfW1hTVEdNWwYYEGz4xG55mNAoZPBMR7a3XQbCx7utB",
    "Amount": 8387882,
    "PostBalance": 1996063329,
    "RewardType": "Fee"
  },
  "Transactions": {
    "Signature": "2Y2RNY6wNv6u8GDQbBq7pvU5DnFBqExW6iQDmncT8sydfp85V8KEHjPFSvnzxRJkCde68cwxvZGnwEiD4w9XAbuh",
    "Status": {
      "Success": true
    },
    "Header": {
      "Fee": 5000,
      "FeePayer": "9YGn24qD8ZCU1EKZEsPLSEv7SVAJ4kpbCTmj7fR5TXYv",
      "RecentBlockhash": "5vQkmLQNWTYZZYsNyZqnjWqmeUNJvH2u9N6PX558zy6J",
      "Signer": "9YGn24qD8ZCU1EKZEsPLSEv7SVAJ4kpbCTmj7fR5TXYv",
      "Signatures": "2Y2RNY6wNv6u8GDQbBq7pvU5DnFBqExW6iQDmncT8sydfp85V8KEHjPFSvnzxRJkCde68cwxvZGnwEiD4w9XAbuh",
      "Accounts": [
        {
          "Address": "9YGn24qD8ZCU1EKZEsPLSEv7SVAJ4kpbCTmj7fR5TXYv",
          "IsSigner": true,
          "IsWritable": true
        },
        {
          "Address": "9DUTDovSg7Tfc2C9mBHvtGfqfgaBY7srBb2ZYPCDgUPY",
          "IsWritable": true
        },
        {
          "Address": "Vote111111111111111111111111111111111111111"
        }
      ]
    },
    "TotalBalanceUpdates": [
      {
        "PreBalance": 77341150529,
        "PostBalance": 77341145529
      },
      {
        "PreBalance": 99348543240,
        "PostBalance": 99348543240,
        "AccountIndex": 1
      },
      {
        "PreBalance": 1,
        "PostBalance": 1,
        "AccountIndex": 2
      }
    ],
    "Instructions": {
      "ProgramAccountIndex": 2,
      "Data": "Fk63Pw6ZqcaWxYBcQPgEbsnBWVcU6cx6sZFHZ3UMy1Y3ouuyyTtUo8CVi1MVSJ7s2DYb5HnARkQyURTZs1Wg1pnfU263CscpMS4soVvAnjRfwpGCibtQrjVdbrLksK1GQvvrQBY919R1Z96cbYR6cjsraqhV9Z",
      "AccountIndexes": [
        1,
        0
      ]
    },
    "Logs": [
      "Program Vote111111111111111111111111111111111111111 invoke [1]",
      "Program Vote111111111111111111111111111111111111111 success"
    ]
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment