commit
						fd8d9edf6c
					
				
							
								
								
									
										5
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@ -27,6 +27,11 @@ jobs:
 | 
			
		||||
          node-version: ${{ matrix.node }}
 | 
			
		||||
          registry-url: "https://registry.npmjs.org"
 | 
			
		||||
 | 
			
		||||
      - name: Install 1.70.x Rust toolchain
 | 
			
		||||
        uses: actions-rs/toolchain@v1
 | 
			
		||||
        with:
 | 
			
		||||
            toolchain: 1.70
 | 
			
		||||
 | 
			
		||||
      - name: Install
 | 
			
		||||
        if: ${{ matrix.flavor == 'dev'}}
 | 
			
		||||
        run: npm ci
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -5,3 +5,4 @@ backend/mempool-config.json
 | 
			
		||||
*.swp
 | 
			
		||||
frontend/src/resources/config.template.js
 | 
			
		||||
frontend/src/resources/config.js
 | 
			
		||||
target
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -1,5 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "editor.tabSize": 2,
 | 
			
		||||
  "typescript.preferences.importModuleSpecifier": "relative",
 | 
			
		||||
  "typescript.tsdk": "./backend/node_modules/typescript/lib"
 | 
			
		||||
  "typescript.tsdk": "./backend/node_modules/typescript/lib",
 | 
			
		||||
  "rust-analyzer.procMacro.ignored": { "napi-derive": ["napi"] }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										533
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										533
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,533 @@
 | 
			
		||||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "aho-corasick"
 | 
			
		||||
version = "1.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "autocfg"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bitflags"
 | 
			
		||||
version = "2.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytemuck"
 | 
			
		||||
version = "1.13.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytes"
 | 
			
		||||
version = "1.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cfg-if"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "convert_case"
 | 
			
		||||
version = "0.6.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-segmentation",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ctor"
 | 
			
		||||
version = "0.2.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1586fa608b1dab41f667475b4a41faec5ba680aee428bfa5de4ea520fdc6e901"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.20",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gbt"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytemuck",
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "napi",
 | 
			
		||||
 "napi-build",
 | 
			
		||||
 "napi-derive",
 | 
			
		||||
 "priority-queue",
 | 
			
		||||
 "tracing",
 | 
			
		||||
 "tracing-log",
 | 
			
		||||
 "tracing-subscriber",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hashbrown"
 | 
			
		||||
version = "0.12.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hermit-abi"
 | 
			
		||||
version = "0.2.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexmap"
 | 
			
		||||
version = "1.9.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "hashbrown",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lazy_static"
 | 
			
		||||
version = "1.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "libc"
 | 
			
		||||
version = "0.2.146"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "libloading"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "log"
 | 
			
		||||
version = "0.4.19"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "matchers"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "regex-automata",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "memchr"
 | 
			
		||||
version = "2.5.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "napi"
 | 
			
		||||
version = "2.13.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "ctor",
 | 
			
		||||
 "napi-derive",
 | 
			
		||||
 "napi-sys",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "tokio",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "napi-build"
 | 
			
		||||
version = "2.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "napi-derive"
 | 
			
		||||
version = "2.13.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "convert_case",
 | 
			
		||||
 "napi-derive-backend",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 1.0.109",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "napi-derive-backend"
 | 
			
		||||
version = "1.0.52"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "convert_case",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "semver",
 | 
			
		||||
 "syn 1.0.109",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "napi-sys"
 | 
			
		||||
version = "2.2.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libloading",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "nu-ansi-term"
 | 
			
		||||
version = "0.46.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "overload",
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num_cpus"
 | 
			
		||||
version = "1.15.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "hermit-abi",
 | 
			
		||||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "once_cell"
 | 
			
		||||
version = "1.18.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "overload"
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-project-lite"
 | 
			
		||||
version = "0.2.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "priority-queue"
 | 
			
		||||
version = "1.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "indexmap",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro2"
 | 
			
		||||
version = "1.0.60"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.28"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex"
 | 
			
		||||
version = "1.8.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "aho-corasick",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "regex-syntax 0.7.2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-automata"
 | 
			
		||||
version = "0.1.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "regex-syntax 0.6.29",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-syntax"
 | 
			
		||||
version = "0.6.29"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-syntax"
 | 
			
		||||
version = "0.7.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "semver"
 | 
			
		||||
version = "1.0.17"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sharded-slab"
 | 
			
		||||
version = "0.1.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "smallvec"
 | 
			
		||||
version = "1.10.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "1.0.109"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "2.0.20"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fcb8d4cebc40aa517dfb69618fa647a346562e67228e2236ae0042ee6ac14775"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thread_local"
 | 
			
		||||
version = "1.1.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tokio"
 | 
			
		||||
version = "1.28.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "num_cpus",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "windows-sys",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing"
 | 
			
		||||
version = "0.1.37"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "tracing-attributes",
 | 
			
		||||
 "tracing-core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-attributes"
 | 
			
		||||
version = "0.1.26"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.20",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-core"
 | 
			
		||||
version = "0.1.31"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "valuable",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-log"
 | 
			
		||||
version = "0.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "tracing-core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-subscriber"
 | 
			
		||||
version = "0.3.17"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "matchers",
 | 
			
		||||
 "nu-ansi-term",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "sharded-slab",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
 "thread_local",
 | 
			
		||||
 "tracing",
 | 
			
		||||
 "tracing-core",
 | 
			
		||||
 "tracing-log",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-ident"
 | 
			
		||||
version = "1.0.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-segmentation"
 | 
			
		||||
version = "1.10.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "valuable"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi"
 | 
			
		||||
version = "0.3.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "winapi-i686-pc-windows-gnu",
 | 
			
		||||
 "winapi-x86_64-pc-windows-gnu",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-i686-pc-windows-gnu"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-x86_64-pc-windows-gnu"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows-sys"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows-targets",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows-targets"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows_aarch64_gnullvm",
 | 
			
		||||
 "windows_aarch64_msvc",
 | 
			
		||||
 "windows_i686_gnu",
 | 
			
		||||
 "windows_i686_msvc",
 | 
			
		||||
 "windows_x86_64_gnu",
 | 
			
		||||
 "windows_x86_64_gnullvm",
 | 
			
		||||
 "windows_x86_64_msvc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_aarch64_gnullvm"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_aarch64_msvc"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_i686_gnu"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_i686_msvc"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_gnu"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_gnullvm"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_msvc"
 | 
			
		||||
version = "0.48.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
 | 
			
		||||
							
								
								
									
										8
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
[workspace]
 | 
			
		||||
