diff --git a/.gitignore b/.gitignore index f09b935..a1f5f0c 100644 --- a/.gitignore +++ b/.gitignore @@ -825,4 +825,7 @@ adapters/**/*.csv adapters/**/*.jsonl # Sublime Text -*.sublime* \ No newline at end of file +*.sublime* + +# parquet-sink-files state file +*state.yaml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index c7bb34e..102283f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "blocks/solana", "blocks/cosmos", "blocks/beacon", + "blocks/solana-parquet", "common", ] resolver = "2" @@ -22,5 +23,6 @@ substreams-solana = "0.14" substreams-cosmos = "0.2" prost = "0.13" prost-types = "0.13" +serde_json = "1.0" # substreams-bitcoin = "1.0" # substreams-near = "0.9" diff --git a/blocks/solana-parquet/Cargo.toml b/blocks/solana-parquet/Cargo.toml new file mode 100644 index 0000000..a8874c4 --- /dev/null +++ b/blocks/solana-parquet/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "raw_blocks_solana_parquet" +edition = { workspace = true } +version = { workspace = true } + +[lib] +crate-type = ["cdylib"] + +[dependencies] +common = { path = "../../common" } +substreams-solana = { workspace = true } +substreams-database-change = { workspace = true } +substreams = { workspace = true } +serde_json = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } diff --git a/blocks/solana-parquet/Makefile b/blocks/solana-parquet/Makefile new file mode 100644 index 0000000..95d689c --- /dev/null +++ b/blocks/solana-parquet/Makefile @@ -0,0 +1,64 @@ +.PHONY: all +all: + make build + make pack + make graph + make info + +.PHONY: build +build: + cargo build --target wasm32-unknown-unknown --release + +.PHONY: pack +pack: + substreams pack + +.PHONY: graph +graph: + substreams graph + +.PHONY: info +info: + substreams info + +.PHONY: run +run: + substreams run substreams.yaml -e solana.substreams.pinax.network:443 ch_out -s 203100000 -o json + +.PHONY: protogen +protogen: + substreams protogen --exclude-paths sf/substreams,google + +.PHONY: cache +cache: + substreams gui substreams.yaml -e solana.substreams.pinax.network:443 ch_out -s 203100000 -t 203200000 --production-mode + +.PHONY: cache-foundational +cache-foundational: + substreams gui https://github.com/streamingfast/substreams-foundational-modules/releases/download/solana-common-v0.3.0/solana-common-v0.3.0.spkg -e solana.substreams.pinax.network:443 blocks_without_votes -s 203100000 -t 203200000 --production-mode + +.PHONY: gui +gui: + substreams gui substreams.yaml -e solana.substreams.pinax.network:443 ch_out -s 203100000 + +.PHONY: sql-setup +sql-setup: + # EVM blocks + substreams-sink-sql setup clickhouse://default:default@localhost:9000/solana substreams.yaml + +.PHONY: sql-run-solana +sql-run-solana: + substreams-sink-sql run clickhouse://default:default@localhost:9000/solana substreams.yaml -e solana.substreams.pinax.network:443 203100000:203100100 --final-blocks-only --undo-buffer-size 1 --on-module-hash-mistmatch=warn --batch-block-flush-interval 1 --development-mode + +.PHONY: sql-run-solana-blocks +sql-run-solana-blocks: + substreams-sink-sql run clickhouse://default:default@localhost:9000/solana substreams.yaml -e solana.substreams.pinax.network:443 2: --final-blocks-only --undo-buffer-size 1 --on-module-hash-mistmatch=warn --batch-block-flush-interval 100 --params ch_out=blocks + +.PHONY: parquet +parquet: + substreams-sink-files run solana.substreams.pinax.network:443 substreams.yaml ch_out_without_votes './out' 203100000:203100500 --encoder parquet --file-block-count 100 --development-mode + +.PHONY: deploy +deploy: + graph build + graph deploy --studio clock \ No newline at end of file diff --git a/blocks/solana-parquet/README.md b/blocks/solana-parquet/README.md new file mode 100644 index 0000000..1691ca6 --- /dev/null +++ b/blocks/solana-parquet/README.md @@ -0,0 +1,29 @@ +## `Solana` Raw Blockchain Data + +> Solana +> [`sf.solana.type.v1.Block`](https://buf.build/streamingfast/firehose-solana/docs/main:sf.solana.type.v1) + +- [x] **Blocks** +- [x] **Transactions** +- [x] **Instruction Calls** +- [x] **Account Activity** +- [x] **Rewards** +- [x] **Vote Transactions** +- [x] **Vote Instruction Calls** +- [x] **Vote Account Activity** +- ~~[ ] **Discriminators**~~ + + +```mermaid +graph TD; + raw[sf.solana.type.v1.Block]; + raw --> blocks; + raw --> transactions; + raw --> instruction_calls; + raw --> account_activity; + raw --> rewards; + raw --> vote_transactions; + raw --> vote_instruction_calls; + raw --> vote_account_activity; + raw --> discriminators; +``` \ No newline at end of file diff --git a/blocks/solana-parquet/docs/example-queries.md b/blocks/solana-parquet/docs/example-queries.md new file mode 100644 index 0000000..c740ae5 --- /dev/null +++ b/blocks/solana-parquet/docs/example-queries.md @@ -0,0 +1,65 @@ +# Example Solana Raw Blocks Queries + +## Select Transaction with instruction calls + +### Query + +```sql +SELECT * +FROM solana.transactions AS tx +JOIN solana.instruction_calls AS ins +ON tx.id = ins.tx_id +WHERE tx.id = '1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja'; +``` + +### Result + +| block\_time | block\_hash | block\_date | block\_slot | block\_height | block\_previous\_block\_hash | block\_parent\_slot | id | index | fee | required\_signatures | required\_signed\_accounts | required\_unsigned\_accounts | signature | success | error | recent\_block\_hash | account\_keys | log\_messages | pre\_balances | post\_balances | signatures | signer | signers | ins.block\_time | ins.block\_hash | ins.block\_date | ins.block\_slot | ins.block\_height | ins.block\_previous\_block\_hash | ins.block\_parent\_slot | tx\_id | tx\_index | tx\_signer | tx\_success | ins.log\_messages | outer\_instruction\_index | inner\_instruction\_index | inner\_executing\_account | outer\_executing\_account | executing\_account | is\_inner | data | account\_arguments | inner\_instructions | +| :------------------- | :-------------------------------------------- | :---------- | :---------- | :------------ | :------------------------------------------- | :------------------ | :-------------------------------------------------------------------------------------- | :---- | :--- | :------------------- | :------------------------- | :--------------------------- | :-------------------------------------------------------------------------------------- | :------ | :---- | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------ | :------------------------------------------------ | :-------------------------------------------------------------------------------------- | :------------------------------------------- | :------------------------------------------- | :------------------- | :-------------------------------------------- | :-------------- | :-------------- | :---------------- | :------------------------------------------- | :---------------------- | :-------------------------------------------------------------------------------------- | :-------- | :------------------------------------------- | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------ | :------------------------ | :------------------------------------------- | :------------------------------------------- | :------------------------------------------- | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | true | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 0 | -1 | | ComputeBudget111111111111111111111111111111 | ComputeBudget111111111111111111111111111111 | false | 0x030000000000000000 | | | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | true | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 1 | -1 | | cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ | cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ | false | 0xfe0003a071319840ebaac7d7cdef76261ccf5fe1d023370b9ed5aa6ba8617f71860000000000000000000000000000000000000000000000000000000002301b063ccdc47a41c98cffbd2b63b1fd977043b2b35d44ee320b791c377961f3a1a12564a254ee0e0a09050b070d060103020804000e00000000000000000000000000000000000000000000000000000000000005f5da3600000000b0dade43655b2a1255342bbe6a371f65d43a591b73de8d5924910b9abb6f45f87eef8297452e5a2207b5ee975ba7fedfc51fda92cc979f5a92403ba67371bbed55eec83500338d19ff0e45ea7fdc920eddc06a9c7e26c469a144458f9df2ab783586d372e4324a06ff902adaa2b843747002ab49c530dbf4c69e1f924ca05fc47e15dab5c700d5a66249438278ee9ab8de8bb07166cbdcd441e8781682c2f005ae6cdf2e452640a264d087879b38053ef7328b66599a4eb2e51062f1ed03e9e4a9318324a59400ccda5a5ca8495427b9502869545ed8fa49025ee998d5707d1fc038de0a9a715734a0652a6867119d8bddf63d71bfbff28459329e9537b64f28a61c69bcbf35db011392645ab56d48f8e4d2848838d6a2a98b2382e20d44980580c342b4aae416f36358a184042dd8c82cc8324794bfa0b0b73be7723ec20c20220700b69b2bedfd01791e127559fc2859e6426f642dce3b0c9e2ffb5d3c388acc2458a93fc9e0d40b668f40f8885ec74d579fb800443aa9a155becc19ab3ef043947353f280193a4901 | F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111 | \(6y43XFem5gkMhTXjt4LTWkhZUNDEgZj8iVpKmo8VxUuu,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ\) | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | true | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 1 | 0 | HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny | cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ | HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny | true | 0x58a666b5a27faa30ee54a2640000000036daf505000000000000000000000000 | GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ | | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | true | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 2 | -1 | | HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny | HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny | false | 0x58a666b5a27faa30ee54a2640000000036daf505000000000000000000000000 | GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ | | + +## Select Transaction with account activity + +### Query + +```sql +SELECT * +FROM solana.transactions AS tx +JOIN solana.account_activity AS act +ON tx.id = act.tx_id +WHERE tx.id = '1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja'; +``` +### Result + +| block\_time | block\_hash | block\_date | block\_slot | block\_height | block\_previous\_block\_hash | block\_parent\_slot | id | index | fee | required\_signatures | required\_signed\_accounts | required\_unsigned\_accounts | signature | success | error | recent\_block\_hash | account\_keys | log\_messages | pre\_balances | post\_balances | signatures | signer | signers | act.block\_time | act.block\_hash | act.block\_date | act.block\_slot | act.block\_height | act.block\_previous\_block\_hash | act.block\_parent\_slot | address | tx\_index | tx\_id | tx\_success | signed | writable | token\_mint\_address | pre\_balance | post\_balance | balance\_change | pre\_token\_balance | post\_token\_balance | token\_balance\_change | token\_balance\_owner | +| :------------------- | :-------------------------------------------- | :---------- | :---------- | :------------ | :------------------------------------------- | :------------------ | :-------------------------------------------------------------------------------------- | :---- | :--- | :------------------- | :------------------------- | :--------------------------- | :-------------------------------------------------------------------------------------- | :------ | :---- | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------ | :------------------------------------------------ | :-------------------------------------------------------------------------------------- | :------------------------------------------- | :------------------------------------------- | :------------------- | :------------------------------------------ | :-------------- | :-------------- | :---------------- | :------------------------------------------- | :---------------------- | :------------------------------------------- | :-------- | :-------------------------------------------------------------------------------------- | :---------- | :----- | :------- | :------------------- | :----------- | :------------ | :-------------- | :------------------ | :------------------- | :--------------------- | :-------------------- | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | Yf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 207 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | true | true | | 6446823771 | 6446818771 | -5000 | | | | | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | Yf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ | 207 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | false | false | | 0 | 0 | 0 | | | | | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | Yf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA | 207 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | false | true | | 49054080 | 49054080 | 0 | | | | | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | Yf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5 | 207 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | false | true | | 2616960 | 2616960 | 0 | | | | | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | Yf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny | 207 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | false | false | | 1141440 | 1141440 | 0 | | | | | +| 2023-07-03T04:56:16Z | 0xYf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 207 | 5000 | 1 | 0 | 5 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | | CuPd97D9FW7dQ2LqiVPeimem9yKhxr8yxKuyf1emvyro | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE,F86tf6LbPqrUDWEoHt7vhNafaVKTVWqXefvH7BMaUBKA,GzGuoKXE8Unn7Vcg1DtomwD27tL4bVUpSK2M1yk6Xfz5,HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny,DxJ3uy4nHka6CGbz14969FoW3qckzHkHxsrfZkZyiimJ,Sysvar1nstructions1111111111111111111111111,cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ,ComputeBudget111111111111111111111111111111 | Program ComputeBudget111111111111111111111111111111 invoke \[1\],Program ComputeBudget111111111111111111111111111111 success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke \[1\],Program data: gjbLTR5rT6jDtGYAAAOgcTGYQOuqx9fN73YmHM9f4dAjNwue1aprqGF/cYY22vUFAAAAAAAAAAAAAAAACO5UomQOCgkFCwcNBgEDAggEAA4AAAAAAEPe2rAAAAAA8zkAAAAAAAA=,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke \[2\],Program log: Instruction: Submit,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4421 of 36554 compute units,Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 168510 of 200000 compute units,Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success | 6446823771,49054080,2616960,1141440,0,0,1141440,1 | 6446818771,49054080,2616960,1141440,0,0,1141440,1 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 6rRiMihF7UdJz25t5QvS7PgP9yzfubN7TBRv26ZBVAhE | 2023-07-03T04:56:16Z | Yf7jzwDHrBnKK3kJPAjaQyS9nubm7F4zGMTNzESKevj | 2023-07-03 | 203100053 | 185694566 | 46nUjSeYibkNvDwrEpxCjtL3ndTcdQo7LHsSuHsZQ5h3 | 203100052 | cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ | 207 | 1o8kTX8uRStCfugAagQyBCeon7k9HhXZeqYB1ifiHTaQWN6gZ4i3JNsq7kBHWbw7odStR9SykguntEQQ1cQpHja | true | false | false | | 1141440 | 1141440 | 0 | | | | | + +## Rewards + +### Query + +```sql +SELECT * +FROM solana.rewards +WHERE pubkey = '1EWZm7aZYxfZHbyiELXtTgN1yT2vU1HF9d8DWswX2Tp' +AND block_slot between 203100018 AND 203100035; +``` + +### Result + +| block\_time | block\_date | block\_hash | block\_slot | block\_height | block\_previous\_block\_hash | block\_parent\_slot | pubkey | lamports | pre\_balance | post\_balance | reward\_type | commission | +| :------------------- | :---------- | :------------------------------------------- | :---------- | :------------ | :------------------------------------------- | :------------------ | :------------------------------------------ | :------- | :----------- | :------------ | :----------- | :--------- | +| 2023-07-03T04:56:04Z | 2023-07-03 | 7ysWVCWs7ti1g5F1ULTuYheF6Ev4hXbg1PiHhzMKeWTh | 203100026 | 185694539 | J9Tn9ykEH6mS8C9XdymJqGi8hhjKpMnuVJ9ce1ijqMVZ | 203100025 | 1EWZm7aZYxfZHbyiELXtTgN1yT2vU1HF9d8DWswX2Tp | 1 | 93064093912 | 93064093913 | Rent | | +| 2023-07-03T04:56:01Z | 2023-07-03 | 8siBGoC23h9zub5gomzR4i3sEkn4hAnDhm7rCAfiBzDH | 203100018 | 185694531 | 88yrKGHMLjQ9KoybgjcshnU9Hagh5koDgq3Mjuig75RQ | 203100017 | 1EWZm7aZYxfZHbyiELXtTgN1yT2vU1HF9d8DWswX2Tp | 1 | 93064138910 | 93064138911 | Rent | | +| 2023-07-03T04:56:05Z | 2023-07-03 | 9NG3jAv434KuSnEqWYTovUcfm2Kg81nNeCevMaV8dkaV | 203100029 | 185694542 | 7HjjsWTH2qeDetyiZQxEHr5twmMCv4zGkM31qgmmMzCa | 203100028 | 1EWZm7aZYxfZHbyiELXtTgN1yT2vU1HF9d8DWswX2Tp | 1 | 93064083913 | 93064083914 | Rent | | +| 2023-07-03T04:56:08Z | 2023-07-03 | AHWvERM3jYQspusM7MJbq38QvxgjkVbAP1PgoWS6p1VV | 203100034 | 185694547 | 4U62GxXQn8FAWVotgEcmb6FGvvRKJ2BaZgnni6YgNWWJ | 203100033 | 1EWZm7aZYxfZHbyiELXtTgN1yT2vU1HF9d8DWswX2Tp | 1 | 93064053914 | 93064053915 | Rent | | +| 2023-07-03T04:56:01Z | 2023-07-03 | HvGkJujqmtsY7tzSYW3YgJQXkKe26KuESuVvzBEvecio | 203100019 | 185694532 | 8siBGoC23h9zub5gomzR4i3sEkn4hAnDhm7rCAfiBzDH | 203100018 | 1EWZm7aZYxfZHbyiELXtTgN1yT2vU1HF9d8DWswX2Tp | 1 | 93064133911 | 93064133912 | Rent | | \ No newline at end of file diff --git a/blocks/solana-parquet/logo.png b/blocks/solana-parquet/logo.png new file mode 100644 index 0000000..4c4766e Binary files /dev/null and b/blocks/solana-parquet/logo.png differ diff --git a/blocks/solana-parquet/proto/solana.rawblocks.proto b/blocks/solana-parquet/proto/solana.rawblocks.proto new file mode 100644 index 0000000..52a4a56 --- /dev/null +++ b/blocks/solana-parquet/proto/solana.rawblocks.proto @@ -0,0 +1,154 @@ +syntax = "proto3"; + +package solana.rawblocks; + +import "google/protobuf/timestamp.proto"; + +message Events { + repeated Block blocks = 1; + repeated Reward rewards = 2; + repeated Transaction transactions = 3; + repeated InstructionCall instruction_calls = 4; + repeated AccountActivity account_activity = 5; + repeated Transaction vote_transactions = 6; + repeated InstructionCall vote_instruction_calls = 7; + repeated AccountActivity vote_account_activity = 8; +} + +message Block { + // clock + google.protobuf.Timestamp time = 1; + string date = 2; + string hash = 3; + + // block + uint64 slot = 4; + uint64 height = 5; + string previous_block_hash = 6; + uint64 parent_slot = 7; + + // counters + uint64 total_transactions = 8; + uint64 successful_transactions = 9; + uint64 failed_transactions = 10; + uint64 total_vote_transactions = 11; + uint64 total_non_vote_transactions = 12; + uint64 successful_vote_transactions = 13; + uint64 successful_non_vote_transactions = 14; + uint64 failed_vote_transactions = 15; + uint64 failed_non_vote_transactions = 16; +} + +message Reward { + // clock + google.protobuf.Timestamp block_time = 1; + string block_date = 2; + string block_hash = 3; + + // block + uint64 block_slot = 4; + uint64 block_height = 5; + string block_previous_block_hash = 6; + uint64 block_parent_slot = 7; + + // reward + string pubkey = 8; + int64 lamports = 9; + uint64 pre_balance = 10; + uint64 post_balance = 11; + string reward_type = 12; + string commission = 13; +} + +message Transaction { + // clock + google.protobuf.Timestamp block_time = 1; + string block_hash = 2; + string block_date = 3; + + // block + uint64 block_slot = 4; + uint64 block_height = 5; + string block_previous_block_hash = 6; + uint64 block_parent_slot = 7; + + // transaction + string id = 8; + uint32 index = 9; + uint64 fee = 10; + uint32 required_signatures = 11; + uint32 required_signed_accounts = 12; + uint32 required_unsigned_accounts = 13; + string signature = 14; + bool success = 15; + string error = 16; + string recent_block_hash = 17; + repeated string account_keys = 18; + string log_messages = 19; + repeated string pre_balances = 20; + repeated string post_balances = 21; + repeated string signatures = 22; + string signer = 23; + repeated string signers = 24; +} + +message InstructionCall { + // clock + google.protobuf.Timestamp block_time = 1; + string block_hash = 2; + string block_date = 3; + + // block + uint64 block_slot = 4; + uint64 block_height = 5; + string block_previous_block_hash = 6; + uint64 block_parent_slot = 7; + + // transaction + string tx_id = 8; + uint32 tx_index = 9; + string tx_signer = 10; + bool tx_success = 11; + repeated string log_messages = 12; + + // instruction + uint32 outer_instruction_index = 13; + int32 inner_instruction_index = 14; + string inner_executing_account = 15; + string outer_executing_account = 16; + string executing_account = 17; + bool is_inner = 18; + string data = 19; + repeated string account_arguments = 20; + // string representing a nested array. Switch to nested array type when supported by sink-files + string inner_instructions = 21; +} + +message AccountActivity { + // clock + google.protobuf.Timestamp block_time = 1; + string block_hash = 2; + string block_date = 3; + + // block + uint64 block_slot = 4; + uint64 block_height = 5; + string block_previous_block_hash = 6; + uint64 block_parent_slot = 7; + + string address = 8; + uint32 tx_index = 9; + string tx_id = 10; + bool tx_success = 11; + bool signed = 12; + bool writable = 13; + optional string token_mint_address = 14; + + uint64 pre_balance = 15; + uint64 post_balance = 16; + int64 balance_change = 17; + optional double pre_token_balance = 18; + optional double post_token_balance = 19; + optional double token_balance_change = 20; + optional string token_balance_owner = 21; +} diff --git a/blocks/solana-parquet/src/account_activity.rs b/blocks/solana-parquet/src/account_activity.rs new file mode 100644 index 0000000..c94f4f3 --- /dev/null +++ b/blocks/solana-parquet/src/account_activity.rs @@ -0,0 +1,155 @@ +use substreams_solana::pb::sf::solana::r#type::v1::{ConfirmedTransaction, MessageHeader, TokenBalance, Transaction}; + +use crate::{ + pb::solana::rawblocks::AccountActivity, + structs::{BlockInfo, BlockTimestamp}, + utils::get_account_keys_extended, +}; + +pub fn collect_account_activities(block_info: &BlockInfo, timestamp: &BlockTimestamp, transactions: &Vec<(usize, &ConfirmedTransaction)>) -> Vec { + let mut account_activities: Vec = Vec::new(); + + for (index, transaction) in transactions { + let meta = match transaction.meta.as_ref() { + Some(m) => m, + None => continue, // Skip if metadata is missing + }; + let transaction_id = transaction.id(); + let transaction_index = index.to_string(); // Consider reusing a buffer if performance is critical + let tx_success = meta.err.is_none(); + + let trx = match transaction.transaction.as_ref() { + Some(t) => t, + None => continue, // Skip if transaction data is missing + }; + + let account_keys_extended = get_account_keys_extended(transaction); + + // Precompute a mapping from account_index to pre_token_balance_index + let account_to_token_balance_map: Vec> = { + // Determine the maximum account_index to size the vector appropriately + let max_account_index = meta.pre_token_balances.iter().map(|balance| balance.account_index as usize).max().unwrap_or(0); + let mut map = vec![None; max_account_index + 1]; + for (i, balance) in meta.pre_token_balances.iter().enumerate() { + let idx = balance.account_index as usize; + // Assuming that the last occurrence is preferred if duplicates exist + map[idx] = Some(i); + } + map + }; + + let header = transaction + .transaction + .as_ref() + .and_then(|tx| tx.message.as_ref()) + .and_then(|msg| msg.header.as_ref()) + .expect("Transaction message header is missing"); + + let writability = determine_writability(header, account_keys_extended.len()); + + for (balance_index, (pre_balance, post_balance)) in meta.pre_balances.iter().zip(meta.post_balances.iter()).enumerate() { + let address = account_keys_extended.get(balance_index).unwrap(); + + // Skip if address is a program derived address + if address.ends_with("1111") { + continue; + } + + // Efficiently retrieve the pre_token_balance_index using the precomputed map + let pre_token_balance_index = account_to_token_balance_map.get(balance_index).copied().flatten().unwrap_or(usize::MAX); + + let (pre_token_balance, post_token_balance, token_balance_change, mint, owner) = if pre_token_balance_index != usize::MAX { + extract_token_balance_changes(&meta.pre_token_balances, &meta.post_token_balances, pre_token_balance_index) + } else { + (None, None, None, None, None) + }; + + let balance_change = *post_balance as i128 - *pre_balance as i128; + let signed = is_signed(trx, balance_index); + let writable = writability.get(balance_index).unwrap_or(&false); + + account_activities.push(AccountActivity { + block_time: Some(timestamp.time), + block_hash: timestamp.hash.clone(), + block_date: timestamp.date.clone(), + block_slot: block_info.slot, + block_height: block_info.height, + block_previous_block_hash: block_info.previous_block_hash.clone(), + block_parent_slot: block_info.parent_slot, + address: address.clone(), + tx_index: transaction_index.parse().unwrap(), + tx_id: transaction_id.clone(), + tx_success, + signed, + writable: *writable, + token_mint_address: mint, + pre_balance: *pre_balance, + post_balance: *post_balance, + balance_change: balance_change as i64, + pre_token_balance, + post_token_balance, + token_balance_change, + token_balance_owner: owner, + }); + } + } + + account_activities +} + +// Extracts the token balance changes for a given account index +fn extract_token_balance_changes(pre_balances: &[TokenBalance], post_balances: &[TokenBalance], index: usize) -> (Option, Option, Option, Option, Option) { + let pre_balance = pre_balances.get(index).and_then(|pre_balance_entry| pre_balance_entry.ui_token_amount.as_ref().map(|ui| ui.ui_amount)); + let post_balance = post_balances + .get(index) + .and_then(|post_balance_entry| post_balance_entry.ui_token_amount.as_ref().map(|ui| ui.ui_amount)); + let mint = pre_balances + .get(index) + .and_then(|pre_balance_entry| if !pre_balance_entry.mint.is_empty() { Some(pre_balance_entry.mint.clone()) } else { None }); + let owner = pre_balances + .get(index) + .and_then(|pre_balance_entry| if !pre_balance_entry.owner.is_empty() { Some(pre_balance_entry.owner.clone()) } else { None }); + + let token_balance_change = match (pre_balance, post_balance) { + (Some(pre), Some(post)) => { + // Use a more precise calculation method to avoid floating-point precision issues + let pre_scaled = (pre * 1_000_000_000.0).round() as i128; + let post_scaled = (post * 1_000_000_000.0).round() as i128; + Some((post_scaled - pre_scaled) as f64 / 1_000_000_000.0) + } + _ => None, + }; + + (pre_balance, post_balance, token_balance_change, mint, owner) +} + +// Returns a vector of writability for each account based on index in the transaction +fn determine_writability(header: &MessageHeader, total_accounts: usize) -> Vec { + let mut writability = vec![false; total_accounts]; + + let num_required_signatures = header.num_required_signatures as usize; + let num_readonly_signed_accounts = header.num_readonly_signed_accounts as usize; + let num_readonly_unsigned_accounts = header.num_readonly_unsigned_accounts as usize; + + for index in 0..total_accounts { + if index < num_required_signatures { + // Signed accounts + if index >= num_readonly_signed_accounts { + writability[index] = true; // Writable + } else { + writability[index] = false; // Read-Only + } + } else if index < total_accounts - num_readonly_unsigned_accounts { + // Unsigned Read-Only accounts + writability[index] = true; + } else { + // Unsigned Writable accounts + writability[index] = false; + } + } + writability +} + +fn is_signed(trx: &Transaction, index: usize) -> bool { + trx.signatures.len() > index +} diff --git a/blocks/solana-parquet/src/blocks.rs b/blocks/solana-parquet/src/blocks.rs new file mode 100644 index 0000000..336335b --- /dev/null +++ b/blocks/solana-parquet/src/blocks.rs @@ -0,0 +1,38 @@ +use substreams_solana::pb::sf::solana::r#type::v1::Block; + +use crate::structs::BlockTimestamp; +use crate::{pb::solana::rawblocks::Block as RawBlock, structs::BlockInfo}; + +use crate::counters::get_block_counters; + +pub fn get_block_info(block: &Block) -> BlockInfo { + BlockInfo { + slot: block.slot, + height: block.block_height.as_ref().expect("Block height is missing").block_height, + previous_block_hash: block.previous_blockhash.clone(), + parent_slot: block.parent_slot, + } +} + +pub fn collect_block(block: &Block, timestamp: &BlockTimestamp, block_info: &BlockInfo) -> Option { + let counters = get_block_counters(block); + + Some(RawBlock { + time: Some(timestamp.time), + date: timestamp.date.clone(), + hash: timestamp.hash.clone(), + slot: block.slot, + height: block_info.height, + previous_block_hash: block_info.previous_block_hash.clone(), + parent_slot: block_info.parent_slot, + total_transactions: counters.total_transactions, + successful_transactions: counters.successful_transactions, + failed_transactions: counters.failed_transactions, + total_vote_transactions: counters.total_vote_transactions, + total_non_vote_transactions: counters.total_non_vote_transactions, + successful_vote_transactions: counters.successful_vote_transactions, + successful_non_vote_transactions: counters.successful_non_vote_transactions, + failed_vote_transactions: counters.failed_vote_transactions, + failed_non_vote_transactions: counters.failed_non_vote_transactions, + }) +} diff --git a/blocks/solana-parquet/src/blocks_without_votes_all.rs b/blocks/solana-parquet/src/blocks_without_votes_all.rs new file mode 100644 index 0000000..15d6422 --- /dev/null +++ b/blocks/solana-parquet/src/blocks_without_votes_all.rs @@ -0,0 +1,19 @@ +use substreams_solana::pb::sf::solana::r#type::v1::Block; + +use crate::utils::VOTE_INSTRUCTION; + +#[substreams::handlers::map] +fn blocks_without_votes_all(mut block: Block) -> Result { + block.transactions.retain(|trx| { + let transaction = match trx.transaction.as_ref() { + Some(transaction) => transaction, + None => return false, + }; + let message = transaction.message.as_ref().expect("Message is missing"); + + // Retain only transactions that do **not** contain a vote instruction + !message.account_keys.iter().any(|v| v == &VOTE_INSTRUCTION) + }); + + Ok(block) +} diff --git a/blocks/solana-parquet/src/counters.rs b/blocks/solana-parquet/src/counters.rs new file mode 100644 index 0000000..6f569a6 --- /dev/null +++ b/blocks/solana-parquet/src/counters.rs @@ -0,0 +1,50 @@ +use crate::structs::BlockCounters; + +use substreams_solana::pb::sf::solana::r#type::v1::Block; + +use crate::utils::VOTE_INSTRUCTION; + +pub fn get_block_counters(block: &Block) -> BlockCounters { + // Counters + let total_transactions = block.transactions.len(); + let mut successful_transactions = 0; + let mut total_vote_transactions = 0; + let mut successful_vote_transactions = 0; + + for trx in block.transactions.iter() { + let transaction = trx.transaction.as_ref().expect("Transaction is missing"); + + let message = transaction.message.as_ref().expect("Transaction message is missing"); + + let is_vote = message.account_keys.iter().any(|key| key == &VOTE_INSTRUCTION); + + if is_vote { + total_vote_transactions += 1; + } + + if trx.meta.as_ref().map_or(false, |meta| meta.err.is_none()) { + successful_transactions += 1; + if is_vote { + successful_vote_transactions += 1; + } + } + } + + let failed_transactions = total_transactions - successful_transactions; + let total_non_vote_transactions = total_transactions - total_vote_transactions; + let successful_non_vote_transactions = successful_transactions - successful_vote_transactions; + let failed_vote_transactions = total_vote_transactions - successful_vote_transactions; + let failed_non_vote_transactions = failed_transactions - failed_vote_transactions; + + BlockCounters { + total_transactions: total_transactions as u64, + successful_transactions: successful_transactions as u64, + failed_transactions: failed_transactions as u64, + total_vote_transactions: total_vote_transactions as u64, + successful_vote_transactions: successful_vote_transactions as u64, + total_non_vote_transactions: total_non_vote_transactions as u64, + successful_non_vote_transactions: successful_non_vote_transactions as u64, + failed_vote_transactions: failed_vote_transactions as u64, + failed_non_vote_transactions: failed_non_vote_transactions as u64, + } +} diff --git a/blocks/solana-parquet/src/instruction_calls.rs b/blocks/solana-parquet/src/instruction_calls.rs new file mode 100644 index 0000000..1ac3e37 --- /dev/null +++ b/blocks/solana-parquet/src/instruction_calls.rs @@ -0,0 +1,123 @@ +use common::utils::bytes_to_hex; +use substreams_solana::{base58, block_view::InstructionView, pb::sf::solana::r#type::v1::Block}; + +use crate::{ + pb::solana::rawblocks::InstructionCall, + structs::{BlockInfo, BlockTimestamp}, +}; + +pub fn collect_instruction_calls(block: &Block, timestamp: &BlockTimestamp, block_info: &BlockInfo) -> Vec { + let mut vec: Vec = vec![]; + + for (index, transaction) in block.transactions.iter().enumerate() { + let message = transaction.transaction.as_ref().expect("Transaction is missing").message.as_ref().expect("Message is missing"); + let signer = base58::encode(message.account_keys.first().expect("Signer is missing")); + + let tx_info = TxInfo { + tx_id: transaction.id().to_string(), + tx_index: index as u32, + tx_signer: signer, + tx_success: transaction.meta.as_ref().unwrap().err.is_none(), + log_messages: transaction.meta.as_ref().unwrap().log_messages.clone(), + }; + + for (instruction_index, instruction_view) in transaction.walk_instructions().enumerate() { + if !instruction_view.is_root() { + continue; + } + + collect_outer_instruction(&mut vec, timestamp, block_info, &tx_info, instruction_index, &instruction_view); + collect_inner_instructions(&mut vec, timestamp, block_info, &tx_info, instruction_index, &instruction_view); + } + } + + vec +} + +fn collect_outer_instruction(vec: &mut Vec, timestamp: &BlockTimestamp, block_info: &BlockInfo, tx_info: &TxInfo, instruction_index: usize, instruction_view: &InstructionView) { + let executing_account = base58::encode(instruction_view.program_id()); + // let account_arguments = to_string_array_to_string(&instruction_view.accounts()); + let account_arguments = instruction_view.accounts().iter().map(|arg| arg.to_string()).collect(); + let data = bytes_to_hex(&instruction_view.data()); + + let inner_instructions_str = build_inner_instructions_str(instruction_view); + + vec.push(InstructionCall { + block_time: Some(timestamp.time), + block_hash: timestamp.hash.clone(), + block_date: timestamp.date.clone(), + block_slot: block_info.slot, + block_height: block_info.height, + block_previous_block_hash: block_info.previous_block_hash.clone(), + block_parent_slot: block_info.parent_slot, + tx_id: tx_info.tx_id.clone(), + tx_index: tx_info.tx_index, + tx_signer: tx_info.tx_signer.to_string(), + tx_success: tx_info.tx_success, + log_messages: tx_info.log_messages.clone(), + outer_instruction_index: instruction_index as u32, + inner_instruction_index: -1, + inner_executing_account: "".to_string(), + outer_executing_account: executing_account.clone(), + executing_account, + is_inner: false, + data, + account_arguments: account_arguments, + inner_instructions: inner_instructions_str, + }); +} + +fn collect_inner_instructions(vec: &mut Vec, timestamp: &BlockTimestamp, block_info: &BlockInfo, tx_info: &TxInfo, instruction_index: usize, instruction_view: &InstructionView) { + for (inner_index, inner_instruction) in instruction_view.inner_instructions().enumerate() { + let inner_data = bytes_to_hex(inner_instruction.data()); + let executing_account = inner_instruction.program_id().to_string(); + let account_arguments = inner_instruction.accounts().iter().map(|arg| arg.to_string()).collect(); + + vec.push(InstructionCall { + block_time: Some(timestamp.time), + block_hash: timestamp.hash.clone(), + block_date: timestamp.date.clone(), + block_slot: block_info.slot, + block_height: block_info.height, + block_previous_block_hash: block_info.previous_block_hash.clone(), + block_parent_slot: block_info.parent_slot, + tx_id: tx_info.tx_id.clone(), + tx_index: tx_info.tx_index, + tx_signer: tx_info.tx_signer.to_string(), + tx_success: tx_info.tx_success, + log_messages: tx_info.log_messages.clone(), + outer_instruction_index: instruction_index as u32, + inner_instruction_index: inner_index as i32, + inner_executing_account: executing_account.clone(), + outer_executing_account: instruction_view.program_id().to_string(), + executing_account, + is_inner: true, + data: inner_data, + account_arguments, + inner_instructions: "".to_string(), + }); + } +} + +fn build_inner_instructions_str(instruction_view: &InstructionView) -> String { + let inner_instructions: Vec<(String, String, Vec)> = instruction_view + .inner_instructions() + .map(|inner_instruction| { + ( + base58::encode(inner_instruction.data()), + inner_instruction.program_id().to_string(), + inner_instruction.accounts().iter().map(|arg| arg.to_string()).collect(), + ) + }) + .collect(); + + serde_json::to_string(&inner_instructions).expect("Failed to serialize inner instructions") +} + +pub struct TxInfo { + pub tx_id: String, + pub tx_index: u32, + pub tx_signer: String, + pub tx_success: bool, + pub log_messages: Vec, +} diff --git a/blocks/solana-parquet/src/lib.rs b/blocks/solana-parquet/src/lib.rs new file mode 100644 index 0000000..b227b9b --- /dev/null +++ b/blocks/solana-parquet/src/lib.rs @@ -0,0 +1,12 @@ +mod account_activity; +mod blocks; +mod blocks_without_votes_all; +mod counters; +mod instruction_calls; +mod pb; +mod rewards; +mod sinks; +mod structs; +mod transactions; +mod tx_errors; +mod utils; diff --git a/blocks/solana-parquet/src/pb/mod.rs b/blocks/solana-parquet/src/pb/mod.rs new file mode 100644 index 0000000..2d7fb7c --- /dev/null +++ b/blocks/solana-parquet/src/pb/mod.rs @@ -0,0 +1,35 @@ +// @generated +pub mod sf { + pub mod solana { + pub mod r#type { + // @@protoc_insertion_point(attribute:sf.solana.type.v1) + pub mod v1 { + include!("sf.solana.type.v1.rs"); + // @@protoc_insertion_point(sf.solana.type.v1) + } + } + } +} +pub mod sol { + pub mod instructions { + // @@protoc_insertion_point(attribute:sol.instructions.v1) + pub mod v1 { + include!("sol.instructions.v1.rs"); + // @@protoc_insertion_point(sol.instructions.v1) + } + } + pub mod transactions { + // @@protoc_insertion_point(attribute:sol.transactions.v1) + pub mod v1 { + include!("sol.transactions.v1.rs"); + // @@protoc_insertion_point(sol.transactions.v1) + } + } +} +pub mod solana { + // @@protoc_insertion_point(attribute:solana.rawblocks) + pub mod rawblocks { + include!("solana.rawblocks.rs"); + // @@protoc_insertion_point(solana.rawblocks) + } +} diff --git a/blocks/solana-parquet/src/pb/sf.solana.type.v1.rs b/blocks/solana-parquet/src/pb/sf.solana.type.v1.rs new file mode 100644 index 0000000..6ab07ec --- /dev/null +++ b/blocks/solana-parquet/src/pb/sf.solana.type.v1.rs @@ -0,0 +1,257 @@ +// @generated +// This file is @generated by prost-build. +/// This Block is backwards compatible with solana.storage.ConfirmedBlock.ConfirmedBlock from +/// the Solana Labs repositories. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Block { + #[prost(string, tag="1")] + pub previous_blockhash: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub blockhash: ::prost::alloc::string::String, + #[prost(uint64, tag="3")] + pub parent_slot: u64, + #[prost(message, repeated, tag="4")] + pub transactions: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub rewards: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="6")] + pub block_time: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub block_height: ::core::option::Option, + /// StreamingFast additions + #[prost(uint64, tag="20")] + pub slot: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ConfirmedTransaction { + #[prost(message, optional, tag="1")] + pub transaction: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub meta: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Transaction { + #[prost(bytes="vec", repeated, tag="1")] + pub signatures: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(message, optional, tag="2")] + pub message: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Message { + #[prost(message, optional, tag="1")] + pub header: ::core::option::Option, + #[prost(bytes="vec", repeated, tag="2")] + pub account_keys: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", tag="3")] + pub recent_blockhash: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub instructions: ::prost::alloc::vec::Vec, + #[prost(bool, tag="5")] + pub versioned: bool, + #[prost(message, repeated, tag="6")] + pub address_table_lookups: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct MessageHeader { + #[prost(uint32, tag="1")] + pub num_required_signatures: u32, + #[prost(uint32, tag="2")] + pub num_readonly_signed_accounts: u32, + #[prost(uint32, tag="3")] + pub num_readonly_unsigned_accounts: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MessageAddressTableLookup { + #[prost(bytes="vec", tag="1")] + pub account_key: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="2")] + pub writable_indexes: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="3")] + pub readonly_indexes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionStatusMeta { + #[prost(message, optional, tag="1")] + pub err: ::core::option::Option, + #[prost(uint64, tag="2")] + pub fee: u64, + #[prost(uint64, repeated, tag="3")] + pub pre_balances: ::prost::alloc::vec::Vec, + #[prost(uint64, repeated, tag="4")] + pub post_balances: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub inner_instructions: ::prost::alloc::vec::Vec, + #[prost(bool, tag="10")] + pub inner_instructions_none: bool, + #[prost(string, repeated, tag="6")] + pub log_messages: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bool, tag="11")] + pub log_messages_none: bool, + #[prost(message, repeated, tag="7")] + pub pre_token_balances: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="8")] + pub post_token_balances: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="9")] + pub rewards: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", repeated, tag="12")] + pub loaded_writable_addresses: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", repeated, tag="13")] + pub loaded_readonly_addresses: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(message, optional, tag="14")] + pub return_data: ::core::option::Option, + #[prost(bool, tag="15")] + pub return_data_none: bool, + /// Sum of compute units consumed by all instructions. + /// Available since Solana v1.10.35 / v1.11.6. + /// Set to `None` for txs executed on earlier versions. + #[prost(uint64, optional, tag="16")] + pub compute_units_consumed: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionError { + #[prost(bytes="vec", tag="1")] + pub err: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InnerInstructions { + #[prost(uint32, tag="1")] + pub index: u32, + #[prost(message, repeated, tag="2")] + pub instructions: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InnerInstruction { + #[prost(uint32, tag="1")] + pub program_id_index: u32, + #[prost(bytes="vec", tag="2")] + pub accounts: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="3")] + pub data: ::prost::alloc::vec::Vec, + /// Invocation stack height of an inner instruction. + /// Available since Solana v1.14.6 + /// Set to `None` for txs executed on earlier versions. + #[prost(uint32, optional, tag="4")] + pub stack_height: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CompiledInstruction { + #[prost(uint32, tag="1")] + pub program_id_index: u32, + #[prost(bytes="vec", tag="2")] + pub accounts: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="3")] + pub data: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TokenBalance { + #[prost(uint32, tag="1")] + pub account_index: u32, + #[prost(string, tag="2")] + pub mint: ::prost::alloc::string::String, + #[prost(message, optional, tag="3")] + pub ui_token_amount: ::core::option::Option, + #[prost(string, tag="4")] + pub owner: ::prost::alloc::string::String, + #[prost(string, tag="5")] + pub program_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UiTokenAmount { + #[prost(double, tag="1")] + pub ui_amount: f64, + #[prost(uint32, tag="2")] + pub decimals: u32, + #[prost(string, tag="3")] + pub amount: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub ui_amount_string: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReturnData { + #[prost(bytes="vec", tag="1")] + pub program_id: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="2")] + pub data: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Reward { + #[prost(string, tag="1")] + pub pubkey: ::prost::alloc::string::String, + #[prost(int64, tag="2")] + pub lamports: i64, + #[prost(uint64, tag="3")] + pub post_balance: u64, + #[prost(enumeration="RewardType", tag="4")] + pub reward_type: i32, + #[prost(string, tag="5")] + pub commission: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Rewards { + #[prost(message, repeated, tag="1")] + pub rewards: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct UnixTimestamp { + #[prost(int64, tag="1")] + pub timestamp: i64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BlockHeight { + #[prost(uint64, tag="1")] + pub block_height: u64, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum RewardType { + Unspecified = 0, + Fee = 1, + Rent = 2, + Staking = 3, + Voting = 4, +} +impl RewardType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + RewardType::Unspecified => "Unspecified", + RewardType::Fee => "Fee", + RewardType::Rent => "Rent", + RewardType::Staking => "Staking", + RewardType::Voting => "Voting", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "Unspecified" => Some(Self::Unspecified), + "Fee" => Some(Self::Fee), + "Rent" => Some(Self::Rent), + "Staking" => Some(Self::Staking), + "Voting" => Some(Self::Voting), + _ => None, + } + } +} +// @@protoc_insertion_point(module) diff --git a/blocks/solana-parquet/src/pb/sol.instructions.v1.rs b/blocks/solana-parquet/src/pb/sol.instructions.v1.rs new file mode 100644 index 0000000..3f62421 --- /dev/null +++ b/blocks/solana-parquet/src/pb/sol.instructions.v1.rs @@ -0,0 +1,21 @@ +// @generated +// This file is @generated by prost-build. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Instructions { + #[prost(message, repeated, tag="1")] + pub instructions: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Instruction { + #[prost(string, tag="1")] + pub program_id: ::prost::alloc::string::String, + #[prost(string, repeated, tag="2")] + pub accounts: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bytes="vec", tag="3")] + pub data: ::prost::alloc::vec::Vec, + #[prost(string, tag="4")] + pub tx_hash: ::prost::alloc::string::String, +} +// @@protoc_insertion_point(module) diff --git a/blocks/solana-parquet/src/pb/sol.transactions.v1.rs b/blocks/solana-parquet/src/pb/sol.transactions.v1.rs new file mode 100644 index 0000000..49d0ddd --- /dev/null +++ b/blocks/solana-parquet/src/pb/sol.transactions.v1.rs @@ -0,0 +1,9 @@ +// @generated +// This file is @generated by prost-build. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Transactions { + #[prost(message, repeated, tag="1")] + pub transactions: ::prost::alloc::vec::Vec, +} +// @@protoc_insertion_point(module) diff --git a/blocks/solana-parquet/src/pb/solana.rawblocks.rs b/blocks/solana-parquet/src/pb/solana.rawblocks.rs new file mode 100644 index 0000000..5950a4e --- /dev/null +++ b/blocks/solana-parquet/src/pb/solana.rawblocks.rs @@ -0,0 +1,249 @@ +// @generated +// This file is @generated by prost-build. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Events { + #[prost(message, repeated, tag="1")] + pub blocks: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub rewards: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="3")] + pub transactions: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub instruction_calls: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub account_activity: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="6")] + pub vote_transactions: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="7")] + pub vote_instruction_calls: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="8")] + pub vote_account_activity: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Block { + /// clock + #[prost(message, optional, tag="1")] + pub time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(string, tag="2")] + pub date: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub hash: ::prost::alloc::string::String, + /// block + #[prost(uint64, tag="4")] + pub slot: u64, + #[prost(uint64, tag="5")] + pub height: u64, + #[prost(string, tag="6")] + pub previous_block_hash: ::prost::alloc::string::String, + #[prost(uint64, tag="7")] + pub parent_slot: u64, + /// counters + #[prost(uint64, tag="8")] + pub total_transactions: u64, + #[prost(uint64, tag="9")] + pub successful_transactions: u64, + #[prost(uint64, tag="10")] + pub failed_transactions: u64, + #[prost(uint64, tag="11")] + pub total_vote_transactions: u64, + #[prost(uint64, tag="12")] + pub total_non_vote_transactions: u64, + #[prost(uint64, tag="13")] + pub successful_vote_transactions: u64, + #[prost(uint64, tag="14")] + pub successful_non_vote_transactions: u64, + #[prost(uint64, tag="15")] + pub failed_vote_transactions: u64, + #[prost(uint64, tag="16")] + pub failed_non_vote_transactions: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Reward { + /// clock + #[prost(message, optional, tag="1")] + pub block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(string, tag="2")] + pub block_date: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub block_hash: ::prost::alloc::string::String, + /// block + #[prost(uint64, tag="4")] + pub block_slot: u64, + #[prost(uint64, tag="5")] + pub block_height: u64, + #[prost(string, tag="6")] + pub block_previous_block_hash: ::prost::alloc::string::String, + #[prost(uint64, tag="7")] + pub block_parent_slot: u64, + /// reward + #[prost(string, tag="8")] + pub pubkey: ::prost::alloc::string::String, + #[prost(int64, tag="9")] + pub lamports: i64, + #[prost(uint64, tag="10")] + pub pre_balance: u64, + #[prost(uint64, tag="11")] + pub post_balance: u64, + #[prost(string, tag="12")] + pub reward_type: ::prost::alloc::string::String, + #[prost(string, tag="13")] + pub commission: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Transaction { + /// clock + #[prost(message, optional, tag="1")] + pub block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(string, tag="2")] + pub block_hash: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub block_date: ::prost::alloc::string::String, + /// block + #[prost(uint64, tag="4")] + pub block_slot: u64, + #[prost(uint64, tag="5")] + pub block_height: u64, + #[prost(string, tag="6")] + pub block_previous_block_hash: ::prost::alloc::string::String, + #[prost(uint64, tag="7")] + pub block_parent_slot: u64, + /// transaction + #[prost(string, tag="8")] + pub id: ::prost::alloc::string::String, + #[prost(uint32, tag="9")] + pub index: u32, + #[prost(uint64, tag="10")] + pub fee: u64, + #[prost(uint32, tag="11")] + pub required_signatures: u32, + #[prost(uint32, tag="12")] + pub required_signed_accounts: u32, + #[prost(uint32, tag="13")] + pub required_unsigned_accounts: u32, + #[prost(string, tag="14")] + pub signature: ::prost::alloc::string::String, + #[prost(bool, tag="15")] + pub success: bool, + #[prost(string, tag="16")] + pub error: ::prost::alloc::string::String, + #[prost(string, tag="17")] + pub recent_block_hash: ::prost::alloc::string::String, + #[prost(string, repeated, tag="18")] + pub account_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, tag="19")] + pub log_messages: ::prost::alloc::string::String, + #[prost(string, repeated, tag="20")] + pub pre_balances: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="21")] + pub post_balances: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="22")] + pub signatures: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, tag="23")] + pub signer: ::prost::alloc::string::String, + #[prost(string, repeated, tag="24")] + pub signers: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InstructionCall { + /// clock + #[prost(message, optional, tag="1")] + pub block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(string, tag="2")] + pub block_hash: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub block_date: ::prost::alloc::string::String, + /// block + #[prost(uint64, tag="4")] + pub block_slot: u64, + #[prost(uint64, tag="5")] + pub block_height: u64, + #[prost(string, tag="6")] + pub block_previous_block_hash: ::prost::alloc::string::String, + #[prost(uint64, tag="7")] + pub block_parent_slot: u64, + /// transaction + #[prost(string, tag="8")] + pub tx_id: ::prost::alloc::string::String, + #[prost(uint32, tag="9")] + pub tx_index: u32, + #[prost(string, tag="10")] + pub tx_signer: ::prost::alloc::string::String, + #[prost(bool, tag="11")] + pub tx_success: bool, + #[prost(string, repeated, tag="12")] + pub log_messages: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// instruction + #[prost(uint32, tag="13")] + pub outer_instruction_index: u32, + #[prost(int32, tag="14")] + pub inner_instruction_index: i32, + #[prost(string, tag="15")] + pub inner_executing_account: ::prost::alloc::string::String, + #[prost(string, tag="16")] + pub outer_executing_account: ::prost::alloc::string::String, + #[prost(string, tag="17")] + pub executing_account: ::prost::alloc::string::String, + #[prost(bool, tag="18")] + pub is_inner: bool, + #[prost(string, tag="19")] + pub data: ::prost::alloc::string::String, + #[prost(string, repeated, tag="20")] + pub account_arguments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// string representing a nested array. Switch to nested array type when supported by sink-files + #[prost(string, tag="21")] + pub inner_instructions: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AccountActivity { + /// clock + #[prost(message, optional, tag="1")] + pub block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(string, tag="2")] + pub block_hash: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub block_date: ::prost::alloc::string::String, + /// block + #[prost(uint64, tag="4")] + pub block_slot: u64, + #[prost(uint64, tag="5")] + pub block_height: u64, + #[prost(string, tag="6")] + pub block_previous_block_hash: ::prost::alloc::string::String, + #[prost(uint64, tag="7")] + pub block_parent_slot: u64, + #[prost(string, tag="8")] + pub address: ::prost::alloc::string::String, + #[prost(uint32, tag="9")] + pub tx_index: u32, + #[prost(string, tag="10")] + pub tx_id: ::prost::alloc::string::String, + #[prost(bool, tag="11")] + pub tx_success: bool, + #[prost(bool, tag="12")] + pub signed: bool, + #[prost(bool, tag="13")] + pub writable: bool, + #[prost(string, optional, tag="14")] + pub token_mint_address: ::core::option::Option<::prost::alloc::string::String>, + #[prost(uint64, tag="15")] + pub pre_balance: u64, + #[prost(uint64, tag="16")] + pub post_balance: u64, + #[prost(int64, tag="17")] + pub balance_change: i64, + #[prost(double, optional, tag="18")] + pub pre_token_balance: ::core::option::Option, + #[prost(double, optional, tag="19")] + pub post_token_balance: ::core::option::Option, + #[prost(double, optional, tag="20")] + pub token_balance_change: ::core::option::Option, + #[prost(string, optional, tag="21")] + pub token_balance_owner: ::core::option::Option<::prost::alloc::string::String>, +} +// @@protoc_insertion_point(module) diff --git a/blocks/solana-parquet/src/rewards.rs b/blocks/solana-parquet/src/rewards.rs new file mode 100644 index 0000000..a8e1b3a --- /dev/null +++ b/blocks/solana-parquet/src/rewards.rs @@ -0,0 +1,43 @@ +use substreams_solana::pb::sf::solana::r#type::v1::Block; + +use crate::{ + pb::solana::rawblocks::Reward, + structs::{BlockInfo, BlockTimestamp}, +}; + +pub fn collect_rewards(block: &Block, timestamp: &BlockTimestamp, block_info: &BlockInfo) -> Vec { + let mut rewards = Vec::new(); + for reward in block.rewards.iter() { + let reward_type = reward_type(reward.reward_type); + let pre_balance = reward.post_balance as i128 - reward.lamports as i128; + + rewards.push(Reward { + block_slot: block.slot, + block_height: block_info.height, + block_previous_block_hash: block_info.previous_block_hash.clone(), + block_parent_slot: block_info.parent_slot, + block_time: Some(timestamp.time), + block_date: timestamp.date.clone(), + block_hash: timestamp.hash.clone(), + pubkey: reward.pubkey.clone(), + lamports: reward.lamports, + pre_balance: pre_balance as u64, + post_balance: reward.post_balance, + reward_type, + commission: reward.commission.clone(), + }); + } + + rewards +} + +pub fn reward_type(reward_type: i32) -> String { + match reward_type { + 0 => "Unspecified".to_string(), + 1 => "Fee".to_string(), + 2 => "Rent".to_string(), + 3 => "Staking".to_string(), + 4 => "Voting".to_string(), + _ => "Unknown".to_string(), + } +} diff --git a/blocks/solana-parquet/src/sinks.rs b/blocks/solana-parquet/src/sinks.rs new file mode 100644 index 0000000..7600177 --- /dev/null +++ b/blocks/solana-parquet/src/sinks.rs @@ -0,0 +1,55 @@ +use substreams::errors::Error; +use substreams::pb::substreams::Clock; +use substreams_solana::b58; +use substreams_solana::pb::sf::solana::r#type::v1::{Block, ConfirmedTransaction}; + +use crate::account_activity::collect_account_activities; +use crate::blocks::{collect_block, get_block_info}; +use crate::instruction_calls::collect_instruction_calls; +use crate::pb::solana::rawblocks::Events; +use crate::rewards::collect_rewards; +use crate::transactions::collect_transactions; +use crate::utils::get_timestamp_without_number; + +static VOTE_INSTRUCTION: [u8; 32] = b58!("Vote111111111111111111111111111111111111111"); + +#[substreams::handlers::map] +pub fn ch_out_without_votes(clock: Clock, block: Block) -> Result { + let timestamp = get_timestamp_without_number(&clock); + let block_info = get_block_info(&block); + + let (non_vote_trx, vote_trx): (Vec<(usize, &ConfirmedTransaction)>, Vec<(usize, &ConfirmedTransaction)>) = block.transactions.iter().enumerate().partition(|(_index, trx)| { + !trx.transaction + .as_ref() + .and_then(|t| t.message.as_ref()) + .map_or(false, |message| message.account_keys.iter().any(|key| key == &VOTE_INSTRUCTION)) + }); + + Ok(Events { + blocks: vec![collect_block(&block, ×tamp, &block_info).unwrap()], + rewards: collect_rewards(&block, ×tamp, &block_info), + transactions: collect_transactions(&non_vote_trx, &block_info, ×tamp), + instruction_calls: collect_instruction_calls(&block, ×tamp, &block_info), + account_activity: collect_account_activities(&block_info, ×tamp, &non_vote_trx), + vote_transactions: collect_transactions(&vote_trx, &block_info, ×tamp), + vote_instruction_calls: collect_instruction_calls(&block, ×tamp, &block_info), + vote_account_activity: collect_account_activities(&block_info, ×tamp, &vote_trx), + }) +} + +#[substreams::handlers::map] +pub fn ch_out(clock: Clock, block: Block) -> Result { + let timestamp = get_timestamp_without_number(&clock); + let block_info = get_block_info(&block); + + Ok(Events { + blocks: vec![collect_block(&block, ×tamp, &block_info).unwrap()], + rewards: collect_rewards(&block, ×tamp, &block_info), + transactions: vec![], + instruction_calls: vec![], + account_activity: vec![], + vote_transactions: vec![], + vote_instruction_calls: vec![], + vote_account_activity: vec![], + }) +} diff --git a/blocks/solana-parquet/src/structs.rs b/blocks/solana-parquet/src/structs.rs new file mode 100644 index 0000000..a7b8337 --- /dev/null +++ b/blocks/solana-parquet/src/structs.rs @@ -0,0 +1,42 @@ +use prost_types::Timestamp; + +#[derive(Default)] +pub struct BlockCounters { + pub total_transactions: u64, + pub successful_transactions: u64, + pub failed_transactions: u64, + pub total_vote_transactions: u64, + pub total_non_vote_transactions: u64, + pub successful_vote_transactions: u64, + pub successful_non_vote_transactions: u64, + pub failed_vote_transactions: u64, + pub failed_non_vote_transactions: u64, +} + +#[derive(Default)] +pub struct BlockInfo { + pub slot: u64, + pub height: u64, + pub previous_block_hash: String, + pub parent_slot: u64, +} + +#[derive(Default)] +pub struct BlockTimestamp { + pub time: Timestamp, + pub date: String, + pub hash: String, +} + +#[derive(Clone)] +pub struct Reward { + pub block_time: String, + pub block_date: String, + pub block_hash: String, + pub pubkey: String, + pub lamports: i64, + pub pre_balance: i128, + pub post_balance: u64, + pub reward_type: String, + pub commission: String, +} diff --git a/blocks/solana-parquet/src/transactions.rs b/blocks/solana-parquet/src/transactions.rs new file mode 100644 index 0000000..5da8bca --- /dev/null +++ b/blocks/solana-parquet/src/transactions.rs @@ -0,0 +1,64 @@ +use common::utils::string_array_to_string_with_escapes; +use substreams_solana::{base58, pb::sf::solana::r#type::v1::ConfirmedTransaction}; + +use crate::{ + pb::solana::rawblocks::Transaction as RawTransaction, + structs::{BlockInfo, BlockTimestamp}, + tx_errors::TransactionErrorDecoder, + utils::get_account_keys_extended, +}; + +pub fn collect_transactions(transactions: &Vec<(usize, &ConfirmedTransaction)>, block_info: &BlockInfo, timestamp: &BlockTimestamp) -> Vec { + let mut trx_vec: Vec = Vec::new(); + + for (index, transaction) in transactions { + let meta = transaction.meta.as_ref().expect("Transaction meta is missing"); + let trx = transaction.transaction.as_ref().expect("Transaction is missing"); + let message = trx.message.as_ref().expect("Transaction message is missing"); + let header = message.header.as_ref().expect("Transaction header is missing"); + + let account_keys = get_account_keys_extended(transaction); + let success = meta.err.is_none(); + let err = match &meta.err { + Some(err) => decode_transaction_error(&err.err), + None => String::new(), + }; + + trx_vec.push(RawTransaction { + block_time: Some(timestamp.time), + block_hash: timestamp.hash.clone(), + block_date: timestamp.date.clone(), + block_slot: block_info.slot, + block_height: block_info.height, + block_previous_block_hash: block_info.previous_block_hash.clone(), + block_parent_slot: block_info.parent_slot, + id: transaction.id(), + index: *index as u32, + fee: meta.fee, + required_signatures: header.num_required_signatures, + required_signed_accounts: header.num_readonly_signed_accounts, + required_unsigned_accounts: header.num_readonly_unsigned_accounts, + signature: transaction.id(), + success, + error: err, + recent_block_hash: base58::encode(&message.recent_blockhash), + account_keys, + log_messages: string_array_to_string_with_escapes(&meta.log_messages), + // TODO: output as uint array when sink-files supports it + pre_balances: meta.pre_balances.iter().map(|balance| balance.to_string()).collect(), + post_balances: meta.post_balances.iter().map(|balance| balance.to_string()).collect(), + signatures: trx.signatures.iter().map(base58::encode).collect(), + signer: message.account_keys.iter().take(trx.signatures.len()).map(|key| base58::encode(key)).next().unwrap(), + signers: message.account_keys.iter().take(trx.signatures.len()).map(|key| base58::encode(key)).collect(), + }); + } + + trx_vec +} + +pub fn decode_transaction_error(err: &Vec) -> String { + match TransactionErrorDecoder::decode_error(err) { + Ok(decoded_error) => TransactionErrorDecoder::format_error(&decoded_error), + Err(decode_error) => format!("Error decoding transaction error: {:?}", decode_error), + } +} diff --git a/blocks/solana-parquet/src/tx_errors.rs b/blocks/solana-parquet/src/tx_errors.rs new file mode 100644 index 0000000..3d484a2 --- /dev/null +++ b/blocks/solana-parquet/src/tx_errors.rs @@ -0,0 +1,260 @@ +#[derive(Debug)] +#[allow(dead_code)] +pub enum TransactionErrorDecodeError { + EmptyError, + UnknownErrorCode(u8), + InvalidInstructionError, + BufferTooShort, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TransactionError { + AccountInUse, + AccountLoadedTwice, + AccountNotFound, + ProgramAccountNotFound, + InsufficientFundsForFee, + InvalidAccountForFee, + AlreadyProcessed, + BlockhashNotFound, + InstructionError(u32, InstructionError), + CallChainTooDeep, + MissingSignatureForFee, + InvalidAccountIndex, + SignatureFailure, + InvalidProgramForExecution, + SanitizeFailure, + ClusterMaintenance, + AccountBorrowOutstanding, + WouldExceedMaxBlockCostLimit, + UnsupportedVersion, + InvalidWritableAccount, + WouldExceedMaxAccountCostLimit, + WouldExceedAccountDataBlockLimit, + TooManyAccountLocks, + AddressLookupTableNotFound, + InvalidAddressLookupTableOwner, + InvalidAddressLookupTableData, + InvalidAddressLookupTableIndex, + InvalidRentPayingAccount, + WouldExceedMaxVoteCostLimit, + WouldExceedAccountDataTotalLimit, + DuplicateInstruction { instruction_index: u32 }, + InsufficientFundsForRent { account_index: u32 }, + MaxLoadedAccountsDataSizeExceeded, + InvalidLoadedAccountsDataSizeLimit, + ResanitizationNeeded, + ProgramExecutionTemporarilyRestricted { account_index: u32 }, + UnbalancedTransaction, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum InstructionError { + GenericError, + InvalidArgument, + InvalidInstructionData, + InvalidAccountData, + AccountDataTooSmall, + InsufficientFunds, + IncorrectProgramId, + MissingRequiredSignature, + AccountAlreadyInitialized, + UninitializedAccount, + UnbalancedInstruction, + ModifiedProgramId, + ExternalAccountLamportSpend, + ExternalAccountDataModified, + ReadonlyLamportChange, + ReadonlyDataModified, + DuplicateAccountIndex, + ExecutableModified, + RentEpochModified, + NotEnoughAccountKeys, + AccountDataSizeChanged, + AccountNotExecutable, + AccountBorrowFailed, + AccountBorrowOutstanding, + DuplicateAccountOutOfSync, + Custom(u32), + InvalidError, + ExecutableDataModified, + ExecutableLamportChange, + ExecutableAccountNotRentExempt, + UnsupportedProgramId, + CallDepth, + MissingAccount, + ReentrancyNotAllowed, + MaxSeedLengthExceeded, + InvalidSeeds, + InvalidRealloc, + ComputationalBudgetExceeded, + PrivilegeEscalation, + ProgramEnvironmentSetupFailure, + ProgramFailedToComplete, + ProgramFailedToCompile, + Immutable, + IncorrectAuthority, + BorshIoError, + AccountNotRentExempt, + InvalidAccountOwner, + ArithmeticOverflow, + UnsupportedSysvar, + IllegalOwner, + MaxAccountsDataAllocationsExceeded, + MaxAccountsExceeded, + MaxInstructionTraceLengthExceeded, + BuiltinProgramsMustConsumeComputeUnits, +} + +impl InstructionError { + fn from_u32(value: u32) -> Self { + match value { + 0 => InstructionError::GenericError, + 1 => InstructionError::InvalidArgument, + 2 => InstructionError::InvalidInstructionData, + 3 => InstructionError::InvalidAccountData, + 4 => InstructionError::AccountDataTooSmall, + 5 => InstructionError::InsufficientFunds, + 6 => InstructionError::IncorrectProgramId, + 7 => InstructionError::MissingRequiredSignature, + 8 => InstructionError::AccountAlreadyInitialized, + 9 => InstructionError::UninitializedAccount, + 10 => InstructionError::UnbalancedInstruction, + 11 => InstructionError::ModifiedProgramId, + 12 => InstructionError::ExternalAccountLamportSpend, + 13 => InstructionError::ExternalAccountDataModified, + 14 => InstructionError::ReadonlyLamportChange, + 15 => InstructionError::ReadonlyDataModified, + 16 => InstructionError::DuplicateAccountIndex, + 17 => InstructionError::ExecutableModified, + 18 => InstructionError::RentEpochModified, + 19 => InstructionError::NotEnoughAccountKeys, + 20 => InstructionError::AccountDataSizeChanged, + 21 => InstructionError::AccountNotExecutable, + 22 => InstructionError::AccountBorrowFailed, + 23 => InstructionError::AccountBorrowOutstanding, + 24 => InstructionError::DuplicateAccountOutOfSync, + 25 => InstructionError::InvalidError, + 26 => InstructionError::ExecutableDataModified, + 27 => InstructionError::ExecutableLamportChange, + 28 => InstructionError::ExecutableAccountNotRentExempt, + 29 => InstructionError::UnsupportedProgramId, + 30 => InstructionError::CallDepth, + 31 => InstructionError::MissingAccount, + 32 => InstructionError::ReentrancyNotAllowed, + 33 => InstructionError::MaxSeedLengthExceeded, + 34 => InstructionError::InvalidSeeds, + 35 => InstructionError::InvalidRealloc, + 36 => InstructionError::ComputationalBudgetExceeded, + 37 => InstructionError::PrivilegeEscalation, + 38 => InstructionError::ProgramEnvironmentSetupFailure, + 39 => InstructionError::ProgramFailedToComplete, + 40 => InstructionError::ProgramFailedToCompile, + 41 => InstructionError::Immutable, + 42 => InstructionError::IncorrectAuthority, + 43 => InstructionError::BorshIoError, + 44 => InstructionError::AccountNotRentExempt, + 45 => InstructionError::InvalidAccountOwner, + 46 => InstructionError::ArithmeticOverflow, + 47 => InstructionError::UnsupportedSysvar, + 48 => InstructionError::IllegalOwner, + 49 => InstructionError::MaxAccountsDataAllocationsExceeded, + 50 => InstructionError::MaxAccountsExceeded, + 51 => InstructionError::MaxInstructionTraceLengthExceeded, + 52 => InstructionError::BuiltinProgramsMustConsumeComputeUnits, + _ => InstructionError::Custom(value as u32), + } + } +} + +pub struct TransactionErrorDecoder; + +impl TransactionErrorDecoder { + /// Decode a transaction error from Vec + pub fn decode_error(error_data: &[u8]) -> Result { + if error_data.is_empty() { + return Err(TransactionErrorDecodeError::EmptyError); + } + + let error_code = error_data[0]; + match error_code { + 0 => Ok(TransactionError::AccountInUse), + 1 => Ok(TransactionError::AccountLoadedTwice), + 2 => Ok(TransactionError::AccountNotFound), + 3 => Ok(TransactionError::ProgramAccountNotFound), + 4 => Ok(TransactionError::InsufficientFundsForFee), + 5 => Ok(TransactionError::InvalidAccountForFee), + 6 => Ok(TransactionError::AlreadyProcessed), + 7 => Ok(TransactionError::BlockhashNotFound), + 8 => { + let instruction_index = Self::extract_index(error_data)?; + let error_number = u32::from_le_bytes(error_data[error_data.len() - 4..].try_into().unwrap()); + Ok(TransactionError::InstructionError(instruction_index, InstructionError::from_u32(error_number))) + } + 9 => Ok(TransactionError::CallChainTooDeep), + 10 => Ok(TransactionError::MissingSignatureForFee), + 11 => Ok(TransactionError::InvalidAccountIndex), + 12 => Ok(TransactionError::SignatureFailure), + 13 => Ok(TransactionError::InvalidProgramForExecution), + 14 => Ok(TransactionError::SanitizeFailure), + 15 => Ok(TransactionError::ClusterMaintenance), + 16 => Ok(TransactionError::AccountBorrowOutstanding), + 17 => Ok(TransactionError::WouldExceedMaxBlockCostLimit), + 18 => Ok(TransactionError::UnsupportedVersion), + 19 => Ok(TransactionError::InvalidWritableAccount), + 20 => Ok(TransactionError::WouldExceedMaxAccountCostLimit), + 21 => Ok(TransactionError::WouldExceedAccountDataBlockLimit), + 22 => Ok(TransactionError::TooManyAccountLocks), + 23 => Ok(TransactionError::AddressLookupTableNotFound), + 24 => Ok(TransactionError::InvalidAddressLookupTableOwner), + 25 => Ok(TransactionError::InvalidAddressLookupTableData), + 26 => Ok(TransactionError::InvalidAddressLookupTableIndex), + 27 => Ok(TransactionError::InvalidRentPayingAccount), + 28 => Ok(TransactionError::WouldExceedMaxVoteCostLimit), + 29 => Ok(TransactionError::WouldExceedAccountDataTotalLimit), + 30 => Ok(TransactionError::DuplicateInstruction { + instruction_index: Self::extract_index(error_data)?, + }), + 31 => Ok(TransactionError::InsufficientFundsForRent { + account_index: Self::extract_index(error_data)?, + }), + 32 => Ok(TransactionError::MaxLoadedAccountsDataSizeExceeded), + 33 => Ok(TransactionError::InvalidLoadedAccountsDataSizeLimit), + 34 => Ok(TransactionError::ResanitizationNeeded), + 35 => Ok(TransactionError::ProgramExecutionTemporarilyRestricted { + account_index: Self::extract_index(error_data)?, + }), + 36 => Ok(TransactionError::UnbalancedTransaction), + _ => Err(TransactionErrorDecodeError::UnknownErrorCode(error_code)), + } + } + + fn extract_index(error_data: &[u8]) -> Result { + if error_data.len() < 5 { + return Err(TransactionErrorDecodeError::BufferTooShort); + } + Ok(u32::from_be_bytes(error_data[1..5].try_into().unwrap())) + } + + /// Format the error into a JSON-like string + pub fn format_error(error: &TransactionError) -> String { + match error { + TransactionError::InstructionError(idx, instruction_error) => match instruction_error { + InstructionError::Custom(code) => { + format!("{{\"InstructionError\":[{},{{\"Custom\":{}}}]}}", idx, code) + } + _ => format!("{{\"InstructionError\":[{},\"{:?}\"]}}", idx, instruction_error), + }, + TransactionError::DuplicateInstruction { instruction_index } => { + format!("{{\"DuplicateInstruction\":{}}}", instruction_index) + } + TransactionError::InsufficientFundsForRent { account_index } => { + format!("{{\"InsufficientFundsForRent\":{{\"account_index\":{}}}}}", account_index) + } + TransactionError::ProgramExecutionTemporarilyRestricted { account_index } => { + format!("{{\"ProgramExecutionTemporarilyRestricted\":{{\"account_index\":{}}}}}", account_index) + } + _ => format!("{:?}", error), + } + } +} diff --git a/blocks/solana-parquet/src/utils.rs b/blocks/solana-parquet/src/utils.rs new file mode 100644 index 0000000..feb0153 --- /dev/null +++ b/blocks/solana-parquet/src/utils.rs @@ -0,0 +1,110 @@ +use common::utils::{add_prefix_to_hex, block_time_to_date}; +use substreams::pb::substreams::Clock; +use substreams_database_change::pb::database::TableChange; +use substreams_solana::{b58, base58, pb::sf::solana::r#type::v1::ConfirmedTransaction}; + +use crate::structs::BlockTimestamp; + +pub static VOTE_INSTRUCTION: [u8; 32] = b58!("Vote111111111111111111111111111111111111111"); + +pub fn insert_timestamp_without_number(row: &mut TableChange, clock: &Clock, is_block: bool, with_prefix: bool) { + let timestamp = clock.clone().timestamp.unwrap(); + let block_date = block_time_to_date(timestamp.to_string().as_str()); + let seconds = timestamp.seconds; + let nanos = timestamp.nanos; + let milliseconds = seconds * 1000 + i64::from(nanos) / 1_000_000; + let block_time = milliseconds.to_string(); + let block_hash = if with_prefix { add_prefix_to_hex(&clock.id) } else { clock.id.to_string() }; + let prefix = if is_block { "" } else { "block_" }; + + row.change(format!("{}date", prefix).as_str(), ("", block_date.as_str())) + .change(format!("{}time", prefix).as_str(), ("", block_time.as_str())) + .change(format!("{}hash", prefix).as_str(), ("", block_hash.as_str())); +} + +pub fn get_timestamp_without_number(clock: &Clock) -> BlockTimestamp { + let timestamp = clock.clone().timestamp.unwrap(); + let block_date = block_time_to_date(timestamp.to_string().as_str()); + + BlockTimestamp { + time: clock.timestamp.expect("timestamp is required"), + date: block_date, + hash: clock.id.to_string(), + } +} + +pub fn build_csv_string(values: &[T]) -> String { + values.iter().map(|value| value.to_string()).collect::>().join(",") +} + +pub fn encode_byte_vectors_to_base58_string(values: &[Vec]) -> String { + let encoded_values: Vec = values.iter().map(|value| format!("\"{}\"", base58::encode(value))).collect(); + format!("[{}]", encoded_values.join(",")) +} + +// Get all encoded account keys including loaded writable and readonly addresses +pub fn get_account_keys_extended(transaction: &ConfirmedTransaction) -> Vec { + let message = transaction.transaction.as_ref().unwrap().message.as_ref().unwrap(); + let meta = transaction.meta.as_ref().unwrap(); + + message + .account_keys + .iter() + .chain(&meta.loaded_writable_addresses) + .chain(&meta.loaded_readonly_addresses) + .map(base58::encode) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encode_byte_vectors_multiple() { + let input = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8], vec![9, 8, 7, 6]]; + let expected = "[\"2VfUX\",\"8SxqM\",\"EPavZ\"]"; + let output = encode_byte_vectors_to_base58_string(&input); + assert_eq!(output, expected); + } + + #[test] + fn test_encode_0x1234() { + let input = vec![vec![0x12, 0x34]]; + let expected = "[\"2PM\"]"; + let output = encode_byte_vectors_to_base58_string(&input); + assert_eq!(output, expected); + } + + #[test] + fn test_encode_mixed_vectors() { + let input = vec![vec![1, 2, 3, 4], vec![0x12, 0x34], vec![5, 6, 7, 8]]; + let expected = "[\"2VfUX\",\"2PM\",\"8SxqM\"]"; + let output = encode_byte_vectors_to_base58_string(&input); + assert_eq!(output, expected); + } + + #[test] + fn test_encode_empty_vector() { + let input: Vec> = vec![]; + let expected = "[]"; + let output = encode_byte_vectors_to_base58_string(&input); + assert_eq!(output, expected); + } + + #[test] + fn test_encode_single_zero_byte() { + let input = vec![vec![0]]; + let expected = "[\"1\"]"; + let output = encode_byte_vectors_to_base58_string(&input); + assert_eq!(output, expected); + } + + #[test] + fn test_encode_leading_zeros() { + let input = vec![vec![0, 0, 1, 2, 3]]; + let expected = "[\"11Ldp\"]"; // '11' represents the two leading zeros + let output = encode_byte_vectors_to_base58_string(&input); + assert_eq!(output, expected); + } +} diff --git a/blocks/solana-parquet/substreams.yaml b/blocks/solana-parquet/substreams.yaml new file mode 100644 index 0000000..2e2a330 --- /dev/null +++ b/blocks/solana-parquet/substreams.yaml @@ -0,0 +1,49 @@ +specVersion: v0.1.0 +package: + name: raw_blocks_solana_parquet + version: v0.1.0 + image: logo.png + url: https://github.com/pinax-network/substreams-raw-blocks + +imports: + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v2.0.0/substreams-database-change-v2.0.0.spkg + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + common: https://github.com/streamingfast/substreams-foundational-modules/releases/download/solana-common-v0.3.0/solana-common-v0.3.0.spkg + +binaries: + default: + type: wasm/rust-v1 + file: ../../target/wasm32-unknown-unknown/release/raw_blocks_solana_parquet.wasm + +protobuf: + files: + - solana.rawblocks.proto + importPaths: + - ./proto + excludePaths: + - sf/substreams + - google + +modules: + - name: blocks_without_votes_all + kind: map + inputs: + - source: sf.solana.type.v1.Block + output: + type: proto:sf.solana.type.v1.Block + + - name: ch_out_without_votes + kind: map + inputs: + - source: sf.substreams.v1.Clock + - map: blocks_without_votes_all + # - map: common:blocks_without_votes + output: + type: proto:solana.rawblocks.Events + - name: ch_out + kind: map + inputs: + - source: sf.substreams.v1.Clock + - source: sf.solana.type.v1.Block + output: + type: proto:solana.rawblocks.Events diff --git a/blocks/solana/Cargo.toml b/blocks/solana/Cargo.toml index 1d50dc1..f26c4d1 100644 --- a/blocks/solana/Cargo.toml +++ b/blocks/solana/Cargo.toml @@ -11,4 +11,4 @@ common = { path = "../../common" } substreams-solana = { workspace = true } substreams-database-change = { workspace = true } substreams = { workspace = true } -serde_json = "1.0" +serde_json = { workspace = true } diff --git a/blocks/solana/Makefile b/blocks/solana/Makefile index f9d16fe..6612741 100644 --- a/blocks/solana/Makefile +++ b/blocks/solana/Makefile @@ -44,7 +44,7 @@ sql-setup: .PHONY: sql-run-solana sql-run-solana: - substreams-sink-sql run clickhouse://default:default@localhost:9000/solana substreams.yaml -e solana.substreams.pinax.network:443 203100000:203100100 --final-blocks-only --undo-buffer-size 1 --on-module-hash-mistmatch=warn --batch-block-flush-interval 1 --development-mode + substreams-sink-sql run clickhouse://default:default@localhost:9000/solana substreams.yaml -e solana.substreams.pinax.network:443 203100000:203100500 --final-blocks-only --undo-buffer-size 1 --on-module-hash-mistmatch=warn --batch-block-flush-interval 1 --development-mode .PHONY: sql-run-solana-blocks sql-run-solana-blocks: