diff --git a/web/explorer/Cargo.lock b/web/explorer/Cargo.lock --- a/web/explorer/Cargo.lock +++ b/web/explorer/Cargo.lock @@ -509,6 +509,7 @@ "maud", "num-format", "qrcode-generator", + "regex", "serde", "serde_json", "thiserror", @@ -1421,9 +1422,9 @@ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", diff --git a/web/explorer/explorer-server/Cargo.toml b/web/explorer/explorer-server/Cargo.toml --- a/web/explorer/explorer-server/Cargo.toml +++ b/web/explorer/explorer-server/Cargo.toml @@ -27,3 +27,4 @@ eyre = "0.6" axum = "0.5.7" tower-http = { version = "0.3.4", features = ["fs"] } +regex = "1.10.5" diff --git a/web/explorer/explorer-server/src/templating/filters.rs b/web/explorer/explorer-server/src/templating/filters.rs --- a/web/explorer/explorer-server/src/templating/filters.rs +++ b/web/explorer/explorer-server/src/templating/filters.rs @@ -7,6 +7,7 @@ use humansize::{file_size_opts as options, FileSize}; use maud::{html, PreEscaped}; use num_format::{Locale, ToFormattedString}; +use regex::bytes::Regex; use crate::blockchain; @@ -118,6 +119,66 @@ Ok(output.into_string()) } +pub fn render_miner(coinbase_data: &[u8]) -> askama::Result<String> { + // Pools with identifiable miners + + // ViaBTC + let reg_viabtc = + Regex::new(r"ViaBTC(.*/Mined by (?P<mined_by>\w+)/)?").unwrap(); + if let Some(captures) = reg_viabtc.captures(coinbase_data) { + if let Some(mined_by) = captures.name("mined_by") { + return Ok(format!( + "ViaBTC | Mined by {}", + String::from_utf8_lossy(mined_by.as_bytes()) + )); + }; + return Ok("ViaBTC".to_string()); + }; + + // CK Pool + // Note: CK Pool software is used by solo miners + // Parse as solo miners unless CK Pool is the only identifier + let reg_ckpool = + Regex::new(r"ckpool(.*/mined by (?P<mined_by>\w+)/)?").unwrap(); + if let Some(captures) = reg_ckpool.captures(coinbase_data) { + if let Some(mined_by) = captures.name("mined_by") { + return Ok(format!( + "{}", + String::from_utf8_lossy(mined_by.as_bytes()) + )); + }; + return Ok("CK Pool".to_string()); + }; + + // Miners identified by fixed string + let fixed_string_miners = [ + ("Mining-Dutch", "Mining-Dutch"), + ("ZULUPooL", "ZULUPooL"), + ("Hath", "Hathor-MM"), + ("zpool.ca", "zpool.ca"), + ("molepool.com", "molepool.com"), + ("CoinMinerz.com", "CoinMinerz.com"), + ("zergpool.com", "zergpool.com"), + ("solopool.org", "solopool.org"), + ("p2p-spb.xyz", "p2p-spb.xyz"), + ("Cminors-Pools", "Cminors-Pools"), + ("Mined by with Om Power", "with Om Power"), + ]; + + for &(str_to_match, str_to_show) in &fixed_string_miners { + let fixed_string_regex = Regex::new(str_to_match).unwrap(); + if fixed_string_regex.captures(coinbase_data).is_some() { + return Ok(str_to_show.to_string()); + }; + } + + // Miner not recognized, return "Unknown" + let output = html! { + "Unknown" + }; + return Ok(output.into_string()); +} + pub fn render_integer_with_commas(int: &u64) -> askama::Result<String> { let string = int.to_formatted_string(&Locale::en); let parts = string.split(',').collect::<Vec<_>>(); @@ -220,3 +281,204 @@ ) -> askama::Result<Option<&'a TokenInfo>> { Ok(tokens.get(token_id)) } + +#[cfg(test)] +mod tests { + // Ref https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html + use super::*; + + #[test] + fn test_render_miner() { + // ViaBTC 791160 (mined by 260786) + let via_coinbase_hex: &[u8] = &hex::decode("\ + 0378120c182f5669614254432f4d696e\ + 6564206279203236303738362f103b6f\ + a20ff3648a69acc31ed9b4946c00",) + .unwrap(); + assert_eq!( + render_miner(via_coinbase_hex).unwrap(), + "ViaBTC | Mined by 260786" + ); + + // ViaBTC 852373 (mined by zuberjawan) + let via_zuberjawan_coinbase_hex: &[u8] = &hex::decode("\ + 0395010d1c2f5669614254432f4d696e\ + 6564206279207a756265726a6177616e\ + 2f10bfdeb80b56e43dedd003504ea2aa0\ + f00", + ) + .unwrap(); + assert_eq!( + render_miner(via_zuberjawan_coinbase_hex).unwrap(), + "ViaBTC | Mined by zuberjawan" + ); + + // Mining-Dutch 854964 + let md_coinbase_hex: &[u8] = &hex::decode("\ + 03b40b0d04caa4a36608fabe6d6d55a9\ + 8c6faaf7a77461d5e72f0564278a6b6c\ + 94e8788b4a791a0190c3bc8e048c0001\ + 00000000000004e248cb5d4100005a00\ + 122f4d696e696e672d44757463682f2d\ + 313134" + ) + .unwrap(); + assert_eq!(render_miner(md_coinbase_hex).unwrap(), "Mining-Dutch"); + + // Hathor-MM 823276 + let hathor_coinbase_hex: &[u8] = &hex::decode("\ + 03ec8f0c486174683dec223b763544f2\ + 8819df8ba957944bbac2e35cb8de151b\ + 030edee88a90135073e02f191d000000" + ) + .unwrap(); + assert_eq!(render_miner(hathor_coinbase_hex).unwrap(), "Hathor-MM"); + + // Zulu Pool 785677 + // A block with both Hathor and Zulu hex strings is returned as Zulu + let zulu_coinbase_hex: &[u8] = &hex::decode("\ + 030dfd0b48617468a881a54b5fbc28b2\ + 7eb3ed59fc4924a3b991033fee7a78b9\ + 19170a92d9b7beaf5a554c55506f6f4c\ + 2d584543000011d8e9bb1b00" + ) + .unwrap(); + assert_eq!(render_miner(zulu_coinbase_hex).unwrap(), "ZULUPooL"); + + // CK Pool specified miner TinyChipHub + // 854770 + let ck_tinychiphub_coinbase_hex: &[u8] = &hex::decode("\ + 03f20a0d0004fae1a166045a8ce5020c\ + 2c5e9f6658e16f3e86ee5e000a636b70\ + 6f6f6c162f6d696e6564206279205469\ + 6e79436869704875622f", + ) + .unwrap(); + assert_eq!( + render_miner(ck_tinychiphub_coinbase_hex).unwrap(), + "TinyChipHub" + ); + + // CK Pool unspecified miner + // 788631 + let ck_unspecified_coinbase_hex: &[u8] = &hex::decode("\ + 0397080c04181678a1046498416404bb\ + 67ca0d0c369241647763010000000000\ + 0a636b706f6f6c", + ) + .unwrap(); + assert_eq!( + render_miner(ck_unspecified_coinbase_hex).unwrap(), + "CK Pool" + ); + + // zpool 790863 + let zpool_coinbase_hex: &[u8] = &hex::decode("\ + 034f110c04602a576408420007907742\ + 2b017a706f6f6c2e636100fabe6d6da8\ + 21294426652ebd8cff8df5e02ffcbbdc\ + 1b1d9e9022832acf4d071e9bfa2d9520\ + 00000000000000" + ) + .unwrap(); + assert_eq!(render_miner(zpool_coinbase_hex).unwrap(), "zpool.ca"); + + // molepool.com 796646 + let molepool_coinbase_hex: &[u8] = &hex::decode( + "\ + 03e6270c047d258c6400189620e6fee4e2170e2f6d6f6c65706f6f6c2e636f6d2f", + ) + .unwrap(); + assert_eq!( + render_miner(molepool_coinbase_hex).unwrap(), + "molepool.com" + ); + + // CoinMinerz.com 787515 + let coinminerz_coinbase_hex: &[u8] = &hex::decode( + "\ + 033b040c04929a37640862c9c313191b1e00102f436f696e4d696e65727a2e636f6d2f", + ) + .unwrap(); + assert_eq!( + render_miner(coinminerz_coinbase_hex).unwrap(), + "CoinMinerz.com" + ); + + // zergpool.com 806676 + let zergpool_coinbase_hex: &[u8] = &hex::decode("\ + 03144f0c04b01ee76408810609082dc3\ + 06007a657267706f6f6c2e636f6d00fa\ + be6d6d27401f3db7d7af865afebab80d\ + 7cb5357e7df5aae8621e46c2aa7c90bb\ + fcdbca0200000000000000" + ) + .unwrap(); + assert_eq!( + render_miner(zergpool_coinbase_hex).unwrap(), + "zergpool.com" + ); + + // solopool.org 806713 + let solopool_coinbase_hex: &[u8] = &hex::decode( + "\ + 03394f0c04529ee76408f51eecb651d049040c736f6c6f706f6f6c2e6f7267", + ) + .unwrap(); + assert_eq!( + render_miner(solopool_coinbase_hex).unwrap(), + "solopool.org" + ); + + // p2p-spb 821556 + let p2pspb_coinbase_hex: &[u8] = &hex::decode("\ + 0334890c2cfabe6d6d2a357fe8c5668c\ + 1edd58a42eaaa181f49afcf397cd6ae2\ + 475393ec06cfca62f010000000000000\ + 007032702d7370622e78797a" + ) + .unwrap(); + assert_eq!(render_miner(p2pspb_coinbase_hex).unwrap(), "p2p-spb.xyz"); + + // Cminors-Pools 827550 + let cminors_coinbase_hex: &[u8] = &hex::decode("\ + 039ea00c0429c1a765088100001fa15a\ + 6800436d696e6f72732d506f6f6c7300\ + fabe6d6df36f3ced97aac363e4f5202e\ + 4b39d539454d9d21c408df8ac4be8eb9\ + 3660420b0100000000000000" + ) + .unwrap(); + assert_eq!( + render_miner(cminors_coinbase_hex).unwrap(), + "Cminors-Pools" + ); + + // AnandrajSingh Pool 840619 + let anandra_coinbase_hex: &[u8] = &hex::decode("\ + 03abd30c00046a362166046994e2100c\ + 601f21668868440000000000204d696e\ + 65642062792077697468204f6d20506f\ + 776572202f416e616e6472616a53696e\ + 676820506f6f6c2f0d20f09f8f86f09f\ + 8f86f09f8f86" + ) + .unwrap(); + assert_eq!( + render_miner(anandra_coinbase_hex).unwrap(), + "with Om Power" + ); + + // Unknown miner + // genesis block 0 + let unknown_coinbase_hex: &[u8] = &hex::decode("\ + 04ffff001d0104455468652054696d65\ + 732030332f4a616e2f32303039204368\ + 616e63656c6c6f72206f6e206272696e\ + 6b206f66207365636f6e64206261696c\ + 6f757420666f722062616e6b73" + ) + .unwrap(); + assert_eq!(render_miner(unknown_coinbase_hex).unwrap(), "Unknown"); + } +} diff --git a/web/explorer/explorer-server/templates/pages/block.html b/web/explorer/explorer-server/templates/pages/block.html --- a/web/explorer/explorer-server/templates/pages/block.html +++ b/web/explorer/explorer-server/templates/pages/block.html @@ -109,7 +109,7 @@ <tr> <td>Mined by</td> - <td>Unknown</td> + <td>{{ coinbase_data|render_miner }}</td> </tr> <tr>