members = [
 | 
			
		||||
	"./backend/rust-gbt",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[profile.release]
 | 
			
		||||
lto = true
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
@ -79,6 +79,8 @@ Query OK, 0 rows affected (0.00 sec)
 | 
			
		||||
 | 
			
		||||
_Make sure to use Node.js 16.10 and npm 7._
 | 
			
		||||
 | 
			
		||||
_The build process requires [Rust](https://www.rust-lang.org/tools/install) to be installed._
 | 
			
		||||
 | 
			
		||||
Install dependencies with `npm` and build the backend:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@
 | 
			
		||||
    "AUDIT": false,
 | 
			
		||||
    "ADVANCED_GBT_AUDIT": false,
 | 
			
		||||
    "ADVANCED_GBT_MEMPOOL": false,
 | 
			
		||||
    "RUST_GBT": false,
 | 
			
		||||
    "CPFP_INDEXING": false,
 | 
			
		||||
    "DISK_CACHE_BLOCK_INTERVAL": 6
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										44
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										44
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -19,6 +19,7 @@
 | 
			
		||||
        "maxmind": "~4.3.8",
 | 
			
		||||
        "mysql2": "~3.2.0",
 | 
			
		||||
        "node-worker-threads-pool": "~1.5.1",
 | 
			
		||||
        "rust-gbt": "file:./rust-gbt",
 | 
			
		||||
        "socks-proxy-agent": "~7.0.0",
 | 
			
		||||
        "typescript": "~4.7.4",
 | 
			
		||||
        "ws": "~8.13.0"
 | 
			
		||||
@ -1485,6 +1486,22 @@
 | 
			
		||||
        "node": ">=6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@napi-rs/cli": {
 | 
			
		||||
      "version": "2.16.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz",
 | 
			
		||||
      "integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "napi": "scripts/index.js"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "github",
 | 
			
		||||
        "url": "https://github.com/sponsors/Brooooooklyn"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@noble/hashes": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
 | 
			
		||||
@ -6665,6 +6682,10 @@
 | 
			
		||||
        "queue-microtask": "^1.2.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/rust-gbt": {
 | 
			
		||||
      "resolved": "rust-gbt",
 | 
			
		||||
      "link": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/safe-buffer": {
 | 
			
		||||
      "version": "5.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
			
		||||
@ -7544,6 +7565,17 @@
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "rust-gbt": {
 | 
			
		||||
      "name": "gbt",
 | 
			
		||||
      "version": "0.1.0",
 | 
			
		||||
      "hasInstallScript": true,
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@napi-rs/cli": "^2.16.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 12"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
@ -8631,6 +8663,12 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz",
 | 
			
		||||
      "integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "@napi-rs/cli": {
 | 
			
		||||
      "version": "2.16.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz",
 | 
			
		||||
      "integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "@noble/hashes": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
 | 
			
		||||
@ -12481,6 +12519,12 @@
 | 
			
		||||
        "queue-microtask": "^1.2.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "rust-gbt": {
 | 
			
		||||
      "version": "file:rust-gbt",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@napi-rs/cli": "^2.16.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "safe-buffer": {
 | 
			
		||||
      "version": "5.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
			
		||||
 | 
			
		||||
@ -22,10 +22,10 @@
 | 
			
		||||
  "main": "index.ts",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "tsc": "./node_modules/typescript/bin/tsc -p tsconfig.build.json",
 | 
			
		||||
    "build": "npm run tsc && npm run create-resources",
 | 
			
		||||
    "build": "npm run build-rust && npm run tsc && npm run create-resources",
 | 
			
		||||
    "create-resources": "cp ./src/tasks/price-feeds/mtgox-weekly.json ./dist/tasks && node dist/api/fetch-version.js",
 | 
			
		||||
    "package": "npm run build && rm -rf package && mv dist package && mv node_modules package && npm run package-rm-build-deps",
 | 
			
		||||
    "package-rm-build-deps": "(cd package/node_modules; rm -r typescript @typescript-eslint)",
 | 
			
		||||
    "package": "npm run build && rm -rf package && mv dist package && mv node_modules package && mv rust-gbt package && npm run package-rm-build-deps",
 | 
			
		||||
    "package-rm-build-deps": "(cd package/node_modules; rm -r typescript @typescript-eslint @napi-rs ../rust-gbt/target ../rust-gbt/node_modules ../rust-gbt/src)",
 | 
			
		||||
    "start": "node --max-old-space-size=2048 dist/index.js",
 | 
			
		||||
    "start-production": "node --max-old-space-size=16384 dist/index.js",
 | 
			
		||||
    "reindex-updated-pools": "npm run start-production --update-pools",
 | 
			
		||||
@ -33,7 +33,8 @@
 | 
			
		||||
    "test": "./node_modules/.bin/jest --coverage",
 | 
			
		||||
    "lint": "./node_modules/.bin/eslint . --ext .ts",
 | 
			
		||||
    "lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
 | 
			
		||||
    "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
 | 
			
		||||
    "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\"",
 | 
			
		||||
    "build-rust": "cd rust-gbt && npm install"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@babel/core": "^7.21.3",
 | 
			
		||||
@ -46,6 +47,7 @@
 | 
			
		||||
    "maxmind": "~4.3.8",
 | 
			
		||||
    "mysql2": "~3.2.0",
 | 
			
		||||
    "node-worker-threads-pool": "~1.5.1",
 | 
			
		||||
    "rust-gbt": "file:./rust-gbt",
 | 
			
		||||
    "socks-proxy-agent": "~7.0.0",
 | 
			
		||||
    "typescript": "~4.7.4",
 | 
			
		||||
    "ws": "~8.13.0"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								backend/rust-gbt/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								backend/rust-gbt/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
*.node
 | 
			
		||||
**/node_modules
 | 
			
		||||
**/.DS_Store
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
							
								
								
									
										25
									
								
								backend/rust-gbt/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								backend/rust-gbt/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "gbt"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
description = "An inefficient re-implementation of the getBlockTemplate algorithm in Rust"
 | 
			
		||||
authors = ["mononaut"]
 | 
			
		||||
edition = "2021"
 | 
			
		||||
publish = false
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
crate-type = ["cdylib"]
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
priority-queue = "1.3.2"
 | 
			
		||||
bytes = "1.4.0"
 | 
			
		||||
napi = { version = "2.13.2", features = ["napi8", "tokio_rt"] }
 | 
			
		||||
napi-derive = "2.13.0"
 | 
			
		||||
bytemuck = "1.13.1"
 | 
			
		||||
tracing = "0.1.36"
 | 
			
		||||
tracing-log = "0.1.3"
 | 
			
		||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"]}
 | 
			
		||||
 | 
			
		||||
[build-dependencies]
 | 
			
		||||
napi-build = "2.0.1"
 | 
			
		||||
							
								
								
									
										123
									
								
								backend/rust-gbt/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								backend/rust-gbt/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,123 @@
 | 
			
		||||
# gbt
 | 
			
		||||
 | 
			
		||||
**gbt:** rust implementation of the getBlockTemplate algorithm
 | 
			
		||||
 | 
			
		||||
This project was bootstrapped by [napi](https://www.npmjs.com/package/@napi-rs/cli).
 | 
			
		||||
 | 
			
		||||
## Installing gbt
 | 
			
		||||
 | 
			
		||||
Installing gbt requires a [supported version of Node and Rust](https://github.com/napi-rs/napi-rs#platform-support).
 | 
			
		||||
 | 
			
		||||
The build process also requires [Rust](https://www.rust-lang.org/tools/install) to be installed.
 | 
			
		||||
 | 
			
		||||
You can install the project with npm. In the project directory, run:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ npm install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This fully installs the project, including installing any dependencies and running the build.
 | 
			
		||||
 | 
			
		||||
## Building gbt
 | 
			
		||||
 | 
			
		||||
If you have already installed the project and only want to run the build, run:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ npm run build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This command uses the [napi build](https://www.npmjs.com/package/@napi-rs/cli) utility to run the Rust build and copy the built library into `./gbt.[TARGET_TRIPLE].node`.
 | 
			
		||||
 | 
			
		||||
## Exploring gbt
 | 
			
		||||
 | 
			
		||||
After building gbt, you can explore its exports at the Node REPL:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ npm install
 | 
			
		||||
$ node
 | 
			
		||||
> require('.').hello()
 | 
			
		||||
"hello node"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Available Scripts
 | 
			
		||||
 | 
			
		||||
In the project directory, you can run:
 | 
			
		||||
 | 
			
		||||
### `npm install`
 | 
			
		||||
 | 
			
		||||
Installs the project, including running `npm run build-release`.
 | 
			
		||||
 | 
			
		||||
### `npm build`
 | 
			
		||||
 | 
			
		||||
Builds the Node addon (`gbt.[TARGET_TRIPLE].node`) from source.
 | 
			
		||||
 | 
			
		||||
Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm build` and `npm build-*` commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
npm run build -- --feature=beetle
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `npm build-debug`
 | 
			
		||||
 | 
			
		||||
Alias for `npm build`.
 | 
			
		||||
 | 
			
		||||
#### `npm build-release`
 | 
			
		||||
 | 
			
		||||
Same as [`npm build`](#npm-build) but, builds the module with the [`release`](https://doc.rust-lang.org/cargo/reference/profiles.html#release) profile. Release builds will compile slower, but run faster.
 | 
			
		||||
 | 
			
		||||
### `npm test`
 | 
			
		||||
 | 
			
		||||
Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/).
 | 
			
		||||
 | 
			
		||||
## Project Layout
 | 
			
		||||
 | 
			
		||||
The directory structure of this project is:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
gbt/
 | 
			
		||||
├── Cargo.toml
 | 
			
		||||
├── README.md
 | 
			
		||||
├── gbt.[TARGET_TRIPLE].node
 | 
			
		||||
├── package.json
 | 
			
		||||
├── src/
 | 
			
		||||
|   └── lib.rs
 | 
			
		||||
└── target/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Cargo.toml
 | 
			
		||||
 | 
			
		||||
The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command.
 | 
			
		||||
 | 
			
		||||
### README.md
 | 
			
		||||
 | 
			
		||||
This file.
 | 
			
		||||
 | 
			
		||||
### gbt.\[TARGET_TRIPLE\].node
 | 
			
		||||
 | 
			
		||||
The Node addon—i.e., a binary Node module—generated by building the project. This is the main module for this package, as dictated by the `"main"` key in `package.json`.
 | 
			
		||||
 | 
			
		||||
Under the hood, a [Node addon](https://nodejs.org/api/addons.html) is a [dynamically-linked shared object](https://en.wikipedia.org/wiki/Library_(computing)#Shared_libraries). The `"build"` script produces this file by copying it from within the `target/` directory, which is where the Rust build produces the shared object.
 | 
			
		||||
 | 
			
		||||
### package.json
 | 
			
		||||
 | 
			
		||||
The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command.
 | 
			
		||||
 | 
			
		||||
### src/
 | 
			
		||||
 | 
			
		||||
The directory tree containing the Rust source code for the project.
 | 
			
		||||
 | 
			
		||||
### src/lib.rs
 | 
			
		||||
 | 
			
		||||
The Rust library's main module.
 | 
			
		||||
 | 
			
		||||
### target/
 | 
			
		||||
 | 
			
		||||
Binary artifacts generated by the Rust build.
 | 
			
		||||
 | 
			
		||||
## Learn More
 | 
			
		||||
 | 
			
		||||
To learn more about Neon, see the [Napi-RS documentation](https://napi.rs/docs/introduction/getting-started).
 | 
			
		||||
 | 
			
		||||
To learn more about Rust, see the [Rust documentation](https://www.rust-lang.org).
 | 
			
		||||
 | 
			
		||||
To learn more about Node, see the [Node documentation](https://nodejs.org).
 | 
			
		||||
							
								
								
									
										3
									
								
								backend/rust-gbt/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/rust-gbt/build.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
fn main() {
 | 
			
		||||
    napi_build::setup();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								backend/rust-gbt/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								backend/rust-gbt/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
 | 
			
		||||
/* auto-generated by NAPI-RS */
 | 
			
		||||
 | 
			
		||||
export interface ThreadTransaction {
 | 
			
		||||
  uid: number
 | 
			
		||||
  order: number
 | 
			
		||||
  fee: number
 | 
			
		||||
  weight: number
 | 
			
		||||
  sigops: number
 | 
			
		||||
  effectiveFeePerVsize: number
 | 
			
		||||
  inputs: Array<number>
 | 
			
		||||
}
 | 
			
		||||
export class GbtGenerator {
 | 
			
		||||
  constructor()
 | 
			
		||||
  /**
 | 
			
		||||
   * # Errors
 | 
			
		||||
   *
 | 
			
		||||
   * Rejects if the thread panics or if the Mutex is poisoned.
 | 
			
		||||
   */
 | 
			
		||||
  make(mempool: Array<ThreadTransaction>, maxUid: number): Promise<GbtResult>
 | 
			
		||||
  /**
 | 
			
		||||
   * # Errors
 | 
			
		||||
   *
 | 
			
		||||
   * Rejects if the thread panics or if the Mutex is poisoned.
 | 
			
		||||
   */
 | 
			
		||||
  update(newTxs: Array<ThreadTransaction>, removeTxs: Array<number>, maxUid: number): Promise<GbtResult>
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * The result from calling the gbt function.
 | 
			
		||||
 *
 | 
			
		||||
 * This tuple contains the following:
 | 
			
		||||
 *        blocks: A 2D Vector of transaction IDs (u32), the inner Vecs each represent a block.
 | 
			
		||||
 * block_weights: A Vector of total weights per block.
 | 
			
		||||
 *      clusters: A 2D Vector of transaction IDs representing clusters of dependent mempool transactions
 | 
			
		||||
 *         rates: A Vector of tuples containing transaction IDs (u32) and effective fee per vsize (f64)
 | 
			
		||||
 */
 | 
			
		||||
export class GbtResult {
 | 
			
		||||
  blocks: Array<Array<number>>
 | 
			
		||||
  blockWeights: Array<number>
 | 
			
		||||
  clusters: Array<Array<number>>
 | 
			
		||||
  rates: Array<Array<number>>
 | 
			
		||||
  constructor(blocks: Array<Array<number>>, blockWeights: Array<number>, clusters: Array<Array<number>>, rates: Array<Array<number>>)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										258
									
								
								backend/rust-gbt/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								backend/rust-gbt/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,258 @@
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
/* prettier-ignore */
 | 
			
		||||
 | 
			
		||||
/* auto-generated by NAPI-RS */
 | 
			
		||||
 | 
			
		||||
const { existsSync, readFileSync } = require('fs')
 | 
			
		||||
const { join } = require('path')
 | 
			
		||||
 | 
			
		||||
const { platform, arch } = process
 | 
			
		||||
 | 
			
		||||
let nativeBinding = null
 | 
			
		||||
let localFileExisted = false
 | 
			
		||||
let loadError = null
 | 
			
		||||
 | 
			
		||||
function isMusl() {
 | 
			
		||||
  // For Node 10
 | 
			
		||||
  if (!process.report || typeof process.report.getReport !== 'function') {
 | 
			
		||||
    try {
 | 
			
		||||
      const lddPath = require('child_process').execSync('which ldd').toString().trim()
 | 
			
		||||
      return readFileSync(lddPath, 'utf8').includes('musl')
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    const { glibcVersionRuntime } = process.report.getReport().header
 | 
			
		||||
    return !glibcVersionRuntime
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
switch (platform) {
 | 
			
		||||
  case 'android':
 | 
			
		||||
    switch (arch) {
 | 
			
		||||
      case 'arm64':
 | 
			
		||||
        localFileExisted = existsSync(join(__dirname, 'gbt.android-arm64.node'))
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.android-arm64.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-android-arm64')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      case 'arm':
 | 
			
		||||
        localFileExisted = existsSync(join(__dirname, 'gbt.android-arm-eabi.node'))
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.android-arm-eabi.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-android-arm-eabi')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        throw new Error(`Unsupported architecture on Android ${arch}`)
 | 
			
		||||
    }
 | 
			
		||||
    break
 | 
			
		||||
  case 'win32':
 | 
			
		||||
    switch (arch) {
 | 
			
		||||
      case 'x64':
 | 
			
		||||
        localFileExisted = existsSync(
 | 
			
		||||
          join(__dirname, 'gbt.win32-x64-msvc.node')
 | 
			
		||||
        )
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.win32-x64-msvc.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-win32-x64-msvc')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      case 'ia32':
 | 
			
		||||
        localFileExisted = existsSync(
 | 
			
		||||
          join(__dirname, 'gbt.win32-ia32-msvc.node')
 | 
			
		||||
        )
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.win32-ia32-msvc.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-win32-ia32-msvc')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      case 'arm64':
 | 
			
		||||
        localFileExisted = existsSync(
 | 
			
		||||
          join(__dirname, 'gbt.win32-arm64-msvc.node')
 | 
			
		||||
        )
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.win32-arm64-msvc.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-win32-arm64-msvc')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        throw new Error(`Unsupported architecture on Windows: ${arch}`)
 | 
			
		||||
    }
 | 
			
		||||
    break
 | 
			
		||||
  case 'darwin':
 | 
			
		||||
    localFileExisted = existsSync(join(__dirname, 'gbt.darwin-universal.node'))
 | 
			
		||||
    try {
 | 
			
		||||
      if (localFileExisted) {
 | 
			
		||||
        nativeBinding = require('./gbt.darwin-universal.node')
 | 
			
		||||
      } else {
 | 
			
		||||
        nativeBinding = require('gbt-darwin-universal')
 | 
			
		||||
      }
 | 
			
		||||
      break
 | 
			
		||||
    } catch {}
 | 
			
		||||
    switch (arch) {
 | 
			
		||||
      case 'x64':
 | 
			
		||||
        localFileExisted = existsSync(join(__dirname, 'gbt.darwin-x64.node'))
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.darwin-x64.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-darwin-x64')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      case 'arm64':
 | 
			
		||||
        localFileExisted = existsSync(
 | 
			
		||||
          join(__dirname, 'gbt.darwin-arm64.node')
 | 
			
		||||
        )
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.darwin-arm64.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-darwin-arm64')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        throw new Error(`Unsupported architecture on macOS: ${arch}`)
 | 
			
		||||
    }
 | 
			
		||||
    break
 | 
			
		||||
  case 'freebsd':
 | 
			
		||||
    if (arch !== 'x64') {
 | 
			
		||||
      throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
 | 
			
		||||
    }
 | 
			
		||||
    localFileExisted = existsSync(join(__dirname, 'gbt.freebsd-x64.node'))
 | 
			
		||||
    try {
 | 
			
		||||
      if (localFileExisted) {
 | 
			
		||||
        nativeBinding = require('./gbt.freebsd-x64.node')
 | 
			
		||||
      } else {
 | 
			
		||||
        nativeBinding = require('gbt-freebsd-x64')
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      loadError = e
 | 
			
		||||
    }
 | 
			
		||||
    break
 | 
			
		||||
  case 'linux':
 | 
			
		||||
    switch (arch) {
 | 
			
		||||
      case 'x64':
 | 
			
		||||
        if (isMusl()) {
 | 
			
		||||
          localFileExisted = existsSync(
 | 
			
		||||
            join(__dirname, 'gbt.linux-x64-musl.node')
 | 
			
		||||
          )
 | 
			
		||||
          try {
 | 
			
		||||
            if (localFileExisted) {
 | 
			
		||||
              nativeBinding = require('./gbt.linux-x64-musl.node')
 | 
			
		||||
            } else {
 | 
			
		||||
              nativeBinding = require('gbt-linux-x64-musl')
 | 
			
		||||
            }
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            loadError = e
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          localFileExisted = existsSync(
 | 
			
		||||
            join(__dirname, 'gbt.linux-x64-gnu.node')
 | 
			
		||||
          )
 | 
			
		||||
          try {
 | 
			
		||||
            if (localFileExisted) {
 | 
			
		||||
              nativeBinding = require('./gbt.linux-x64-gnu.node')
 | 
			
		||||
            } else {
 | 
			
		||||
              nativeBinding = require('gbt-linux-x64-gnu')
 | 
			
		||||
            }
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            loadError = e
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      case 'arm64':
 | 
			
		||||
        if (isMusl()) {
 | 
			
		||||
          localFileExisted = existsSync(
 | 
			
		||||
            join(__dirname, 'gbt.linux-arm64-musl.node')
 | 
			
		||||
          )
 | 
			
		||||
          try {
 | 
			
		||||
            if (localFileExisted) {
 | 
			
		||||
              nativeBinding = require('./gbt.linux-arm64-musl.node')
 | 
			
		||||
            } else {
 | 
			
		||||
              nativeBinding = require('gbt-linux-arm64-musl')
 | 
			
		||||
            }
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            loadError = e
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          localFileExisted = existsSync(
 | 
			
		||||
            join(__dirname, 'gbt.linux-arm64-gnu.node')
 | 
			
		||||
          )
 | 
			
		||||
          try {
 | 
			
		||||
            if (localFileExisted) {
 | 
			
		||||
              nativeBinding = require('./gbt.linux-arm64-gnu.node')
 | 
			
		||||
            } else {
 | 
			
		||||
              nativeBinding = require('gbt-linux-arm64-gnu')
 | 
			
		||||
            }
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            loadError = e
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      case 'arm':
 | 
			
		||||
        localFileExisted = existsSync(
 | 
			
		||||
          join(__dirname, 'gbt.linux-arm-gnueabihf.node')
 | 
			
		||||
        )
 | 
			
		||||
        try {
 | 
			
		||||
          if (localFileExisted) {
 | 
			
		||||
            nativeBinding = require('./gbt.linux-arm-gnueabihf.node')
 | 
			
		||||
          } else {
 | 
			
		||||
            nativeBinding = require('gbt-linux-arm-gnueabihf')
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          loadError = e
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        throw new Error(`Unsupported architecture on Linux: ${arch}`)
 | 
			
		||||
    }
 | 
			
		||||
    break
 | 
			
		||||
  default:
 | 
			
		||||
    throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (!nativeBinding) {
 | 
			
		||||
  if (loadError) {
 | 
			
		||||
    throw loadError
 | 
			
		||||
  }
 | 
			
		||||
  throw new Error(`Failed to load native binding`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const { GbtGenerator, GbtResult } = nativeBinding
 | 
			
		||||
 | 
			
		||||
module.exports.GbtGenerator = GbtGenerator
 | 
			
		||||
module.exports.GbtResult = GbtResult
 | 
			
		||||
							
								
								
									
										34
									
								
								backend/rust-gbt/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								backend/rust-gbt/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "gbt",
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "lockfileVersion": 3,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "name": "gbt",
 | 
			
		||||
      "version": "0.1.0",
 | 
			
		||||
      "hasInstallScript": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@napi-rs/cli": "^2.16.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@napi-rs/cli": {
 | 
			
		||||
      "version": "2.16.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz",
 | 
			
		||||
      "integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==",
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "napi": "scripts/index.js"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "github",
 | 
			
		||||
        "url": "https://github.com/sponsors/Brooooooklyn"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								backend/rust-gbt/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								backend/rust-gbt/package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "gbt",
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "description": "An inefficient re-implementation of the getBlockTemplate algorithm in Rust",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "types": "index.d.ts",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "artifacts": "napi artifacts",
 | 
			
		||||
    "build": "napi build --platform",
 | 
			
		||||
    "build-debug": "npm run build",
 | 
			
		||||
    "build-release": "npm run build -- --release --strip",
 | 
			
		||||
    "install": "npm run build-release",
 | 
			
		||||
    "prepublishOnly": "napi prepublish -t npm",
 | 
			
		||||
    "test": "cargo test"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "mononaut",
 | 
			
		||||
  "napi": {
 | 
			
		||||
    "name": "gbt",
 | 
			
		||||
    "triples": {
 | 
			
		||||
      "defaults": false,
 | 
			
		||||
      "additional": [
 | 
			
		||||
        "x86_64-unknown-linux-gnu",
 | 
			
		||||
        "x86_64-unknown-freebsd"
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@napi-rs/cli": "^2.16.1"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">= 12"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										220
									
								
								backend/rust-gbt/src/audit_transaction.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								backend/rust-gbt/src/audit_transaction.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,220 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    u32_hasher_types::{u32hashset_new, U32HasherState},
 | 
			
		||||
    ThreadTransaction,
 | 
			
		||||
};
 | 
			
		||||
use std::{
 | 
			
		||||
    cmp::Ordering,
 | 
			
		||||
    collections::HashSet,
 | 
			
		||||
    hash::{Hash, Hasher},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::struct_excessive_bools)]
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
pub struct AuditTransaction {
 | 
			
		||||
    pub uid: u32,
 | 
			
		||||
    order: u32,
 | 
			
		||||
    pub fee: u64,
 | 
			
		||||
    pub weight: u32,
 | 
			
		||||
    // exact sigop-adjusted weight
 | 
			
		||||
    pub sigop_adjusted_weight: u32,
 | 
			
		||||
    // sigop-adjusted vsize rounded up the the next integer
 | 
			
		||||
    pub sigop_adjusted_vsize: u32,
 | 
			
		||||
    pub sigops: u32,
 | 
			
		||||
    adjusted_fee_per_vsize: f64,
 | 
			
		||||
    pub effective_fee_per_vsize: f64,
 | 
			
		||||
    pub dependency_rate: f64,
 | 
			
		||||
    pub inputs: Vec<u32>,
 | 
			
		||||
    pub relatives_set_flag: bool,
 | 
			
		||||
    pub ancestors: HashSet<u32, U32HasherState>,
 | 
			
		||||
    pub children: HashSet<u32, U32HasherState>,
 | 
			
		||||
    ancestor_fee: u64,
 | 
			
		||||
    ancestor_sigop_adjusted_weight: u32,
 | 
			
		||||
    ancestor_sigop_adjusted_vsize: u32,
 | 
			
		||||
    ancestor_sigops: u32,
 | 
			
		||||
    // Safety: Must be private to prevent NaN breaking Ord impl.
 | 
			
		||||
    score: f64,
 | 
			
		||||
    pub used: bool,
 | 
			
		||||
    /// whether this transaction has been moved to the "modified" priority queue
 | 
			
		||||
    pub modified: bool,
 | 
			
		||||
    pub dirty: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Hash for AuditTransaction {
 | 
			
		||||
    fn hash<H: Hasher>(&self, state: &mut H) {
 | 
			
		||||
        self.uid.hash(state);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialEq for AuditTransaction {
 | 
			
		||||
    fn eq(&self, other: &Self) -> bool {
 | 
			
		||||
        self.uid == other.uid
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Eq for AuditTransaction {}
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
pub fn partial_cmp_uid_score(a: (u32, u32, f64), b: (u32, u32, f64)) -> Option<Ordering> {
 | 
			
		||||
    // If either score is NaN, this is false,
 | 
			
		||||
    // and partial_cmp will return None
 | 
			
		||||
    if a.2 != b.2 {
 | 
			
		||||
        // compare by score (sorts by ascending score)
 | 
			
		||||
        a.2.partial_cmp(&b.2)
 | 
			
		||||
    } else if a.1 != b.1 {
 | 
			
		||||
        // tie-break by comparing partial txids (sorts by descending txid)
 | 
			
		||||
        Some(b.1.cmp(&a.1))
 | 
			
		||||
    } else {
 | 
			
		||||
        // tie-break partial txid collisions by comparing uids (sorts by descending uid)
 | 
			
		||||
        Some(b.0.cmp(&a.0))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialOrd for AuditTransaction {
 | 
			
		||||
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 | 
			
		||||
        partial_cmp_uid_score(
 | 
			
		||||
            (self.uid, self.order, self.score),
 | 
			
		||||
            (other.uid, other.order, other.score),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Ord for AuditTransaction {
 | 
			
		||||
    fn cmp(&self, other: &Self) -> Ordering {
 | 
			
		||||
        // Safety: The only possible values for score are f64
 | 
			
		||||
        // that are not NaN. This is because outside code can not
 | 
			
		||||
        // freely assign score. Also, calc_new_score guarantees no NaN.
 | 
			
		||||
        self.partial_cmp(other).expect("score will never be NaN")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
fn calc_fee_rate(fee: f64, vsize: f64) -> f64 {
 | 
			
		||||
    fee / (if vsize == 0.0 { 1.0 } else { vsize })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AuditTransaction {
 | 
			
		||||
    pub fn from_thread_transaction(tx: &ThreadTransaction) -> Self {
 | 
			
		||||
        // rounded up to the nearest integer
 | 
			
		||||
        let is_adjusted = tx.weight < (tx.sigops * 20);
 | 
			
		||||
        let sigop_adjusted_vsize = ((tx.weight + 3) / 4).max(tx.sigops * 5);
 | 
			
		||||
        let sigop_adjusted_weight = tx.weight.max(tx.sigops * 20);
 | 
			
		||||
        let effective_fee_per_vsize = if is_adjusted {
 | 
			
		||||
            calc_fee_rate(tx.fee, f64::from(sigop_adjusted_weight) / 4.0)
 | 
			
		||||
        } else {
 | 
			
		||||
            tx.effective_fee_per_vsize
 | 
			
		||||
        };
 | 
			
		||||
        Self {
 | 
			
		||||
            uid: tx.uid,
 | 
			
		||||
            order: tx.order,
 | 
			
		||||
            fee: tx.fee as u64,
 | 
			
		||||
            weight: tx.weight,
 | 
			
		||||
            sigop_adjusted_weight,
 | 
			
		||||
            sigop_adjusted_vsize,
 | 
			
		||||
            sigops: tx.sigops,
 | 
			
		||||
            adjusted_fee_per_vsize: calc_fee_rate(tx.fee, f64::from(sigop_adjusted_vsize)),
 | 
			
		||||
            effective_fee_per_vsize,
 | 
			
		||||
            dependency_rate: f64::INFINITY,
 | 
			
		||||
            inputs: tx.inputs.clone(),
 | 
			
		||||
            relatives_set_flag: false,
 | 
			
		||||
            ancestors: u32hashset_new(),
 | 
			
		||||
            children: u32hashset_new(),
 | 
			
		||||
            ancestor_fee: tx.fee as u64,
 | 
			
		||||
            ancestor_sigop_adjusted_weight: sigop_adjusted_weight,
 | 
			
		||||
            ancestor_sigop_adjusted_vsize: sigop_adjusted_vsize,
 | 
			
		||||
            ancestor_sigops: tx.sigops,
 | 
			
		||||
            score: 0.0,
 | 
			
		||||
            used: false,
 | 
			
		||||
            modified: false,
 | 
			
		||||
            dirty: effective_fee_per_vsize != tx.effective_fee_per_vsize,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub const fn score(&self) -> f64 {
 | 
			
		||||
        self.score
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub const fn order(&self) -> u32 {
 | 
			
		||||
        self.order
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub const fn ancestor_sigop_adjusted_vsize(&self) -> u32 {
 | 
			
		||||
        self.ancestor_sigop_adjusted_vsize
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub const fn ancestor_sigops(&self) -> u32 {
 | 
			
		||||
        self.ancestor_sigops
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub fn cluster_rate(&self) -> f64 {
 | 
			
		||||
        // Safety: self.ancestor_weight can never be 0.
 | 
			
		||||
        // Even if it could, as it approaches 0, the value inside the min() call
 | 
			
		||||
        // grows, so if we think of 0 as "grew infinitely" then dependency_rate would be
 | 
			
		||||
        // the smaller of the two. If either side is NaN, the other side is returned.
 | 
			
		||||
        self.dependency_rate.min(calc_fee_rate(
 | 
			
		||||
            self.ancestor_fee as f64,
 | 
			
		||||
            f64::from(self.ancestor_sigop_adjusted_weight) / 4.0,
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_dirty_if_different(&mut self, cluster_rate: f64) {
 | 
			
		||||
        if self.effective_fee_per_vsize != cluster_rate {
 | 
			
		||||
            self.effective_fee_per_vsize = cluster_rate;
 | 
			
		||||
            self.dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Safety: This function must NEVER set score to NaN.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn calc_new_score(&mut self) {
 | 
			
		||||
        self.score = self.adjusted_fee_per_vsize.min(calc_fee_rate(
 | 
			
		||||
            self.ancestor_fee as f64,
 | 
			
		||||
            f64::from(self.ancestor_sigop_adjusted_vsize),
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub fn set_ancestors(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        ancestors: HashSet<u32, U32HasherState>,
 | 
			
		||||
        total_fee: u64,
 | 
			
		||||
        total_sigop_adjusted_weight: u32,
 | 
			
		||||
        total_sigop_adjusted_vsize: u32,
 | 
			
		||||
        total_sigops: u32,
 | 
			
		||||
    ) {
 | 
			
		||||
        self.ancestors = ancestors;
 | 
			
		||||
        self.ancestor_fee = self.fee + total_fee;
 | 
			
		||||
        self.ancestor_sigop_adjusted_weight =
 | 
			
		||||
            self.sigop_adjusted_weight + total_sigop_adjusted_weight;
 | 
			
		||||
        self.ancestor_sigop_adjusted_vsize = self.sigop_adjusted_vsize + total_sigop_adjusted_vsize;
 | 
			
		||||
        self.ancestor_sigops = self.sigops + total_sigops;
 | 
			
		||||
        self.calc_new_score();
 | 
			
		||||
        self.relatives_set_flag = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub fn remove_root(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        root_txid: u32,
 | 
			
		||||
        root_fee: u64,
 | 
			
		||||
        root_sigop_adjusted_weight: u32,
 | 
			
		||||
        root_sigop_adjusted_vsize: u32,
 | 
			
		||||
        root_sigops: u32,
 | 
			
		||||
        cluster_rate: f64,
 | 
			
		||||
    ) -> f64 {
 | 
			
		||||
        let old_score = self.score();
 | 
			
		||||
        self.dependency_rate = self.dependency_rate.min(cluster_rate);
 | 
			
		||||
        if self.ancestors.remove(&root_txid) {
 | 
			
		||||
            self.ancestor_fee -= root_fee;
 | 
			
		||||
            self.ancestor_sigop_adjusted_weight -= root_sigop_adjusted_weight;
 | 
			
		||||
            self.ancestor_sigop_adjusted_vsize -= root_sigop_adjusted_vsize;
 | 
			
		||||
            self.ancestor_sigops -= root_sigops;
 | 
			
		||||
            self.calc_new_score();
 | 
			
		||||
        }
 | 
			
		||||
        old_score
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										421
									
								
								backend/rust-gbt/src/gbt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								backend/rust-gbt/src/gbt.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,421 @@
 | 
			
		||||
use priority_queue::PriorityQueue;
 | 
			
		||||
use std::{cmp::Ordering, collections::HashSet, mem::ManuallyDrop};
 | 
			
		||||
use tracing::{info, trace};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    audit_transaction::{partial_cmp_uid_score, AuditTransaction},
 | 
			
		||||
    u32_hasher_types::{u32hashset_new, u32priority_queue_with_capacity, U32HasherState},
 | 
			
		||||
    GbtResult, ThreadTransactionsMap,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const MAX_BLOCK_WEIGHT_UNITS: u32 = 4_000_000 - 4_000;
 | 
			
		||||
const BLOCK_SIGOPS: u32 = 80_000;
 | 
			
		||||
const BLOCK_RESERVED_WEIGHT: u32 = 4_000;
 | 
			
		||||
const BLOCK_RESERVED_SIGOPS: u32 = 400;
 | 
			
		||||
const MAX_BLOCKS: usize = 8;
 | 
			
		||||
 | 
			
		||||
type AuditPool = Vec<Option<ManuallyDrop<AuditTransaction>>>;
 | 
			
		||||
type ModifiedQueue = PriorityQueue<u32, TxPriority, U32HasherState>;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct TxPriority {
 | 
			
		||||
    uid: u32,
 | 
			
		||||
    order: u32,
 | 
			
		||||
    score: f64,
 | 
			
		||||
}
 | 
			
		||||
impl PartialEq for TxPriority {
 | 
			
		||||
    fn eq(&self, other: &Self) -> bool {
 | 
			
		||||
        self.uid == other.uid
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl Eq for TxPriority {}
 | 
			
		||||
impl PartialOrd for TxPriority {
 | 
			
		||||
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 | 
			
		||||
        partial_cmp_uid_score(
 | 
			
		||||
            (self.uid, self.order, self.score),
 | 
			
		||||
            (other.uid, other.order, other.score),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl Ord for TxPriority {
 | 
			
		||||
    fn cmp(&self, other: &Self) -> Ordering {
 | 
			
		||||
        self.partial_cmp(other).expect("score will never be NaN")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core.
 | 
			
		||||
///
 | 
			
		||||
/// See `BlockAssembler` in Bitcoin Core's
 | 
			
		||||
/// [miner.cpp](https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp).
 | 
			
		||||
/// Ported from mempool backend's
 | 
			
		||||
/// [tx-selection-worker.ts](https://github.com/mempool/mempool/blob/master/backend/src/api/tx-selection-worker.ts).
 | 
			
		||||
//
 | 
			
		||||
// TODO: Make gbt smaller to fix these lints.
 | 
			
		||||
#[allow(clippy::too_many_lines)]
 | 
			
		||||
#[allow(clippy::cognitive_complexity)]
 | 
			
		||||
pub fn gbt(mempool: &mut ThreadTransactionsMap, max_uid: usize) -> GbtResult {
 | 
			
		||||
    let mempool_len = mempool.len();
 | 
			
		||||
    let mut audit_pool: AuditPool = Vec::with_capacity(max_uid + 1);
 | 
			
		||||
    audit_pool.resize(max_uid + 1, None);
 | 
			
		||||
    let mut mempool_stack: Vec<u32> = Vec::with_capacity(mempool_len);
 | 
			
		||||
    let mut clusters: Vec<Vec<u32>> = Vec::new();
 | 
			
		||||
    let mut block_weights: Vec<u32> = Vec::new();
 | 
			
		||||
 | 
			
		||||
    info!("Initializing working structs");
 | 
			
		||||
    for (uid, tx) in &mut *mempool {
 | 
			
		||||
        let audit_tx = AuditTransaction::from_thread_transaction(tx);
 | 
			
		||||
        // Safety: audit_pool and mempool_stack must always contain the same transactions
 | 
			
		||||
        audit_pool[*uid as usize] = Some(ManuallyDrop::new(audit_tx));
 | 
			
		||||
        mempool_stack.push(*uid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    info!("Building relatives graph & calculate ancestor scores");
 | 
			
		||||
    for txid in &mempool_stack {
 | 
			
		||||
        set_relatives(*txid, &mut audit_pool);
 | 
			
		||||
    }
 | 
			
		||||
    trace!("Post relative graph Audit Pool: {:#?}", audit_pool);
 | 
			
		||||
 | 
			
		||||
    info!("Sorting by descending ancestor score");
 | 
			
		||||
    let mut mempool_stack: Vec<(u32, u32, f64)> = mempool_stack
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .map(|txid| {
 | 
			
		||||
            let atx = audit_pool
 | 
			
		||||
                .get(txid as usize)
 | 
			
		||||
                .and_then(Option::as_ref)
 | 
			
		||||
                .expect("All txids are from audit_pool");
 | 
			
		||||
            (txid, atx.order(), atx.score())
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    mempool_stack.sort_unstable_by(|a, b| partial_cmp_uid_score(*a, *b).expect("Not NaN"));
 | 
			
		||||
    let mut mempool_stack: Vec<u32> = mempool_stack.into_iter().map(|(txid, _, _)| txid).collect();
 | 
			
		||||
 | 
			
		||||
    info!("Building blocks by greedily choosing the highest feerate package");
 | 
			
		||||
    info!("(i.e. the package rooted in the transaction with the best ancestor score)");
 | 
			
		||||
    let mut blocks: Vec<Vec<u32>> = Vec::new();
 | 
			
		||||
    let mut block_weight: u32 = BLOCK_RESERVED_WEIGHT;
 | 
			
		||||
    let mut block_sigops: u32 = BLOCK_RESERVED_SIGOPS;
 | 
			
		||||
    // No need to be bigger than 4096 transactions for the per-block transaction Vec.
 | 
			
		||||
    let initial_txes_per_block: usize = 4096.min(mempool_len);
 | 
			
		||||
    let mut transactions: Vec<u32> = Vec::with_capacity(initial_txes_per_block);
 | 
			
		||||
    let mut modified: ModifiedQueue = u32priority_queue_with_capacity(mempool_len);
 | 
			
		||||
    let mut overflow: Vec<u32> = Vec::new();
 | 
			
		||||
    let mut failures = 0;
 | 
			
		||||
    while !mempool_stack.is_empty() || !modified.is_empty() {
 | 
			
		||||
        // This trace log storm is big, so to make scrolling through
 | 
			
		||||
        // Each iteration easier, leaving a bunch of empty rows
 | 
			
		||||
        // And a header of ======
 | 
			
		||||
        trace!("\n\n\n\n\n\n\n\n\n\n==================================");
 | 
			
		||||
        trace!("mempool_array: {:#?}", mempool_stack);
 | 
			
		||||
        trace!("clusters: {:#?}", clusters);
 | 
			
		||||
        trace!("modified: {:#?}", modified);
 | 
			
		||||
        trace!("audit_pool: {:#?}", audit_pool);
 | 
			
		||||
        trace!("blocks: {:#?}", blocks);
 | 
			
		||||
        trace!("block_weight: {:#?}", block_weight);
 | 
			
		||||
        trace!("block_sigops: {:#?}", block_sigops);
 | 
			
		||||
        trace!("transactions: {:#?}", transactions);
 | 
			
		||||
        trace!("overflow: {:#?}", overflow);
 | 
			
		||||
        trace!("failures: {:#?}", failures);
 | 
			
		||||
        trace!("\n==================================");
 | 
			
		||||
 | 
			
		||||
        let next_from_stack = next_valid_from_stack(&mut mempool_stack, &audit_pool);
 | 
			
		||||
        let next_from_queue = next_valid_from_queue(&mut modified, &audit_pool);
 | 
			
		||||
        if next_from_stack.is_none() && next_from_queue.is_none() {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        let (next_tx, from_stack) = match (next_from_stack, next_from_queue) {
 | 
			
		||||
            (Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) {
 | 
			
		||||
                std::cmp::Ordering::Less => (stack_tx, true),
 | 
			
		||||
                _ => (queue_tx, false),
 | 
			
		||||
            },
 | 
			
		||||
            (Some(stack_tx), None) => (stack_tx, true),
 | 
			
		||||
            (None, Some(queue_tx)) => (queue_tx, false),
 | 
			
		||||
            (None, None) => unreachable!(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if from_stack {
 | 
			
		||||
            mempool_stack.pop();
 | 
			
		||||
        } else {
 | 
			
		||||
            modified.pop();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if blocks.len() < (MAX_BLOCKS - 1)
 | 
			
		||||
            && ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize())
 | 
			
		||||
                >= MAX_BLOCK_WEIGHT_UNITS)
 | 
			
		||||
                || (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS))
 | 
			
		||||
        {
 | 
			
		||||
            // hold this package in an overflow list while we check for smaller options
 | 
			
		||||
            overflow.push(next_tx.uid);
 | 
			
		||||
            failures += 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            let mut package: Vec<(u32, u32, usize)> = Vec::new();
 | 
			
		||||
            let mut cluster: Vec<u32> = Vec::new();
 | 
			
		||||
            let is_cluster: bool = !next_tx.ancestors.is_empty();
 | 
			
		||||
            for ancestor_id in &next_tx.ancestors {
 | 
			
		||||
                if let Some(Some(ancestor)) = audit_pool.get(*ancestor_id as usize) {
 | 
			
		||||
                    package.push((*ancestor_id, ancestor.order(), ancestor.ancestors.len()));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            package.sort_unstable_by(|a, b| -> Ordering {
 | 
			
		||||
                if a.2 != b.2 {
 | 
			
		||||
                    // order by ascending ancestor count
 | 
			
		||||
                    a.2.cmp(&b.2)
 | 
			
		||||
                } else if a.1 != b.1 {
 | 
			
		||||
                    // tie-break by ascending partial txid
 | 
			
		||||
                    a.1.cmp(&b.1)
 | 
			
		||||
                } else {
 | 
			
		||||
                    // tie-break partial txid collisions by ascending uid
 | 
			
		||||
                    a.0.cmp(&b.0)
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            package.push((next_tx.uid, next_tx.order(), next_tx.ancestors.len()));
 | 
			
		||||
 | 
			
		||||
            let cluster_rate = next_tx.cluster_rate();
 | 
			
		||||
 | 
			
		||||
            for (txid, _, _) in &package {
 | 
			
		||||
                cluster.push(*txid);
 | 
			
		||||
                if let Some(Some(tx)) = audit_pool.get_mut(*txid as usize) {
 | 
			
		||||
                    tx.used = true;
 | 
			
		||||
                    tx.set_dirty_if_different(cluster_rate);
 | 
			
		||||
                    transactions.push(tx.uid);
 | 
			
		||||
                    block_weight += tx.weight;
 | 
			
		||||
                    block_sigops += tx.sigops;
 | 
			
		||||
                }
 | 
			
		||||
                update_descendants(*txid, &mut audit_pool, &mut modified, cluster_rate);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if is_cluster {
 | 
			
		||||
                clusters.push(cluster);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            failures = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // this block is full
 | 
			
		||||
        let exceeded_package_tries =
 | 
			
		||||
            failures > 1000 && block_weight > (MAX_BLOCK_WEIGHT_UNITS - BLOCK_RESERVED_WEIGHT);
 | 
			
		||||
        let queue_is_empty = mempool_stack.is_empty() && modified.is_empty();
 | 
			
		||||
        if (exceeded_package_tries || queue_is_empty) && blocks.len() < (MAX_BLOCKS - 1) {
 | 
			
		||||
            // finalize this block
 | 
			
		||||
            if !transactions.is_empty() {
 | 
			
		||||
                blocks.push(transactions);
 | 
			
		||||
                block_weights.push(block_weight);
 | 
			
		||||
            }
 | 
			
		||||
            // reset for the next block
 | 
			
		||||
            transactions = Vec::with_capacity(initial_txes_per_block);
 | 
			
		||||
            block_weight = BLOCK_RESERVED_WEIGHT;
 | 
			
		||||
            block_sigops = BLOCK_RESERVED_SIGOPS;
 | 
			
		||||
            failures = 0;
 | 
			
		||||
            // 'overflow' packages didn't fit in this block, but are valid candidates for the next
 | 
			
		||||
            overflow.reverse();
 | 
			
		||||
            for overflowed in &overflow {
 | 
			
		||||
                if let Some(Some(overflowed_tx)) = audit_pool.get(*overflowed as usize) {
 | 
			
		||||
                    if overflowed_tx.modified {
 | 
			
		||||
                        modified.push(
 | 
			
		||||
                            *overflowed,
 | 
			
		||||
                            TxPriority {
 | 
			
		||||
                                uid: *overflowed,
 | 
			
		||||
                                order: overflowed_tx.order(),
 | 
			
		||||
                                score: overflowed_tx.score(),
 | 
			
		||||
                            },
 | 
			
		||||
                        );
 | 
			
		||||
                    } else {
 | 
			
		||||
                        mempool_stack.push(*overflowed);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            overflow = Vec::new();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    info!("add the final unbounded block if it contains any transactions");
 | 
			
		||||
    if !transactions.is_empty() {
 | 
			
		||||
        blocks.push(transactions);
 | 
			
		||||
        block_weights.push(block_weight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    info!("make a list of dirty transactions and their new rates");
 | 
			
		||||
    let mut rates: Vec<Vec<f64>> = Vec::new();
 | 
			
		||||
    for (uid, thread_tx) in mempool {
 | 
			
		||||
        // Takes ownership of the audit_tx and replaces with None
 | 
			
		||||
        if let Some(Some(audit_tx)) = audit_pool.get_mut(*uid as usize).map(Option::take) {
 | 
			
		||||
            trace!("txid: {}, is_dirty: {}", uid, audit_tx.dirty);
 | 
			
		||||
            if audit_tx.dirty {
 | 
			
		||||
                rates.push(vec![f64::from(*uid), audit_tx.effective_fee_per_vsize]);
 | 
			
		||||
                thread_tx.effective_fee_per_vsize = audit_tx.effective_fee_per_vsize;
 | 
			
		||||
            }
 | 
			
		||||
            // Drops the AuditTransaction manually
 | 
			
		||||
            // There are no audit_txs that are not in the mempool HashMap
 | 
			
		||||
            // So there is guaranteed to be no memory leaks.
 | 
			
		||||
            ManuallyDrop::into_inner(audit_tx);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    trace!("\n\n\n\n\n====================");
 | 
			
		||||
    trace!("blocks: {:#?}", blocks);
 | 
			
		||||
    trace!("clusters: {:#?}", clusters);
 | 
			
		||||
    trace!("rates: {:#?}\n====================\n\n\n\n\n", rates);
 | 
			
		||||
 | 
			
		||||
    GbtResult {
 | 
			
		||||
        blocks,
 | 
			
		||||
        block_weights,
 | 
			
		||||
        clusters,
 | 
			
		||||
        rates,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn next_valid_from_stack<'a>(
 | 
			
		||||
    mempool_stack: &mut Vec<u32>,
 | 
			
		||||
    audit_pool: &'a AuditPool,
 | 
			
		||||
) -> Option<&'a AuditTransaction> {
 | 
			
		||||
    while let Some(next_txid) = mempool_stack.last() {
 | 
			
		||||
        match audit_pool.get(*next_txid as usize) {
 | 
			
		||||
            Some(Some(tx)) if !tx.used && !tx.modified => {
 | 
			
		||||
                return Some(tx);
 | 
			
		||||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                mempool_stack.pop();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn next_valid_from_queue<'a>(
 | 
			
		||||
    queue: &mut ModifiedQueue,
 | 
			
		||||
    audit_pool: &'a AuditPool,
 | 
			
		||||
) -> Option<&'a AuditTransaction> {
 | 
			
		||||
    while let Some((next_txid, _)) = queue.peek() {
 | 
			
		||||
        match audit_pool.get(*next_txid as usize) {
 | 
			
		||||
            Some(Some(tx)) if !tx.used => {
 | 
			
		||||
                return Some(tx);
 | 
			
		||||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                queue.pop();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn set_relatives(txid: u32, audit_pool: &mut AuditPool) {
 | 
			
		||||
    let mut parents: HashSet<u32, U32HasherState> = u32hashset_new();
 | 
			
		||||
    if let Some(Some(tx)) = audit_pool.get(txid as usize) {
 | 
			
		||||
        if tx.relatives_set_flag {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        for input in &tx.inputs {
 | 
			
		||||
            parents.insert(*input);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut ancestors: HashSet<u32, U32HasherState> = u32hashset_new();
 | 
			
		||||
    for parent_id in &parents {
 | 
			
		||||
        set_relatives(*parent_id, audit_pool);
 | 
			
		||||
 | 
			
		||||
        if let Some(Some(parent)) = audit_pool.get_mut(*parent_id as usize) {
 | 
			
		||||
            // Safety: ancestors must always contain only txes in audit_pool
 | 
			
		||||
            ancestors.insert(*parent_id);
 | 
			
		||||
            parent.children.insert(txid);
 | 
			
		||||
            for ancestor in &parent.ancestors {
 | 
			
		||||
                ancestors.insert(*ancestor);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut total_fee: u64 = 0;
 | 
			
		||||
    let mut total_sigop_adjusted_weight: u32 = 0;
 | 
			
		||||
    let mut total_sigop_adjusted_vsize: u32 = 0;
 | 
			
		||||
    let mut total_sigops: u32 = 0;
 | 
			
		||||
 | 
			
		||||
    for ancestor_id in &ancestors {
 | 
			
		||||
        let Some(ancestor) = audit_pool
 | 
			
		||||
            .get(*ancestor_id as usize)
 | 
			
		||||
            .expect("audit_pool contains all ancestors") else { todo!() };
 | 
			
		||||
        total_fee += ancestor.fee;
 | 
			
		||||
        total_sigop_adjusted_weight += ancestor.sigop_adjusted_weight;
 | 
			
		||||
        total_sigop_adjusted_vsize += ancestor.sigop_adjusted_vsize;
 | 
			
		||||
        total_sigops += ancestor.sigops;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(Some(tx)) = audit_pool.get_mut(txid as usize) {
 | 
			
		||||
        tx.set_ancestors(
 | 
			
		||||
            ancestors,
 | 
			
		||||
            total_fee,
 | 
			
		||||
            total_sigop_adjusted_weight,
 | 
			
		||||
            total_sigop_adjusted_vsize,
 | 
			
		||||
            total_sigops,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// iterate over remaining descendants, removing the root as a valid ancestor & updating the ancestor score
 | 
			
		||||
fn update_descendants(
 | 
			
		||||
    root_txid: u32,
 | 
			
		||||
    audit_pool: &mut AuditPool,
 | 
			
		||||
    modified: &mut ModifiedQueue,
 | 
			
		||||
    cluster_rate: f64,
 | 
			
		||||
) {
 | 
			
		||||
    let mut visited: HashSet<u32, U32HasherState> = u32hashset_new();
 | 
			
		||||
    let mut descendant_stack: Vec<u32> = Vec::new();
 | 
			
		||||
    let root_fee: u64;
 | 
			
		||||
    let root_sigop_adjusted_weight: u32;
 | 
			
		||||
    let root_sigop_adjusted_vsize: u32;
 | 
			
		||||
    let root_sigops: u32;
 | 
			
		||||
    if let Some(Some(root_tx)) = audit_pool.get(root_txid as usize) {
 | 
			
		||||
        for descendant_id in &root_tx.children {
 | 
			
		||||
            if !visited.contains(descendant_id) {
 | 
			
		||||
                descendant_stack.push(*descendant_id);
 | 
			
		||||
                visited.insert(*descendant_id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        root_fee = root_tx.fee;
 | 
			
		||||
        root_sigop_adjusted_weight = root_tx.sigop_adjusted_weight;
 | 
			
		||||
        root_sigop_adjusted_vsize = root_tx.sigop_adjusted_vsize;
 | 
			
		||||
        root_sigops = root_tx.sigops;
 | 
			
		||||
    } else {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    while let Some(next_txid) = descendant_stack.pop() {
 | 
			
		||||
        if let Some(Some(descendant)) = audit_pool.get_mut(next_txid as usize) {
 | 
			
		||||
            // remove root tx as ancestor
 | 
			
		||||
            let old_score = descendant.remove_root(
 | 
			
		||||
                root_txid,
 | 
			
		||||
                root_fee,
 | 
			
		||||
                root_sigop_adjusted_weight,
 | 
			
		||||
                root_sigop_adjusted_vsize,
 | 
			
		||||
                root_sigops,
 | 
			
		||||
                cluster_rate,
 | 
			
		||||
            );
 | 
			
		||||
            // add to priority queue or update priority if score has changed
 | 
			
		||||
            if descendant.score() < old_score {
 | 
			
		||||
                descendant.modified = true;
 | 
			
		||||
                modified.push_decrease(
 | 
			
		||||
                    descendant.uid,
 | 
			
		||||
                    TxPriority {
 | 
			
		||||
                        uid: descendant.uid,
 | 
			
		||||
                        order: descendant.order(),
 | 
			
		||||
                        score: descendant.score(),
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            } else if descendant.score() > old_score {
 | 
			
		||||
                descendant.modified = true;
 | 
			
		||||
                modified.push_increase(
 | 
			
		||||
                    descendant.uid,
 | 
			
		||||
                    TxPriority {
 | 
			
		||||
                        uid: descendant.uid,
 | 
			
		||||
                        order: descendant.order(),
 | 
			
		||||
                        score: descendant.score(),
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // add this node's children to the stack
 | 
			
		||||
            for child_id in &descendant.children {
 | 
			
		||||
                if !visited.contains(child_id) {
 | 
			
		||||
                    descendant_stack.push(*child_id);
 | 
			
		||||
                    visited.insert(*child_id);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										177
									
								
								backend/rust-gbt/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								backend/rust-gbt/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,177 @@
 | 
			
		||||
#![warn(clippy::all)]
 | 
			
		||||
#![warn(clippy::pedantic)]
 | 
			
		||||
#![warn(clippy::nursery)]
 | 
			
		||||
#![allow(clippy::cast_precision_loss)]
 | 
			
		||||
#![allow(clippy::cast_possible_truncation)]
 | 
			
		||||
#![allow(clippy::cast_sign_loss)]
 | 
			
		||||
#![allow(clippy::float_cmp)]
 | 
			
		||||
 | 
			
		||||
use napi::bindgen_prelude::Result;
 | 
			
		||||
use napi_derive::napi;
 | 
			
		||||
use thread_transaction::ThreadTransaction;
 | 
			
		||||
use tracing::{debug, info, trace};
 | 
			
		||||
use tracing_log::LogTracer;
 | 
			
		||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
 | 
			
		||||
mod audit_transaction;
 | 
			
		||||
mod gbt;
 | 
			
		||||
mod thread_transaction;
 | 
			
		||||
mod u32_hasher_types;
 | 
			
		||||
 | 
			
		||||
use u32_hasher_types::{u32hashmap_with_capacity, U32HasherState};
 | 
			
		||||
 | 
			
		||||
/// This is the initial capacity of the `GbtGenerator` struct's inner `HashMap`.
 | 
			
		||||
///
 | 
			
		||||
/// Note: This doesn't *have* to be a power of 2. (uwu)
 | 
			
		||||
const STARTING_CAPACITY: usize = 1_048_576;
 | 
			
		||||
 | 
			
		||||
type ThreadTransactionsMap = HashMap<u32, ThreadTransaction, U32HasherState>;
 | 
			
		||||
 | 
			
		||||
#[napi]
 | 
			
		||||
pub struct GbtGenerator {
 | 
			
		||||
    thread_transactions: Arc<Mutex<ThreadTransactionsMap>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[napi::module_init]
 | 
			
		||||
fn init() {
 | 
			
		||||
    // Set all `tracing` logs to print to STDOUT
 | 
			
		||||
    // Note: Passing RUST_LOG env variable to the node process
 | 
			
		||||
    //       will change the log level for the rust module.
 | 
			
		||||
    tracing::subscriber::set_global_default(
 | 
			
		||||
        FmtSubscriber::builder()
 | 
			
		||||
            .with_env_filter(EnvFilter::from_default_env())
 | 
			
		||||
            .with_ansi(
 | 
			
		||||
                // Default to no-color logs.
 | 
			
		||||
                // Setting RUST_LOG_COLOR to 1 or true|TRUE|True etc.
 | 
			
		||||
                // will enable color
 | 
			
		||||
                std::env::var("RUST_LOG_COLOR")
 | 
			
		||||
                    .map(|s| ["1", "true"].contains(&&*s.to_lowercase()))
 | 
			
		||||
                    .unwrap_or(false),
 | 
			
		||||
            )
 | 
			
		||||
            .finish(),
 | 
			
		||||
    )
 | 
			
		||||
    .expect("Logging subscriber failed");
 | 
			
		||||
    // Convert all `log` logs into `tracing` events
 | 
			
		||||
    LogTracer::init().expect("Legacy log subscriber failed");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[napi]
 | 
			
		||||
impl GbtGenerator {
 | 
			
		||||
    #[napi(constructor)]
 | 
			
		||||
    #[allow(clippy::new_without_default)]
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        debug!("Created new GbtGenerator");
 | 
			
		||||
        Self {
 | 
			
		||||
            thread_transactions: Arc::new(Mutex::new(u32hashmap_with_capacity(STARTING_CAPACITY))),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    ///
 | 
			
		||||
    /// Rejects if the thread panics or if the Mutex is poisoned.
 | 
			
		||||
    #[napi]
 | 
			
		||||
    pub async fn make(&self, mempool: Vec<ThreadTransaction>, max_uid: u32) -> Result<GbtResult> {
 | 
			
		||||
        trace!("make: Current State {:#?}", self.thread_transactions);
 | 
			
		||||
        run_task(
 | 
			
		||||
            Arc::clone(&self.thread_transactions),
 | 
			
		||||
            max_uid as usize,
 | 
			
		||||
            move |map| {
 | 
			
		||||
                for tx in mempool {
 | 
			
		||||
                    map.insert(tx.uid, tx);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    ///
 | 
			
		||||
    /// Rejects if the thread panics or if the Mutex is poisoned.
 | 
			
		||||
    #[napi]
 | 
			
		||||
    pub async fn update(
 | 
			
		||||
        &self,
 | 
			
		||||
        new_txs: Vec<ThreadTransaction>,
 | 
			
		||||
        remove_txs: Vec<u32>,
 | 
			
		||||
        max_uid: u32,
 | 
			
		||||
    ) -> Result<GbtResult> {
 | 
			
		||||
        trace!("update: Current State {:#?}", self.thread_transactions);
 | 
			
		||||
        run_task(
 | 
			
		||||
            Arc::clone(&self.thread_transactions),
 | 
			
		||||
            max_uid as usize,
 | 
			
		||||
            move |map| {
 | 
			
		||||
                for tx in new_txs {
 | 
			
		||||
                    map.insert(tx.uid, tx);
 | 
			
		||||
                }
 | 
			
		||||
                for txid in &remove_txs {
 | 
			
		||||
                    map.remove(txid);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The result from calling the gbt function.
 | 
			
		||||
///
 | 
			
		||||
/// This tuple contains the following:
 | 
			
		||||
///        blocks: A 2D Vector of transaction IDs (u32), the inner Vecs each represent a block.
 | 
			
		||||
/// block_weights: A Vector of total weights per block.
 | 
			
		||||
///      clusters: A 2D Vector of transaction IDs representing clusters of dependent mempool transactions
 | 
			
		||||
///         rates: A Vector of tuples containing transaction IDs (u32) and effective fee per vsize (f64)
 | 
			
		||||
#[napi(constructor)]
 | 
			
		||||
pub struct GbtResult {
 | 
			
		||||
    pub blocks: Vec<Vec<u32>>,
 | 
			
		||||
    pub block_weights: Vec<u32>,
 | 
			
		||||
    pub clusters: Vec<Vec<u32>>,
 | 
			
		||||
    pub rates: Vec<Vec<f64>>, // Tuples not supported. u32 fits inside f64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// All on another thread, this runs an arbitrary task in between
 | 
			
		||||
/// taking the lock and running gbt.
 | 
			
		||||
///
 | 
			
		||||
/// Rather than filling / updating the `HashMap` on the main thread,
 | 
			
		||||
/// this allows for `HashMap` modifying tasks to be run before running and returning gbt results.
 | 
			
		||||
///
 | 
			
		||||
/// `thread_transactions` is a cloned `Arc` of the `Mutex` for the `HashMap` state.
 | 
			
		||||
/// `callback` is a `'static + Send` `FnOnce` closure/function that takes a mutable reference
 | 
			
		||||
/// to the `HashMap` as the only argument. (A move closure is recommended to meet the bounds)
 | 
			
		||||
async fn run_task<F>(
 | 
			
		||||
    thread_transactions: Arc<Mutex<ThreadTransactionsMap>>,
 | 
			
		||||
    max_uid: usize,
 | 
			
		||||
    callback: F,
 | 
			
		||||
) -> Result<GbtResult>
 | 
			
		||||
where
 | 
			
		||||
    F: FnOnce(&mut ThreadTransactionsMap) + Send + 'static,
 | 
			
		||||
{
 | 
			
		||||
    debug!("Spawning thread...");
 | 
			
		||||
    let handle = napi::tokio::task::spawn_blocking(move || {
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Getting lock for thread_transactions from thread {:?}...",
 | 
			
		||||
            std::thread::current().id()
 | 
			
		||||
        );
 | 
			
		||||
        let mut map = thread_transactions
 | 
			
		||||
            .lock()
 | 
			
		||||
            .map_err(|_| napi::Error::from_reason("THREAD_TRANSACTIONS Mutex poisoned"))?;
 | 
			
		||||
        callback(&mut map);
 | 
			
		||||
 | 
			
		||||
        info!("Starting gbt algorithm for {} elements...", map.len());
 | 
			
		||||
        let result = gbt::gbt(&mut map, max_uid);
 | 
			
		||||
        info!("Finished gbt algorithm for {} elements...", map.len());
 | 
			
		||||
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Releasing lock for thread_transactions from thread {:?}...",
 | 
			
		||||
            std::thread::current().id()
 | 
			
		||||
        );
 | 
			
		||||
        drop(map);
 | 
			
		||||
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    handle
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|_| napi::Error::from_reason("thread panicked"))?
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								backend/rust-gbt/src/thread_transaction.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/rust-gbt/src/thread_transaction.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
use napi_derive::napi;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[napi(object)]
 | 
			
		||||
pub struct ThreadTransaction {
 | 
			
		||||
    pub uid: u32,
 | 
			
		||||
    pub order: u32,
 | 
			
		||||
    pub fee: f64,
 | 
			
		||||
    pub weight: u32,
 | 
			
		||||
    pub sigops: u32,
 | 
			
		||||
    pub effective_fee_per_vsize: f64,
 | 
			
		||||
    pub inputs: Vec<u32>,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								backend/rust-gbt/src/u32_hasher_types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								backend/rust-gbt/src/u32_hasher_types.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
			
		||||
use priority_queue::PriorityQueue;
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::{HashMap, HashSet},
 | 
			
		||||
    fmt::Debug,
 | 
			
		||||
    hash::{BuildHasher, Hasher},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// This is the only way to create a `HashMap` with the `U32HasherState` and capacity
 | 
			
		||||
pub fn u32hashmap_with_capacity<V>(capacity: usize) -> HashMap<u32, V, U32HasherState> {
 | 
			
		||||
    HashMap::with_capacity_and_hasher(capacity, U32HasherState(()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// This is the only way to create a `PriorityQueue` with the `U32HasherState` and capacity
 | 
			
		||||
pub fn u32priority_queue_with_capacity<V: Ord>(
 | 
			
		||||
    capacity: usize,
 | 
			
		||||
) -> PriorityQueue<u32, V, U32HasherState> {
 | 
			
		||||
    PriorityQueue::with_capacity_and_hasher(capacity, U32HasherState(()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// This is the only way to create a `HashSet` with the `U32HasherState`
 | 
			
		||||
pub fn u32hashset_new() -> HashSet<u32, U32HasherState> {
 | 
			
		||||
    HashSet::with_hasher(U32HasherState(()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A private unit type is contained so no one can make an instance of it.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct U32HasherState(());
 | 
			
		||||
 | 
			
		||||
impl Debug for U32HasherState {
 | 
			
		||||
    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BuildHasher for U32HasherState {
 | 
			
		||||
    type Hasher = U32Hasher;
 | 
			
		||||
 | 
			
		||||
    fn build_hasher(&self) -> Self::Hasher {
 | 
			
		||||
        U32Hasher(0)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// This also can't be created outside this module due to private field.
 | 
			
		||||
pub struct U32Hasher(u32);
 | 
			
		||||
 | 
			
		||||
impl Hasher for U32Hasher {
 | 
			
		||||
    fn finish(&self) -> u64 {
 | 
			
		||||
        // Safety: Two u32s next to each other will make a u64
 | 
			
		||||
        bytemuck::cast([self.0, 0])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write(&mut self, bytes: &[u8]) {
 | 
			
		||||
        // Assert in debug builds (testing too) that only 4 byte keys (u32, i32, f32, etc.) run
 | 
			
		||||
        debug_assert!(bytes.len() == 4);
 | 
			
		||||
        // Safety: We know that the size of the key is 4 bytes
 | 
			
		||||
        // We also know that the only way to get an instance of HashMap using this "hasher"
 | 
			
		||||
        // is through the public functions in this module which set the key type to u32.
 | 
			
		||||
        self.0 = *bytemuck::from_bytes(bytes);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::U32HasherState;
 | 
			
		||||
    use priority_queue::PriorityQueue;
 | 
			
		||||
    use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_hashmap() {
 | 
			
		||||
        let mut hm: HashMap<u32, String, U32HasherState> = HashMap::with_hasher(U32HasherState(()));
 | 
			
		||||
 | 
			
		||||
        // Testing basic operations with the custom hasher
 | 
			
		||||
        hm.insert(0, String::from("0"));
 | 
			
		||||
        hm.insert(42, String::from("42"));
 | 
			
		||||
        hm.insert(256, String::from("256"));
 | 
			
		||||
        hm.insert(u32::MAX, String::from("MAX"));
 | 
			
		||||
        hm.insert(u32::MAX >> 2, String::from("MAX >> 2"));
 | 
			
		||||
 | 
			
		||||
        assert_eq!(hm.get(&0), Some(&String::from("0")));
 | 
			
		||||
        assert_eq!(hm.get(&42), Some(&String::from("42")));
 | 
			
		||||
        assert_eq!(hm.get(&256), Some(&String::from("256")));
 | 
			
		||||
        assert_eq!(hm.get(&u32::MAX), Some(&String::from("MAX")));
 | 
			
		||||
        assert_eq!(hm.get(&(u32::MAX >> 2)), Some(&String::from("MAX >> 2")));
 | 
			
		||||
        assert_eq!(hm.get(&(u32::MAX >> 4)), None);
 | 
			
		||||
        assert_eq!(hm.get(&3), None);
 | 
			
		||||
        assert_eq!(hm.get(&43), None);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_priority_queue() {
 | 
			
		||||
        let mut pq: PriorityQueue<u32, i32, U32HasherState> =
 | 
			
		||||
            PriorityQueue::with_hasher(U32HasherState(()));
 | 
			
		||||
 | 
			
		||||
        // Testing basic operations with the custom hasher
 | 
			
		||||
        assert_eq!(pq.push(1, 5), None);
 | 
			
		||||
        assert_eq!(pq.push(2, -10), None);
 | 
			
		||||
        assert_eq!(pq.push(3, 7), None);
 | 
			
		||||
        assert_eq!(pq.push(4, 20), None);
 | 
			
		||||
        assert_eq!(pq.push(u32::MAX, -42), None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(pq.push_increase(1, 4), Some(4));
 | 
			
		||||
        assert_eq!(pq.push_increase(2, -8), Some(-10));
 | 
			
		||||
        assert_eq!(pq.push_increase(3, 5), Some(5));
 | 
			
		||||
        assert_eq!(pq.push_increase(4, 21), Some(20));
 | 
			
		||||
        assert_eq!(pq.push_increase(u32::MAX, -99), Some(-99));
 | 
			
		||||
        assert_eq!(pq.push_increase(42, 1337), None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(pq.push_decrease(1, 4), Some(5));
 | 
			
		||||
        assert_eq!(pq.push_decrease(2, -10), Some(-8));
 | 
			
		||||
        assert_eq!(pq.push_decrease(3, 5), Some(7));
 | 
			
		||||
        assert_eq!(pq.push_decrease(4, 20), Some(21));
 | 
			
		||||
        assert_eq!(pq.push_decrease(u32::MAX, 100), Some(100));
 | 
			
		||||
        assert_eq!(pq.push_decrease(69, 420), None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(pq.peek(), Some((&42, &1337)));
 | 
			
		||||
        assert_eq!(pq.pop(), Some((42, 1337)));
 | 
			
		||||
        assert_eq!(pq.peek(), Some((&69, &420)));
 | 
			
		||||
        assert_eq!(pq.pop(), Some((69, 420)));
 | 
			
		||||
        assert_eq!(pq.peek(), Some((&4, &20)));
 | 
			
		||||
        assert_eq!(pq.pop(), Some((4, 20)));
 | 
			
		||||
        assert_eq!(pq.peek(), Some((&3, &5)));
 | 
			
		||||
        assert_eq!(pq.pop(), Some((3, 5)));
 | 
			
		||||
        assert_eq!(pq.peek(), Some((&1, &4)));
 | 
			
		||||
        assert_eq!(pq.pop(), Some((1, 4)));
 | 
			
		||||
        assert_eq!(pq.peek(), Some((&2, &-10)));
 | 
			
		||||
        assert_eq!(pq.pop(), Some((2, -10)));
 | 
			
		||||
        assert_eq!(pq.peek(), Some((&u32::MAX, &-42)));
 | 
			
		||||
        assert_eq!(pq.pop(), Some((u32::MAX, -42)));
 | 
			
		||||
        assert_eq!(pq.peek(), None);
 | 
			
		||||
        assert_eq!(pq.pop(), None);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -27,6 +27,7 @@
 | 
			
		||||
    "AUDIT": true,
 | 
			
		||||
    "ADVANCED_GBT_AUDIT": true,
 | 
			
		||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
			
		||||
    "RUST_GBT": false,
 | 
			
		||||
    "CPFP_INDEXING": true,
 | 
			
		||||
    "MAX_BLOCKS_BULK_QUERY": 999,
 | 
			
		||||
    "DISK_CACHE_BLOCK_INTERVAL": 999
 | 
			
		||||
 | 
			
		||||
@ -40,6 +40,7 @@ describe('Mempool Backend Config', () => {
 | 
			
		||||
        AUDIT: false,
 | 
			
		||||
        ADVANCED_GBT_AUDIT: false,
 | 
			
		||||
        ADVANCED_GBT_MEMPOOL: false,
 | 
			
		||||
        RUST_GBT: false,
 | 
			
		||||
        CPFP_INDEXING: false,
 | 
			
		||||
        MAX_BLOCKS_BULK_QUERY: 0,
 | 
			
		||||
        DISK_CACHE_BLOCK_INTERVAL: 6,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										68
									
								
								backend/src/__tests__/gbt/gbt-tests.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								backend/src/__tests__/gbt/gbt-tests.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
import fs from 'fs';
 | 
			
		||||
import { GbtGenerator, ThreadTransaction } from '../../../rust-gbt';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
const baseline = require('./test-data/target-template.json');
 | 
			
		||||
const testVector = require('./test-data/test-data-ids.json');
 | 
			
		||||
const vectorUidMap: Map<number, string> = new Map(testVector.map(x => [x[0], x[1]]));
 | 
			
		||||
const vectorTxidMap: Map<string, number>  = new Map(testVector.map(x => [x[1], x[0]]));
 | 
			
		||||
// Note that this test buffer is specially constructed
 | 
			
		||||
// such that uids are assigned in numerical txid order
 | 
			
		||||
// so that ties break the same way as in Core's implementation
 | 
			
		||||
const vectorBuffer: Buffer = fs.readFileSync(path.join(__dirname, './', './test-data/test-buffer.bin'));
 | 
			
		||||
 | 
			
		||||
describe('Rust GBT', () => {
 | 
			
		||||
  test('should produce the same template as getBlockTemplate from Bitcoin Core', async () => {
 | 
			
		||||
    const rustGbt = new GbtGenerator();
 | 
			
		||||
    const { mempool, maxUid } = mempoolFromArrayBuffer(vectorBuffer.buffer);
 | 
			
		||||
    const result = await rustGbt.make(mempool, maxUid);
 | 
			
		||||
 | 
			
		||||
    const blocks: [string, number][][] = result.blocks.map(block => {
 | 
			
		||||
      return block.map(uid => [vectorUidMap.get(uid) || 'missing', uid]);
 | 
			
		||||
    });
 | 
			
		||||
    const template = baseline.map(tx => [tx.txid, vectorTxidMap.get(tx.txid)]);
 | 
			
		||||
 | 
			
		||||
    expect(blocks[0].length).toEqual(baseline.length);
 | 
			
		||||
    expect(blocks[0]).toEqual(template);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function mempoolFromArrayBuffer(buf: ArrayBuffer): { mempool: ThreadTransaction[], maxUid: number } {
 | 
			
		||||
  let maxUid = 0;
 | 
			
		||||
  const view = new DataView(buf);
 | 
			
		||||
  const count = view.getUint32(0, false);
 | 
			
		||||
  const txs: ThreadTransaction[] = [];
 | 
			
		||||
  let offset = 4;
 | 
			
		||||
  for (let i = 0; i < count; i++) {
 | 
			
		||||
    const uid = view.getUint32(offset, false);
 | 
			
		||||
    maxUid = Math.max(maxUid, uid);
 | 
			
		||||
    const tx: ThreadTransaction = {
 | 
			
		||||
      uid,
 | 
			
		||||
      order: txidToOrdering(vectorUidMap.get(uid) as string),
 | 
			
		||||
      fee: view.getFloat64(offset + 4, false),
 | 
			
		||||
      weight: view.getUint32(offset + 12, false),
 | 
			
		||||
      sigops: view.getUint32(offset + 16, false),
 | 
			
		||||
      // feePerVsize: view.getFloat64(offset + 20, false),
 | 
			
		||||
      effectiveFeePerVsize: view.getFloat64(offset + 28, false),
 | 
			
		||||
      inputs: [],
 | 
			
		||||
    };
 | 
			
		||||
    const numInputs = view.getUint32(offset + 36, false);
 | 
			
		||||
    offset += 40;
 | 
			
		||||
    for (let j = 0; j < numInputs; j++) {
 | 
			
		||||
      tx.inputs.push(view.getUint32(offset, false));
 | 
			
		||||
      offset += 4;
 | 
			
		||||
    }
 | 
			
		||||
    txs.push(tx);
 | 
			
		||||
  }
 | 
			
		||||
  return { mempool: txs, maxUid };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function txidToOrdering(txid: string): number {
 | 
			
		||||
  return parseInt(
 | 
			
		||||
    txid.substr(62, 2) +
 | 
			
		||||
      txid.substr(60, 2) +
 | 
			
		||||
      txid.substr(58, 2) +
 | 
			
		||||
      txid.substr(56, 2),
 | 
			
		||||
    16
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7070
									
								
								backend/src/__tests__/gbt/test-data/target-template.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7070
									
								
								backend/src/__tests__/gbt/test-data/target-template.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/src/__tests__/gbt/test-data/test-buffer.bin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/src/__tests__/gbt/test-data/test-buffer.bin
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								backend/src/__tests__/gbt/test-data/test-data-ids.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/src/__tests__/gbt/test-data/test-data-ids.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -34,7 +34,7 @@ class Blocks {
 | 
			
		||||
  private lastDifficultyAdjustmentTime = 0;
 | 
			
		||||
  private previousDifficultyRetarget = 0;
 | 
			
		||||
  private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
 | 
			
		||||
  private newAsyncBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => Promise<void>)[] = [];
 | 
			
		||||
  private newAsyncBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise<void>)[] = [];
 | 
			
		||||
 | 
			
		||||
  private mainLoopTimeout: number = 120000;
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ class Blocks {
 | 
			
		||||
    this.newBlockCallbacks.push(fn);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setNewAsyncBlockCallback(fn: (block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => Promise<void>) {
 | 
			
		||||
  public setNewAsyncBlockCallback(fn: (block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise<void>) {
 | 
			
		||||
    this.newAsyncBlockCallbacks.push(fn);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -642,7 +642,7 @@ class Blocks {
 | 
			
		||||
      const verboseBlock = await bitcoinClient.getBlock(blockHash, 2);
 | 
			
		||||
      const block = BitcoinApi.convertBlock(verboseBlock);
 | 
			
		||||
      const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
 | 
			
		||||
      const transactions = await this.$getTransactionsExtended(blockHash, block.height, false, false, true);
 | 
			
		||||
      const transactions = await this.$getTransactionsExtended(blockHash, block.height, false, false, true) as MempoolTransactionExtended[];
 | 
			
		||||
      if (config.MEMPOOL.BACKEND !== 'esplora') {
 | 
			
		||||
        // fill in missing transaction fee data from verboseBlock
 | 
			
		||||
        for (let i = 0; i < transactions.length; i++) {
 | 
			
		||||
 | 
			
		||||
@ -195,6 +195,7 @@ class DiskCache {
 | 
			
		||||
 | 
			
		||||
        if (data.mempoolArray) {
 | 
			
		||||
          for (const tx of data.mempoolArray) {
 | 
			
		||||
            delete tx.uid;
 | 
			
		||||
            data.mempool[tx.txid] = tx;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
@ -207,6 +208,7 @@ class DiskCache {
 | 
			
		||||
            const cacheData2 = JSON.parse(fs.readFileSync(fileName, 'utf8'));
 | 
			
		||||
            if (cacheData2.mempoolArray) {
 | 
			
		||||
              for (const tx of cacheData2.mempoolArray) {
 | 
			
		||||
                delete tx.uid;
 | 
			
		||||
                data.mempool[tx.txid] = tx;
 | 
			
		||||
              }
 | 
			
		||||
            } else {
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction } from '../../rust-gbt';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { MempoolBlock, MempoolTransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats } from '../mempool.interfaces';
 | 
			
		||||
import { Common, OnlineFeeStatsCalculator } from './common';
 | 
			
		||||
@ -5,16 +6,18 @@ import config from '../config';
 | 
			
		||||
import { Worker } from 'worker_threads';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
const MAX_UINT32 = Math.pow(2, 32) - 1;
 | 
			
		||||
 | 
			
		||||
class MempoolBlocks {
 | 
			
		||||
  private mempoolBlocks: MempoolBlockWithTransactions[] = [];
 | 
			
		||||
  private mempoolBlockDeltas: MempoolBlockDelta[] = [];
 | 
			
		||||
  private txSelectionWorker: Worker | null = null;
 | 
			
		||||
  private rustInitialized: boolean = false;
 | 
			
		||||
  private rustGbtGenerator: GbtGenerator = new GbtGenerator();
 | 
			
		||||
 | 
			
		||||
  private nextUid: number = 1;
 | 
			
		||||
  private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  public getMempoolBlocks(): MempoolBlock[] {
 | 
			
		||||
    return this.mempoolBlocks.map((block) => {
 | 
			
		||||
      return {
 | 
			
		||||
@ -40,9 +43,7 @@ class MempoolBlocks {
 | 
			
		||||
    const latestMempool = memPool;
 | 
			
		||||
    const memPoolArray: MempoolTransactionExtended[] = [];
 | 
			
		||||
    for (const i in latestMempool) {
 | 
			
		||||
      if (latestMempool.hasOwnProperty(i)) {
 | 
			
		||||
        memPoolArray.push(latestMempool[i]);
 | 
			
		||||
      }
 | 
			
		||||
      memPoolArray.push(latestMempool[i]);
 | 
			
		||||
    }
 | 
			
		||||
    const start = new Date().getTime();
 | 
			
		||||
 | 
			
		||||
@ -218,16 +219,17 @@ class MempoolBlocks {
 | 
			
		||||
    // to reduce the overhead of passing this data to the worker thread
 | 
			
		||||
    const strippedMempool: Map<number, CompactThreadTransaction> = new Map();
 | 
			
		||||
    Object.values(newMempool).forEach(entry => {
 | 
			
		||||
      if (entry.uid != null) {
 | 
			
		||||
        strippedMempool.set(entry.uid, {
 | 
			
		||||
      if (entry.uid !== null && entry.uid !== undefined) {
 | 
			
		||||
        const stripped = {
 | 
			
		||||
          uid: entry.uid,
 | 
			
		||||
          fee: entry.fee,
 | 
			
		||||
          weight: (entry.adjustedVsize * 4),
 | 
			
		||||
          sigops: entry.sigops,
 | 
			
		||||
          feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
			
		||||
          effectiveFeePerVsize: entry.effectiveFeePerVsize || entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
			
		||||
          inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
			
		||||
        });
 | 
			
		||||
          inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => (uid !== null && uid !== undefined)) as number[],
 | 
			
		||||
        };
 | 
			
		||||
        strippedMempool.set(entry.uid, stripped);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -260,8 +262,10 @@ class MempoolBlocks {
 | 
			
		||||
      // clean up thread error listener
 | 
			
		||||
      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
			
		||||
 | 
			
		||||
      const processed = this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
 | 
			
		||||
      const processed = this.processBlockTemplates(newMempool, blocks, null, Object.entries(rates), Object.values(clusters), saveResults);
 | 
			
		||||
 | 
			
		||||
      logger.debug(`makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
 | 
			
		||||
 | 
			
		||||
      return processed;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
@ -279,12 +283,12 @@ class MempoolBlocks {
 | 
			
		||||
    const start = Date.now();
 | 
			
		||||
 | 
			
		||||
    for (const tx of Object.values(added)) {
 | 
			
		||||
      this.setUid(tx);
 | 
			
		||||
      this.setUid(tx, true);
 | 
			
		||||
    }
 | 
			
		||||
    const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[];
 | 
			
		||||
    const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => (uid !== null && uid !== undefined)) as number[];
 | 
			
		||||
    // prepare a stripped down version of the mempool with only the minimum necessary data
 | 
			
		||||
    // to reduce the overhead of passing this data to the worker thread
 | 
			
		||||
    const addedStripped: CompactThreadTransaction[] = added.filter(entry => entry.uid != null).map(entry => {
 | 
			
		||||
    const addedStripped: CompactThreadTransaction[] = added.filter(entry => (entry.uid !== null && entry.uid !== undefined)).map(entry => {
 | 
			
		||||
      return {
 | 
			
		||||
        uid: entry.uid || 0,
 | 
			
		||||
        fee: entry.fee,
 | 
			
		||||
@ -292,7 +296,7 @@ class MempoolBlocks {
 | 
			
		||||
        sigops: entry.sigops,
 | 
			
		||||
        feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
			
		||||
        effectiveFeePerVsize: entry.effectiveFeePerVsize || entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
			
		||||
        inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
			
		||||
        inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => (uid !== null && uid !== undefined)) as number[],
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -314,84 +318,131 @@ class MempoolBlocks {
 | 
			
		||||
      // clean up thread error listener
 | 
			
		||||
      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
			
		||||
 | 
			
		||||
      this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
 | 
			
		||||
      this.processBlockTemplates(newMempool, blocks, null, Object.entries(rates), Object.values(clusters), saveResults);
 | 
			
		||||
      logger.debug(`updateBlockTemplates completed in ${(Date.now() - start) / 1000} seconds`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private processBlockTemplates(mempool, blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }, saveResults): MempoolBlockWithTransactions[] {
 | 
			
		||||
    for (const txid of Object.keys(rates)) {
 | 
			
		||||
  private resetRustGbt(): void {
 | 
			
		||||
    this.rustInitialized = false;
 | 
			
		||||
    this.rustGbtGenerator = new GbtGenerator();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $rustMakeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
 | 
			
		||||
    const start = Date.now();
 | 
			
		||||
 | 
			
		||||
    // reset mempool short ids
 | 
			
		||||
    if (saveResults) {
 | 
			
		||||
      this.resetUids();
 | 
			
		||||
    }
 | 
			
		||||
    // set missing short ids
 | 
			
		||||
    for (const tx of Object.values(newMempool)) {
 | 
			
		||||
      this.setUid(tx, !saveResults);
 | 
			
		||||
    }
 | 
			
		||||
    // set short ids for transaction inputs
 | 
			
		||||
    for (const tx of Object.values(newMempool)) {
 | 
			
		||||
      tx.inputs = tx.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => (uid !== null && uid !== undefined)) as number[];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // run the block construction algorithm in a separate thread, and wait for a result
 | 
			
		||||
    const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator();
 | 
			
		||||
    try {
 | 
			
		||||
      const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids(
 | 
			
		||||
        await rustGbt.make(Object.values(newMempool) as RustThreadTransaction[], this.nextUid),
 | 
			
		||||
      );
 | 
			
		||||
      if (saveResults) {
 | 
			
		||||
        this.rustInitialized = true;
 | 
			
		||||
      }
 | 
			
		||||
      const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, saveResults);
 | 
			
		||||
      logger.debug(`RUST makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
 | 
			
		||||
      return processed;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('RUST makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      if (saveResults) {
 | 
			
		||||
        this.resetRustGbt();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return this.mempoolBlocks;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $oneOffRustBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }): Promise<MempoolBlockWithTransactions[]> {
 | 
			
		||||
    return this.$rustMakeBlockTemplates(newMempool, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $rustUpdateBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[]): Promise<void> {
 | 
			
		||||
    // GBT optimization requires that uids never get too sparse
 | 
			
		||||
    // as a sanity check, we should also explicitly prevent uint32 uid overflow
 | 
			
		||||
    if (this.nextUid + added.length >= Math.min(Math.max(262144, 2 * mempoolSize), MAX_UINT32)) {
 | 
			
		||||
      this.resetRustGbt();
 | 
			
		||||
    }
 | 
			
		||||
    if (!this.rustInitialized) {
 | 
			
		||||
      // need to reset the worker
 | 
			
		||||
      await this.$rustMakeBlockTemplates(newMempool, true);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const start = Date.now();
 | 
			
		||||
    // set missing short ids
 | 
			
		||||
    for (const tx of added) {
 | 
			
		||||
      this.setUid(tx, true);
 | 
			
		||||
    }
 | 
			
		||||
    // set short ids for transaction inputs
 | 
			
		||||
    for (const tx of added) {
 | 
			
		||||
      tx.inputs = tx.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => (uid !== null && uid !== undefined)) as number[];
 | 
			
		||||
    }
 | 
			
		||||
    const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => (uid !== null && uid !== undefined)) as number[];
 | 
			
		||||
 | 
			
		||||
    // run the block construction algorithm in a separate thread, and wait for a result
 | 
			
		||||
    try {
 | 
			
		||||
      const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids(
 | 
			
		||||
        await this.rustGbtGenerator.update(
 | 
			
		||||
          added as RustThreadTransaction[],
 | 
			
		||||
          removedUids,
 | 
			
		||||
          this.nextUid,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0);
 | 
			
		||||
      if (mempoolSize !== resultMempoolSize) {
 | 
			
		||||
        throw new Error('GBT returned wrong number of transactions, cache is probably out of sync');
 | 
			
		||||
      } else {
 | 
			
		||||
        this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, true);
 | 
			
		||||
      }
 | 
			
		||||
      this.removeUids(removedUids);
 | 
			
		||||
      logger.debug(`RUST updateBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('RUST updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      this.resetRustGbt();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], saveResults): MempoolBlockWithTransactions[] {
 | 
			
		||||
    for (const [txid, rate] of rates) {
 | 
			
		||||
      if (txid in mempool) {
 | 
			
		||||
        mempool[txid].effectiveFeePerVsize = rates[txid];
 | 
			
		||||
        mempool[txid].effectiveFeePerVsize = rate;
 | 
			
		||||
        mempool[txid].cpfpChecked = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const lastBlockIndex = blocks.length - 1;
 | 
			
		||||
    let hasBlockStack = blocks.length >= 8;
 | 
			
		||||
    let stackWeight;
 | 
			
		||||
    let feeStatsCalculator: OnlineFeeStatsCalculator | void;
 | 
			
		||||
    if (hasBlockStack) {
 | 
			
		||||
      stackWeight = blocks[blocks.length - 1].reduce((total, tx) => total + (mempool[tx]?.weight || 0), 0);
 | 
			
		||||
      if (blockWeights && blockWeights[7] !== null) {
 | 
			
		||||
        stackWeight = blockWeights[7];
 | 
			
		||||
      } else {
 | 
			
		||||
        stackWeight = blocks[lastBlockIndex].reduce((total, tx) => total + (mempool[tx]?.weight || 0), 0);
 | 
			
		||||
      }
 | 
			
		||||
      hasBlockStack = stackWeight > config.MEMPOOL.BLOCK_WEIGHT_UNITS;
 | 
			
		||||
      feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5, [10, 20, 30, 40, 50, 60, 70, 80, 90]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const readyBlocks: { transactionIds, transactions, totalSize, totalWeight, totalFees, feeStats }[] = [];
 | 
			
		||||
    const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
 | 
			
		||||
    // update this thread's mempool with the results
 | 
			
		||||
    for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
 | 
			
		||||
      const block: string[] = blocks[blockIndex];
 | 
			
		||||
      let txid: string;
 | 
			
		||||
      let mempoolTx: MempoolTransactionExtended;
 | 
			
		||||
      let totalSize = 0;
 | 
			
		||||
      let totalVsize = 0;
 | 
			
		||||
      let totalWeight = 0;
 | 
			
		||||
      let totalFees = 0;
 | 
			
		||||
      const transactions: MempoolTransactionExtended[] = [];
 | 
			
		||||
      for (let txIndex = 0; txIndex < block.length; txIndex++) {
 | 
			
		||||
        txid = block[txIndex];
 | 
			
		||||
        if (txid) {
 | 
			
		||||
          mempoolTx = mempool[txid];
 | 
			
		||||
          // save position in projected blocks
 | 
			
		||||
          mempoolTx.position = {
 | 
			
		||||
            block: blockIndex,
 | 
			
		||||
            vsize: totalVsize + (mempoolTx.vsize / 2),
 | 
			
		||||
          };
 | 
			
		||||
          mempoolTx.ancestors = [];
 | 
			
		||||
          mempoolTx.descendants = [];
 | 
			
		||||
          mempoolTx.bestDescendant = null;
 | 
			
		||||
          mempoolTx.cpfpChecked = true;
 | 
			
		||||
 | 
			
		||||
          // online calculation of stack-of-blocks fee stats
 | 
			
		||||
          if (hasBlockStack && blockIndex === blocks.length - 1 && feeStatsCalculator) {
 | 
			
		||||
            feeStatsCalculator.processNext(mempoolTx);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          totalSize += mempoolTx.size;
 | 
			
		||||
          totalVsize += mempoolTx.vsize;
 | 
			
		||||
          totalWeight += mempoolTx.weight;
 | 
			
		||||
          totalFees += mempoolTx.fee;
 | 
			
		||||
 | 
			
		||||
          if (totalVsize <= sizeLimit) {
 | 
			
		||||
            transactions.push(mempoolTx);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      readyBlocks.push({
 | 
			
		||||
        transactionIds: block,
 | 
			
		||||
        transactions,
 | 
			
		||||
        totalSize,
 | 
			
		||||
        totalWeight,
 | 
			
		||||
        totalFees,
 | 
			
		||||
        feeStats: (hasBlockStack && blockIndex === blocks.length - 1 && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const cluster of Object.values(clusters)) {
 | 
			
		||||
    for (const cluster of clusters) {
 | 
			
		||||
      for (const memberTxid of cluster) {
 | 
			
		||||
        if (memberTxid in mempool) {
 | 
			
		||||
          const mempoolTx = mempool[memberTxid];
 | 
			
		||||
        const mempoolTx = mempool[memberTxid];
 | 
			
		||||
        if (mempoolTx) {
 | 
			
		||||
          const ancestors: Ancestor[] = [];
 | 
			
		||||
          const descendants: Ancestor[] = [];
 | 
			
		||||
          let matched = false;
 | 
			
		||||
@ -411,15 +462,62 @@ class MempoolBlocks {
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          mempoolTx.ancestors = ancestors;
 | 
			
		||||
          mempoolTx.descendants = descendants;
 | 
			
		||||
          mempoolTx.bestDescendant = null;
 | 
			
		||||
          Object.assign(mempoolTx, {ancestors, descendants, bestDescendant: null, cpfpChecked: true});
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const mempoolBlocks = readyBlocks.map((b, index) => {
 | 
			
		||||
      return this.dataToMempoolBlocks(b.transactionIds, b.transactions, b.totalSize, b.totalWeight, b.totalFees, b.feeStats);
 | 
			
		||||
    const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
 | 
			
		||||
    // update this thread's mempool with the results
 | 
			
		||||
    let mempoolTx: MempoolTransactionExtended;
 | 
			
		||||
    const mempoolBlocks: MempoolBlockWithTransactions[] = blocks.map((block, blockIndex) => {
 | 
			
		||||
      let totalSize = 0;
 | 
			
		||||
      let totalVsize = 0;
 | 
			
		||||
      let totalWeight = 0;
 | 
			
		||||
      let totalFees = 0;
 | 
			
		||||
      const transactions: MempoolTransactionExtended[] = [];
 | 
			
		||||
      for (const txid of block) {
 | 
			
		||||
        if (txid) {
 | 
			
		||||
          mempoolTx = mempool[txid];
 | 
			
		||||
          // save position in projected blocks
 | 
			
		||||
          mempoolTx.position = {
 | 
			
		||||
            block: blockIndex,
 | 
			
		||||
            vsize: totalVsize + (mempoolTx.vsize / 2),
 | 
			
		||||
          };
 | 
			
		||||
          if (!mempoolTx.cpfpChecked) {
 | 
			
		||||
            if (mempoolTx.ancestors?.length) {
 | 
			
		||||
              mempoolTx.ancestors = [];
 | 
			
		||||
            }
 | 
			
		||||
            if (mempoolTx.descendants?.length) {
 | 
			
		||||
              mempoolTx.descendants = [];
 | 
			
		||||
            }
 | 
			
		||||
            mempoolTx.bestDescendant = null;
 | 
			
		||||
            mempoolTx.cpfpChecked = true;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // online calculation of stack-of-blocks fee stats
 | 
			
		||||
          if (hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) {
 | 
			
		||||
            feeStatsCalculator.processNext(mempoolTx);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          totalSize += mempoolTx.size;
 | 
			
		||||
          totalVsize += mempoolTx.vsize;
 | 
			
		||||
          totalWeight += mempoolTx.weight;
 | 
			
		||||
          totalFees += mempoolTx.fee;
 | 
			
		||||
 | 
			
		||||
          if (totalVsize <= sizeLimit) {
 | 
			
		||||
            transactions.push(mempoolTx);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return this.dataToMempoolBlocks(
 | 
			
		||||
        block,
 | 
			
		||||
        transactions,
 | 
			
		||||
        totalSize,
 | 
			
		||||
        totalWeight,
 | 
			
		||||
        totalFees,
 | 
			
		||||
        (hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined,
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (saveResults) {
 | 
			
		||||
@ -452,16 +550,20 @@ class MempoolBlocks {
 | 
			
		||||
    this.nextUid = 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private setUid(tx: MempoolTransactionExtended): number {
 | 
			
		||||
    const uid = this.nextUid;
 | 
			
		||||
    this.nextUid++;
 | 
			
		||||
    this.uidMap.set(uid, tx.txid);
 | 
			
		||||
    tx.uid = uid;
 | 
			
		||||
    return uid;
 | 
			
		||||
  private setUid(tx: MempoolTransactionExtended, skipSet = false): number {
 | 
			
		||||
    if (tx.uid === null || tx.uid === undefined || !skipSet) {
 | 
			
		||||
      const uid = this.nextUid;
 | 
			
		||||
      this.nextUid++;
 | 
			
		||||
      this.uidMap.set(uid, tx.txid);
 | 
			
		||||
      tx.uid = uid;
 | 
			
		||||
      return uid;
 | 
			
		||||
    } else {
 | 
			
		||||
      return tx.uid;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getUid(tx: MempoolTransactionExtended): number | void {
 | 
			
		||||
    if (tx?.uid != null && this.uidMap.has(tx.uid)) {
 | 
			
		||||
    if (tx?.uid !== null && tx?.uid !== undefined && this.uidMap.has(tx.uid)) {
 | 
			
		||||
      return tx.uid;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -496,6 +598,28 @@ class MempoolBlocks {
 | 
			
		||||
    }
 | 
			
		||||
    return { blocks: convertedBlocks, rates: convertedRates, clusters: convertedClusters } as { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private convertNapiResultTxids({ blocks, blockWeights, rates, clusters }: GbtResult)
 | 
			
		||||
    : { blocks: string[][], blockWeights: number[], rates: [string, number][], clusters: string[][] } {
 | 
			
		||||
    const convertedBlocks: string[][] = blocks.map(block => block.map(uid => {
 | 
			
		||||
      const txid = this.uidMap.get(uid);
 | 
			
		||||
      if (txid !== undefined) {
 | 
			
		||||
        return txid;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Error('GBT returned a block containing a transaction with unknown uid');
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
    const convertedRates: [string, number][] = [];
 | 
			
		||||
    for (const [rateUid, rate] of rates) {
 | 
			
		||||
      const rateTxid = this.uidMap.get(rateUid) as string;
 | 
			
		||||
      convertedRates.push([rateTxid, rate]);
 | 
			
		||||
    }
 | 
			
		||||
    const convertedClusters: string[][] = [];
 | 
			
		||||
    for (const cluster of clusters) {
 | 
			
		||||
      convertedClusters.push(cluster.map(uid => this.uidMap.get(uid)) as string[]);
 | 
			
		||||
    }
 | 
			
		||||
    return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new MempoolBlocks();
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ class Mempool {
 | 
			
		||||
                                                    maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
 | 
			
		||||
  private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
 | 
			
		||||
    deletedTransactions: MempoolTransactionExtended[]) => void) | undefined;
 | 
			
		||||
  private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
 | 
			
		||||
  private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, mempoolSize: number, newTransactions: MempoolTransactionExtended[],
 | 
			
		||||
    deletedTransactions: MempoolTransactionExtended[]) => Promise<void>) | undefined;
 | 
			
		||||
 | 
			
		||||
  private txPerSecondArray: number[] = [];
 | 
			
		||||
@ -69,7 +69,7 @@ class Mempool {
 | 
			
		||||
    this.mempoolChangedCallback = fn;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
 | 
			
		||||
  public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; }, mempoolSize: number,
 | 
			
		||||
    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]) => Promise<void>): void {
 | 
			
		||||
    this.$asyncMempoolChangedCallback = fn;
 | 
			
		||||
  }
 | 
			
		||||
@ -84,16 +84,21 @@ class Mempool {
 | 
			
		||||
 | 
			
		||||
  public async $setMempool(mempoolData: { [txId: string]: MempoolTransactionExtended }) {
 | 
			
		||||
    this.mempoolCache = mempoolData;
 | 
			
		||||
    let count = 0;
 | 
			
		||||
    for (const txid of Object.keys(this.mempoolCache)) {
 | 
			
		||||
      if (this.mempoolCache[txid].sigops == null || this.mempoolCache[txid].effectiveFeePerVsize == null) {
 | 
			
		||||
        this.mempoolCache[txid] = transactionUtils.extendMempoolTransaction(this.mempoolCache[txid]);
 | 
			
		||||
      }
 | 
			
		||||
      if (this.mempoolCache[txid].order == null) {
 | 
			
		||||
        this.mempoolCache[txid].order = transactionUtils.txidToOrdering(txid);
 | 
			
		||||
      }
 | 
			
		||||
      count++;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.mempoolChangedCallback) {
 | 
			
		||||
      this.mempoolChangedCallback(this.mempoolCache, [], []);
 | 
			
		||||
    }
 | 
			
		||||
    if (this.$asyncMempoolChangedCallback) {
 | 
			
		||||
      await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
 | 
			
		||||
      await this.$asyncMempoolChangedCallback(this.mempoolCache, count, [], []);
 | 
			
		||||
    }
 | 
			
		||||
    this.addToSpendMap(Object.values(this.mempoolCache));
 | 
			
		||||
  }
 | 
			
		||||
@ -237,23 +242,24 @@ class Mempool {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const newMempoolSize = currentMempoolSize + newTransactions.length - deletedTransactions.length;
 | 
			
		||||
    const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx));
 | 
			
		||||
    this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6);
 | 
			
		||||
 | 
			
		||||
    if (!this.inSync && transactions.length === Object.keys(this.mempoolCache).length) {
 | 
			
		||||
    if (!this.inSync && transactions.length === newMempoolSize) {
 | 
			
		||||
      this.inSync = true;
 | 
			
		||||
      logger.notice('The mempool is now in sync!');
 | 
			
		||||
      loadingIndicators.setProgress('mempool', 100);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.mempoolCacheDelta = Math.abs(transactions.length - Object.keys(this.mempoolCache).length);
 | 
			
		||||
    this.mempoolCacheDelta = Math.abs(transactions.length - newMempoolSize);
 | 
			
		||||
 | 
			
		||||
    if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
 | 
			
		||||
      this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
 | 
			
		||||
    }
 | 
			
		||||
    if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
 | 
			
		||||
      this.updateTimerProgress(timer, 'running async mempool callback');
 | 
			
		||||
      await this.$asyncMempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
 | 
			
		||||
      await this.$asyncMempoolChangedCallback(this.mempoolCache, newMempoolSize, newTransactions, deletedTransactions);
 | 
			
		||||
      this.updateTimerProgress(timer, 'completed async mempool callback');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,6 +76,7 @@ class TransactionUtils {
 | 
			
		||||
    const adjustedFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1,
 | 
			
		||||
      (transaction.fee || 0) / adjustedVsize);
 | 
			
		||||
    const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, {
 | 
			
		||||
      order: this.txidToOrdering(transaction.txid),
 | 
			
		||||
      vsize: Math.round(transaction.weight / 4),
 | 
			
		||||
      adjustedVsize,
 | 
			
		||||
      sigops,
 | 
			
		||||
@ -154,6 +155,17 @@ class TransactionUtils {
 | 
			
		||||
 | 
			
		||||
    return sigops;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // returns the most significant 4 bytes of the txid as an integer
 | 
			
		||||
  public txidToOrdering(txid: string): number {
 | 
			
		||||
    return parseInt(
 | 
			
		||||
      txid.substr(62, 2) +
 | 
			
		||||
        txid.substr(60, 2) +
 | 
			
		||||
        txid.substr(58, 2) +
 | 
			
		||||
        txid.substr(56, 2),
 | 
			
		||||
      16
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new TransactionUtils();
 | 
			
		||||
 | 
			
		||||
@ -333,7 +333,7 @@ class WebsocketHandler {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended },
 | 
			
		||||
  async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number,
 | 
			
		||||
    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]): Promise<void> {
 | 
			
		||||
    if (!this.wss) {
 | 
			
		||||
      throw new Error('WebSocket.Server is not set');
 | 
			
		||||
@ -342,7 +342,11 @@ class WebsocketHandler {
 | 
			
		||||
    this.printLogs();
 | 
			
		||||
 | 
			
		||||
    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
			
		||||
      await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, true);
 | 
			
		||||
      if (config.MEMPOOL.RUST_GBT) {
 | 
			
		||||
        await mempoolBlocks.$rustUpdateBlockTemplates(newMempool, mempoolSize, newTransactions, deletedTransactions);
 | 
			
		||||
      } else {
 | 
			
		||||
        await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, true);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      mempoolBlocks.updateMempoolBlocks(newMempool, true);
 | 
			
		||||
    }
 | 
			
		||||
@ -570,7 +574,7 @@ class WebsocketHandler {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  async handleNewBlock(block: BlockExtended, txIds: string[], transactions: TransactionExtended[]): Promise<void> {
 | 
			
		||||
  async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise<void> {
 | 
			
		||||
    if (!this.wss) {
 | 
			
		||||
      throw new Error('WebSocket.Server is not set');
 | 
			
		||||
    }
 | 
			
		||||
@ -588,7 +592,11 @@ class WebsocketHandler {
 | 
			
		||||
      if (separateAudit) {
 | 
			
		||||
        auditMempool = deepClone(_memPool);
 | 
			
		||||
        if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
 | 
			
		||||
          projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false);
 | 
			
		||||
          if (config.MEMPOOL.RUST_GBT) {
 | 
			
		||||
            projectedBlocks = await mempoolBlocks.$oneOffRustBlockTemplates(auditMempool);
 | 
			
		||||
          } else {
 | 
			
		||||
            projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
 | 
			
		||||
        }
 | 
			
		||||
@ -655,7 +663,11 @@ class WebsocketHandler {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
			
		||||
      await mempoolBlocks.$makeBlockTemplates(_memPool, true);
 | 
			
		||||
      if (config.MEMPOOL.RUST_GBT) {
 | 
			
		||||
        await mempoolBlocks.$rustUpdateBlockTemplates(_memPool, Object.keys(_memPool).length, [], transactions);
 | 
			
		||||
      } else {
 | 
			
		||||
        await mempoolBlocks.$makeBlockTemplates(_memPool, true);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      mempoolBlocks.updateMempoolBlocks(_memPool, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ interface IConfig {
 | 
			
		||||
    AUDIT: boolean;
 | 
			
		||||
    ADVANCED_GBT_AUDIT: boolean;
 | 
			
		||||
    ADVANCED_GBT_MEMPOOL: boolean;
 | 
			
		||||
    RUST_GBT: boolean;
 | 
			
		||||
    CPFP_INDEXING: boolean;
 | 
			
		||||
    MAX_BLOCKS_BULK_QUERY: number;
 | 
			
		||||
    DISK_CACHE_BLOCK_INTERVAL: number;
 | 
			
		||||
@ -160,6 +161,7 @@ const defaults: IConfig = {
 | 
			
		||||
    'AUDIT': false,
 | 
			
		||||
    'ADVANCED_GBT_AUDIT': false,
 | 
			
		||||
    'ADVANCED_GBT_MEMPOOL': false,
 | 
			
		||||
    'RUST_GBT': false,
 | 
			
		||||
    'CPFP_INDEXING': false,
 | 
			
		||||
    'MAX_BLOCKS_BULK_QUERY': 0,
 | 
			
		||||
    'DISK_CACHE_BLOCK_INTERVAL': 6,
 | 
			
		||||
 | 
			
		||||
@ -94,9 +94,11 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MempoolTransactionExtended extends TransactionExtended {
 | 
			
		||||
  order: number;
 | 
			
		||||
  sigops: number;
 | 
			
		||||
  adjustedVsize: number;
 | 
			
		||||
  adjustedFeePerVsize: number;
 | 
			
		||||
  inputs?: number[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AuditTransaction {
 | 
			
		||||
@ -126,9 +128,9 @@ export interface CompactThreadTransaction {
 | 
			
		||||
  weight: number;
 | 
			
		||||
  sigops: number;
 | 
			
		||||
  feePerVsize: number;
 | 
			
		||||
  effectiveFeePerVsize?: number;
 | 
			
		||||
  effectiveFeePerVsize: number;
 | 
			
		||||
  inputs: number[];
 | 
			
		||||
  cpfpRoot?: string;
 | 
			
		||||
  cpfpRoot?: number;
 | 
			
		||||
  cpfpChecked?: boolean;
 | 
			
		||||
  dirty?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,12 @@ WORKDIR /build
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
RUN apt-get update
 | 
			
		||||
RUN apt-get install -y build-essential python3 pkg-config
 | 
			
		||||
RUN apt-get install -y build-essential python3 pkg-config curl
 | 
			
		||||
 | 
			
		||||
# Install Rust via rustup
 | 
			
		||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
 | 
			
		||||
ENV PATH="/root/.cargo/bin:$PATH"
 | 
			
		||||
 | 
			
		||||
RUN npm install --omit=dev --omit=optional
 | 
			
		||||
RUN npm run package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@
 | 
			
		||||
    "AUDIT": __MEMPOOL_AUDIT__,
 | 
			
		||||
    "ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__,
 | 
			
		||||
    "ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
 | 
			
		||||
    "RUST_GBT": __MEMPOOL_RUST_GBT__,
 | 
			
		||||
    "CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__,
 | 
			
		||||
    "MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__,
 | 
			
		||||
    "DISK_CACHE_BLOCK_INTERVAL": __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__,
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@ __MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.githu
 | 
			
		||||
__MEMPOOL_AUDIT__=${MEMPOOL_AUDIT:=false}
 | 
			
		||||
__MEMPOOL_ADVANCED_GBT_AUDIT__=${MEMPOOL_ADVANCED_GBT_AUDIT:=false}
 | 
			
		||||
__MEMPOOL_ADVANCED_GBT_MEMPOOL__=${MEMPOOL_ADVANCED_GBT_MEMPOOL:=false}
 | 
			
		||||
__MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=false}
 | 
			
		||||
__MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false}
 | 
			
		||||
__MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0}
 | 
			
		||||
__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__=${MEMPOOL_DISK_CACHE_BLOCK_INTERVAL:=6}
 | 
			
		||||
@ -155,6 +156,7 @@ sed -i "s!__MEMPOOL_POOLS_JSON_URL__!${__MEMPOOL_POOLS_JSON_URL__}!g" mempool-co
 | 
			
		||||
sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}!g" mempool-config.json
 | 
			
		||||
sed -i "s!__MEMPOOL_AUDIT__!${__MEMPOOL_AUDIT__}!g" mempool-config.json
 | 
			
		||||
sed -i "s!__MEMPOOL_ADVANCED_GBT_MEMPOOL__!${__MEMPOOL_ADVANCED_GBT_MEMPOOL__}!g" mempool-config.json
 | 
			
		||||
sed -i "s!__MEMPOOL_RUST_GBT__!${__MEMPOOL_GBT__}!g" mempool-config.json
 | 
			
		||||
sed -i "s!__MEMPOOL_ADVANCED_GBT_AUDIT__!${__MEMPOOL_ADVANCED_GBT_AUDIT__}!g" mempool-config.json
 | 
			
		||||
sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-config.json
 | 
			
		||||
sed -i "s!__MEMPOOL_MAX_BLOCKS_BULK_QUERY__!${__MEMPOOL_MAX_BLOCKS_BULK_QUERY__}!g" mempool-config.json
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@
 | 
			
		||||
    "CPFP_INDEXING": true,
 | 
			
		||||
    "ADVANCED_GBT_AUDIT": true,
 | 
			
		||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
			
		||||
    "RUST_GBT": true,
 | 
			
		||||
    "USE_SECOND_NODE_FOR_MINFEE": true,
 | 
			
		||||
    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@
 | 
			
		||||
    "AUDIT": true,
 | 
			
		||||
    "ADVANCED_GBT_AUDIT": true,
 | 
			
		||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
			
		||||
    "RUST_GBT": true,
 | 
			
		||||
    "POLL_RATE_MS": 1000,
 | 
			
		||||
    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@
 | 
			
		||||
    "AUDIT": true,
 | 
			
		||||
    "ADVANCED_GBT_AUDIT": true,
 | 
			
		||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
			
		||||
    "RUST_GBT": true,
 | 
			
		||||
    "POLL_RATE_MS": 1000,
 | 
			
		||||
    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								rust-toolchain
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								rust-toolchain
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
1.70
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user