commit
						408c86963b
					
				
							
								
								
									
										5
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@ -27,6 +27,11 @@ jobs:
 | 
				
			|||||||
          node-version: ${{ matrix.node }}
 | 
					          node-version: ${{ matrix.node }}
 | 
				
			||||||
          registry-url: "https://registry.npmjs.org"
 | 
					          registry-url: "https://registry.npmjs.org"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Install 1.70.x Rust toolchain
 | 
				
			||||||
 | 
					        uses: actions-rs/toolchain@v1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					            toolchain: 1.70
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install
 | 
					      - name: Install
 | 
				
			||||||
        if: ${{ matrix.flavor == 'dev'}}
 | 
					        if: ${{ matrix.flavor == 'dev'}}
 | 
				
			||||||
        run: npm ci
 | 
					        run: npm ci
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -5,3 +5,4 @@ backend/mempool-config.json
 | 
				
			|||||||
*.swp
 | 
					*.swp
 | 
				
			||||||
frontend/src/resources/config.template.js
 | 
					frontend/src/resources/config.template.js
 | 
				
			||||||
frontend/src/resources/config.js
 | 
					frontend/src/resources/config.js
 | 
				
			||||||
 | 
					target
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -1,5 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "editor.tabSize": 2,
 | 
					  "editor.tabSize": 2,
 | 
				
			||||||
  "typescript.preferences.importModuleSpecifier": "relative",
 | 
					  "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._
 | 
					_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:
 | 
					Install dependencies with `npm` and build the backend:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@
 | 
				
			|||||||
    "AUDIT": false,
 | 
					    "AUDIT": false,
 | 
				
			||||||
    "ADVANCED_GBT_AUDIT": false,
 | 
					    "ADVANCED_GBT_AUDIT": false,
 | 
				
			||||||
    "ADVANCED_GBT_MEMPOOL": false,
 | 
					    "ADVANCED_GBT_MEMPOOL": false,
 | 
				
			||||||
 | 
					    "RUST_GBT": false,
 | 
				
			||||||
    "CPFP_INDEXING": false,
 | 
					    "CPFP_INDEXING": false,
 | 
				
			||||||
    "DISK_CACHE_BLOCK_INTERVAL": 6
 | 
					    "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",
 | 
					        "maxmind": "~4.3.8",
 | 
				
			||||||
        "mysql2": "~3.2.0",
 | 
					        "mysql2": "~3.2.0",
 | 
				
			||||||
        "node-worker-threads-pool": "~1.5.1",
 | 
					        "node-worker-threads-pool": "~1.5.1",
 | 
				
			||||||
 | 
					        "rust-gbt": "file:./rust-gbt",
 | 
				
			||||||
        "socks-proxy-agent": "~7.0.0",
 | 
					        "socks-proxy-agent": "~7.0.0",
 | 
				
			||||||
        "typescript": "~4.7.4",
 | 
					        "typescript": "~4.7.4",
 | 
				
			||||||
        "ws": "~8.13.0"
 | 
					        "ws": "~8.13.0"
 | 
				
			||||||
@ -1485,6 +1486,22 @@
 | 
				
			|||||||
        "node": ">=6"
 | 
					        "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": {
 | 
					    "node_modules/@noble/hashes": {
 | 
				
			||||||
      "version": "1.3.0",
 | 
					      "version": "1.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
 | 
				
			||||||
@ -6665,6 +6682,10 @@
 | 
				
			|||||||
        "queue-microtask": "^1.2.2"
 | 
					        "queue-microtask": "^1.2.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/rust-gbt": {
 | 
				
			||||||
 | 
					      "resolved": "rust-gbt",
 | 
				
			||||||
 | 
					      "link": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/safe-buffer": {
 | 
					    "node_modules/safe-buffer": {
 | 
				
			||||||
      "version": "5.2.1",
 | 
					      "version": "5.2.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
				
			||||||
@ -7544,6 +7565,17 @@
 | 
				
			|||||||
      "funding": {
 | 
					      "funding": {
 | 
				
			||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					        "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": {
 | 
					  "dependencies": {
 | 
				
			||||||
@ -8631,6 +8663,12 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ=="
 | 
					      "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": {
 | 
					    "@noble/hashes": {
 | 
				
			||||||
      "version": "1.3.0",
 | 
					      "version": "1.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
 | 
				
			||||||
@ -12481,6 +12519,12 @@
 | 
				
			|||||||
        "queue-microtask": "^1.2.2"
 | 
					        "queue-microtask": "^1.2.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "rust-gbt": {
 | 
				
			||||||
 | 
					      "version": "file:rust-gbt",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@napi-rs/cli": "^2.16.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "safe-buffer": {
 | 
					    "safe-buffer": {
 | 
				
			||||||
      "version": "5.2.1",
 | 
					      "version": "5.2.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -22,10 +22,10 @@
 | 
				
			|||||||
  "main": "index.ts",
 | 
					  "main": "index.ts",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "tsc": "./node_modules/typescript/bin/tsc -p tsconfig.build.json",
 | 
					    "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",
 | 
					    "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": "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)",
 | 
					    "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": "node --max-old-space-size=2048 dist/index.js",
 | 
				
			||||||
    "start-production": "node --max-old-space-size=16384 dist/index.js",
 | 
					    "start-production": "node --max-old-space-size=16384 dist/index.js",
 | 
				
			||||||
    "reindex-updated-pools": "npm run start-production --update-pools",
 | 
					    "reindex-updated-pools": "npm run start-production --update-pools",
 | 
				
			||||||
@ -33,7 +33,8 @@
 | 
				
			|||||||
    "test": "./node_modules/.bin/jest --coverage",
 | 
					    "test": "./node_modules/.bin/jest --coverage",
 | 
				
			||||||
    "lint": "./node_modules/.bin/eslint . --ext .ts",
 | 
					    "lint": "./node_modules/.bin/eslint . --ext .ts",
 | 
				
			||||||
    "lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
 | 
					    "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": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@babel/core": "^7.21.3",
 | 
					    "@babel/core": "^7.21.3",
 | 
				
			||||||
@ -46,6 +47,7 @@
 | 
				
			|||||||
    "maxmind": "~4.3.8",
 | 
					    "maxmind": "~4.3.8",
 | 
				
			||||||
    "mysql2": "~3.2.0",
 | 
					    "mysql2": "~3.2.0",
 | 
				
			||||||
    "node-worker-threads-pool": "~1.5.1",
 | 
					    "node-worker-threads-pool": "~1.5.1",
 | 
				
			||||||
 | 
					    "rust-gbt": "file:./rust-gbt",
 | 
				
			||||||
    "socks-proxy-agent": "~7.0.0",
 | 
					    "socks-proxy-agent": "~7.0.0",
 | 
				
			||||||
    "typescript": "~4.7.4",
 | 
					    "typescript": "~4.7.4",
 | 
				
			||||||
    "ws": "~8.13.0"
 | 
					    "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,
 | 
					    "AUDIT": true,
 | 
				
			||||||
    "ADVANCED_GBT_AUDIT": true,
 | 
					    "ADVANCED_GBT_AUDIT": true,
 | 
				
			||||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
					    "ADVANCED_GBT_MEMPOOL": true,
 | 
				
			||||||
 | 
					    "RUST_GBT": false,
 | 
				
			||||||
    "CPFP_INDEXING": true,
 | 
					    "CPFP_INDEXING": true,
 | 
				
			||||||
    "MAX_BLOCKS_BULK_QUERY": 999,
 | 
					    "MAX_BLOCKS_BULK_QUERY": 999,
 | 
				
			||||||
    "DISK_CACHE_BLOCK_INTERVAL": 999
 | 
					    "DISK_CACHE_BLOCK_INTERVAL": 999
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,7 @@ describe('Mempool Backend Config', () => {
 | 
				
			|||||||
        AUDIT: false,
 | 
					        AUDIT: false,
 | 
				
			||||||
        ADVANCED_GBT_AUDIT: false,
 | 
					        ADVANCED_GBT_AUDIT: false,
 | 
				
			||||||
        ADVANCED_GBT_MEMPOOL: false,
 | 
					        ADVANCED_GBT_MEMPOOL: false,
 | 
				
			||||||
 | 
					        RUST_GBT: false,
 | 
				
			||||||
        CPFP_INDEXING: false,
 | 
					        CPFP_INDEXING: false,
 | 
				
			||||||
        MAX_BLOCKS_BULK_QUERY: 0,
 | 
					        MAX_BLOCKS_BULK_QUERY: 0,
 | 
				
			||||||
        DISK_CACHE_BLOCK_INTERVAL: 6,
 | 
					        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 lastDifficultyAdjustmentTime = 0;
 | 
				
			||||||
  private previousDifficultyRetarget = 0;
 | 
					  private previousDifficultyRetarget = 0;
 | 
				
			||||||
  private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
 | 
					  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;
 | 
					  private mainLoopTimeout: number = 120000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -60,7 +60,7 @@ class Blocks {
 | 
				
			|||||||
    this.newBlockCallbacks.push(fn);
 | 
					    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);
 | 
					    this.newAsyncBlockCallbacks.push(fn);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -642,7 +642,7 @@ class Blocks {
 | 
				
			|||||||
      const verboseBlock = await bitcoinClient.getBlock(blockHash, 2);
 | 
					      const verboseBlock = await bitcoinClient.getBlock(blockHash, 2);
 | 
				
			||||||
      const block = BitcoinApi.convertBlock(verboseBlock);
 | 
					      const block = BitcoinApi.convertBlock(verboseBlock);
 | 
				
			||||||
      const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
 | 
					      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') {
 | 
					      if (config.MEMPOOL.BACKEND !== 'esplora') {
 | 
				
			||||||
        // fill in missing transaction fee data from verboseBlock
 | 
					        // fill in missing transaction fee data from verboseBlock
 | 
				
			||||||
        for (let i = 0; i < transactions.length; i++) {
 | 
					        for (let i = 0; i < transactions.length; i++) {
 | 
				
			||||||
 | 
				
			|||||||
@ -195,6 +195,7 @@ class DiskCache {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (data.mempoolArray) {
 | 
					        if (data.mempoolArray) {
 | 
				
			||||||
          for (const tx of data.mempoolArray) {
 | 
					          for (const tx of data.mempoolArray) {
 | 
				
			||||||
 | 
					            delete tx.uid;
 | 
				
			||||||
            data.mempool[tx.txid] = tx;
 | 
					            data.mempool[tx.txid] = tx;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -207,6 +208,7 @@ class DiskCache {
 | 
				
			|||||||
            const cacheData2 = JSON.parse(fs.readFileSync(fileName, 'utf8'));
 | 
					            const cacheData2 = JSON.parse(fs.readFileSync(fileName, 'utf8'));
 | 
				
			||||||
            if (cacheData2.mempoolArray) {
 | 
					            if (cacheData2.mempoolArray) {
 | 
				
			||||||
              for (const tx of cacheData2.mempoolArray) {
 | 
					              for (const tx of cacheData2.mempoolArray) {
 | 
				
			||||||
 | 
					                delete tx.uid;
 | 
				
			||||||
                data.mempool[tx.txid] = tx;
 | 
					                data.mempool[tx.txid] = tx;
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction } from '../../rust-gbt';
 | 
				
			||||||
import logger from '../logger';
 | 
					import logger from '../logger';
 | 
				
			||||||
import { MempoolBlock, MempoolTransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats } from '../mempool.interfaces';
 | 
					import { MempoolBlock, MempoolTransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats } from '../mempool.interfaces';
 | 
				
			||||||
import { Common, OnlineFeeStatsCalculator } from './common';
 | 
					import { Common, OnlineFeeStatsCalculator } from './common';
 | 
				
			||||||
@ -5,16 +6,18 @@ import config from '../config';
 | 
				
			|||||||
import { Worker } from 'worker_threads';
 | 
					import { Worker } from 'worker_threads';
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MAX_UINT32 = Math.pow(2, 32) - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MempoolBlocks {
 | 
					class MempoolBlocks {
 | 
				
			||||||
  private mempoolBlocks: MempoolBlockWithTransactions[] = [];
 | 
					  private mempoolBlocks: MempoolBlockWithTransactions[] = [];
 | 
				
			||||||
  private mempoolBlockDeltas: MempoolBlockDelta[] = [];
 | 
					  private mempoolBlockDeltas: MempoolBlockDelta[] = [];
 | 
				
			||||||
  private txSelectionWorker: Worker | null = null;
 | 
					  private txSelectionWorker: Worker | null = null;
 | 
				
			||||||
 | 
					  private rustInitialized: boolean = false;
 | 
				
			||||||
 | 
					  private rustGbtGenerator: GbtGenerator = new GbtGenerator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private nextUid: number = 1;
 | 
					  private nextUid: number = 1;
 | 
				
			||||||
  private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
 | 
					  private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public getMempoolBlocks(): MempoolBlock[] {
 | 
					  public getMempoolBlocks(): MempoolBlock[] {
 | 
				
			||||||
    return this.mempoolBlocks.map((block) => {
 | 
					    return this.mempoolBlocks.map((block) => {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
@ -40,9 +43,7 @@ class MempoolBlocks {
 | 
				
			|||||||
    const latestMempool = memPool;
 | 
					    const latestMempool = memPool;
 | 
				
			||||||
    const memPoolArray: MempoolTransactionExtended[] = [];
 | 
					    const memPoolArray: MempoolTransactionExtended[] = [];
 | 
				
			||||||
    for (const i in latestMempool) {
 | 
					    for (const i in latestMempool) {
 | 
				
			||||||
      if (latestMempool.hasOwnProperty(i)) {
 | 
					      memPoolArray.push(latestMempool[i]);
 | 
				
			||||||
        memPoolArray.push(latestMempool[i]);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const start = new Date().getTime();
 | 
					    const start = new Date().getTime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -218,16 +219,17 @@ class MempoolBlocks {
 | 
				
			|||||||
    // to reduce the overhead of passing this data to the worker thread
 | 
					    // to reduce the overhead of passing this data to the worker thread
 | 
				
			||||||
    const strippedMempool: Map<number, CompactThreadTransaction> = new Map();
 | 
					    const strippedMempool: Map<number, CompactThreadTransaction> = new Map();
 | 
				
			||||||
    Object.values(newMempool).forEach(entry => {
 | 
					    Object.values(newMempool).forEach(entry => {
 | 
				
			||||||
      if (entry.uid != null) {
 | 
					      if (entry.uid !== null && entry.uid !== undefined) {
 | 
				
			||||||
        strippedMempool.set(entry.uid, {
 | 
					        const stripped = {
 | 
				
			||||||
          uid: entry.uid,
 | 
					          uid: entry.uid,
 | 
				
			||||||
          fee: entry.fee,
 | 
					          fee: entry.fee,
 | 
				
			||||||
          weight: (entry.adjustedVsize * 4),
 | 
					          weight: (entry.adjustedVsize * 4),
 | 
				
			||||||
          sigops: entry.sigops,
 | 
					          sigops: entry.sigops,
 | 
				
			||||||
          feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
					          feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
				
			||||||
          effectiveFeePerVsize: entry.effectiveFeePerVsize || 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
 | 
					      // clean up thread error listener
 | 
				
			||||||
      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
					      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`);
 | 
					      logger.debug(`makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return processed;
 | 
					      return processed;
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
					      logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
@ -279,12 +283,12 @@ class MempoolBlocks {
 | 
				
			|||||||
    const start = Date.now();
 | 
					    const start = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const tx of Object.values(added)) {
 | 
					    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
 | 
					    // 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
 | 
					    // 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 {
 | 
					      return {
 | 
				
			||||||
        uid: entry.uid || 0,
 | 
					        uid: entry.uid || 0,
 | 
				
			||||||
        fee: entry.fee,
 | 
					        fee: entry.fee,
 | 
				
			||||||
@ -292,7 +296,7 @@ class MempoolBlocks {
 | 
				
			|||||||
        sigops: entry.sigops,
 | 
					        sigops: entry.sigops,
 | 
				
			||||||
        feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
					        feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
 | 
				
			||||||
        effectiveFeePerVsize: entry.effectiveFeePerVsize || 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
 | 
					      // clean up thread error listener
 | 
				
			||||||
      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
					      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`);
 | 
					      logger.debug(`updateBlockTemplates completed in ${(Date.now() - start) / 1000} seconds`);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : 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[] {
 | 
					  private resetRustGbt(): void {
 | 
				
			||||||
    for (const txid of Object.keys(rates)) {
 | 
					    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) {
 | 
					      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 hasBlockStack = blocks.length >= 8;
 | 
				
			||||||
    let stackWeight;
 | 
					    let stackWeight;
 | 
				
			||||||
    let feeStatsCalculator: OnlineFeeStatsCalculator | void;
 | 
					    let feeStatsCalculator: OnlineFeeStatsCalculator | void;
 | 
				
			||||||
    if (hasBlockStack) {
 | 
					    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;
 | 
					      hasBlockStack = stackWeight > config.MEMPOOL.BLOCK_WEIGHT_UNITS;
 | 
				
			||||||
      feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5, [10, 20, 30, 40, 50, 60, 70, 80, 90]);
 | 
					      feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5, [10, 20, 30, 40, 50, 60, 70, 80, 90]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const readyBlocks: { transactionIds, transactions, totalSize, totalWeight, totalFees, feeStats }[] = [];
 | 
					    for (const cluster of clusters) {
 | 
				
			||||||
    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 memberTxid of cluster) {
 | 
					      for (const memberTxid of cluster) {
 | 
				
			||||||
        if (memberTxid in mempool) {
 | 
					        const mempoolTx = mempool[memberTxid];
 | 
				
			||||||
          const mempoolTx = mempool[memberTxid];
 | 
					        if (mempoolTx) {
 | 
				
			||||||
          const ancestors: Ancestor[] = [];
 | 
					          const ancestors: Ancestor[] = [];
 | 
				
			||||||
          const descendants: Ancestor[] = [];
 | 
					          const descendants: Ancestor[] = [];
 | 
				
			||||||
          let matched = false;
 | 
					          let matched = false;
 | 
				
			||||||
@ -411,15 +462,62 @@ class MempoolBlocks {
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
          mempoolTx.ancestors = ancestors;
 | 
					          Object.assign(mempoolTx, {ancestors, descendants, bestDescendant: null, cpfpChecked: true});
 | 
				
			||||||
          mempoolTx.descendants = descendants;
 | 
					 | 
				
			||||||
          mempoolTx.bestDescendant = null;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const mempoolBlocks = readyBlocks.map((b, index) => {
 | 
					    const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
 | 
				
			||||||
      return this.dataToMempoolBlocks(b.transactionIds, b.transactions, b.totalSize, b.totalWeight, b.totalFees, b.feeStats);
 | 
					    // 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) {
 | 
					    if (saveResults) {
 | 
				
			||||||
@ -452,16 +550,20 @@ class MempoolBlocks {
 | 
				
			|||||||
    this.nextUid = 1;
 | 
					    this.nextUid = 1;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private setUid(tx: MempoolTransactionExtended): number {
 | 
					  private setUid(tx: MempoolTransactionExtended, skipSet = false): number {
 | 
				
			||||||
    const uid = this.nextUid;
 | 
					    if (tx.uid === null || tx.uid === undefined || !skipSet) {
 | 
				
			||||||
    this.nextUid++;
 | 
					      const uid = this.nextUid;
 | 
				
			||||||
    this.uidMap.set(uid, tx.txid);
 | 
					      this.nextUid++;
 | 
				
			||||||
    tx.uid = uid;
 | 
					      this.uidMap.set(uid, tx.txid);
 | 
				
			||||||
    return uid;
 | 
					      tx.uid = uid;
 | 
				
			||||||
 | 
					      return uid;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return tx.uid;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private getUid(tx: MempoolTransactionExtended): number | void {
 | 
					  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;
 | 
					      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[] }};
 | 
					    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();
 | 
					export default new MempoolBlocks();
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ class Mempool {
 | 
				
			|||||||
                                                    maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
 | 
					                                                    maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
 | 
				
			||||||
  private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
 | 
					  private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
 | 
				
			||||||
    deletedTransactions: MempoolTransactionExtended[]) => void) | undefined;
 | 
					    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;
 | 
					    deletedTransactions: MempoolTransactionExtended[]) => Promise<void>) | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private txPerSecondArray: number[] = [];
 | 
					  private txPerSecondArray: number[] = [];
 | 
				
			||||||
@ -69,7 +69,7 @@ class Mempool {
 | 
				
			|||||||
    this.mempoolChangedCallback = fn;
 | 
					    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 {
 | 
					    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]) => Promise<void>): void {
 | 
				
			||||||
    this.$asyncMempoolChangedCallback = fn;
 | 
					    this.$asyncMempoolChangedCallback = fn;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -84,16 +84,21 @@ class Mempool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  public async $setMempool(mempoolData: { [txId: string]: MempoolTransactionExtended }) {
 | 
					  public async $setMempool(mempoolData: { [txId: string]: MempoolTransactionExtended }) {
 | 
				
			||||||
    this.mempoolCache = mempoolData;
 | 
					    this.mempoolCache = mempoolData;
 | 
				
			||||||
 | 
					    let count = 0;
 | 
				
			||||||
    for (const txid of Object.keys(this.mempoolCache)) {
 | 
					    for (const txid of Object.keys(this.mempoolCache)) {
 | 
				
			||||||
      if (this.mempoolCache[txid].sigops == null || this.mempoolCache[txid].effectiveFeePerVsize == null) {
 | 
					      if (this.mempoolCache[txid].sigops == null || this.mempoolCache[txid].effectiveFeePerVsize == null) {
 | 
				
			||||||
        this.mempoolCache[txid] = transactionUtils.extendMempoolTransaction(this.mempoolCache[txid]);
 | 
					        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) {
 | 
					    if (this.mempoolChangedCallback) {
 | 
				
			||||||
      this.mempoolChangedCallback(this.mempoolCache, [], []);
 | 
					      this.mempoolChangedCallback(this.mempoolCache, [], []);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.$asyncMempoolChangedCallback) {
 | 
					    if (this.$asyncMempoolChangedCallback) {
 | 
				
			||||||
      await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
 | 
					      await this.$asyncMempoolChangedCallback(this.mempoolCache, count, [], []);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.addToSpendMap(Object.values(this.mempoolCache));
 | 
					    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));
 | 
					    const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx));
 | 
				
			||||||
    this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6);
 | 
					    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;
 | 
					      this.inSync = true;
 | 
				
			||||||
      logger.notice('The mempool is now in sync!');
 | 
					      logger.notice('The mempool is now in sync!');
 | 
				
			||||||
      loadingIndicators.setProgress('mempool', 100);
 | 
					      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)) {
 | 
					    if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
 | 
				
			||||||
      this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
 | 
					      this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
 | 
					    if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
 | 
				
			||||||
      this.updateTimerProgress(timer, 'running async mempool callback');
 | 
					      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');
 | 
					      this.updateTimerProgress(timer, 'completed async mempool callback');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -76,6 +76,7 @@ class TransactionUtils {
 | 
				
			|||||||
    const adjustedFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1,
 | 
					    const adjustedFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1,
 | 
				
			||||||
      (transaction.fee || 0) / adjustedVsize);
 | 
					      (transaction.fee || 0) / adjustedVsize);
 | 
				
			||||||
    const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, {
 | 
					    const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, {
 | 
				
			||||||
 | 
					      order: this.txidToOrdering(transaction.txid),
 | 
				
			||||||
      vsize: Math.round(transaction.weight / 4),
 | 
					      vsize: Math.round(transaction.weight / 4),
 | 
				
			||||||
      adjustedVsize,
 | 
					      adjustedVsize,
 | 
				
			||||||
      sigops,
 | 
					      sigops,
 | 
				
			||||||
@ -154,6 +155,17 @@ class TransactionUtils {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return sigops;
 | 
					    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();
 | 
					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> {
 | 
					    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]): Promise<void> {
 | 
				
			||||||
    if (!this.wss) {
 | 
					    if (!this.wss) {
 | 
				
			||||||
      throw new Error('WebSocket.Server is not set');
 | 
					      throw new Error('WebSocket.Server is not set');
 | 
				
			||||||
@ -342,7 +342,11 @@ class WebsocketHandler {
 | 
				
			|||||||
    this.printLogs();
 | 
					    this.printLogs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
					    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 {
 | 
					    } else {
 | 
				
			||||||
      mempoolBlocks.updateMempoolBlocks(newMempool, true);
 | 
					      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) {
 | 
					    if (!this.wss) {
 | 
				
			||||||
      throw new Error('WebSocket.Server is not set');
 | 
					      throw new Error('WebSocket.Server is not set');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -588,7 +592,11 @@ class WebsocketHandler {
 | 
				
			|||||||
      if (separateAudit) {
 | 
					      if (separateAudit) {
 | 
				
			||||||
        auditMempool = deepClone(_memPool);
 | 
					        auditMempool = deepClone(_memPool);
 | 
				
			||||||
        if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
 | 
					        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 {
 | 
					        } else {
 | 
				
			||||||
          projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
 | 
					          projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -655,7 +663,11 @@ class WebsocketHandler {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
					    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 {
 | 
					    } else {
 | 
				
			||||||
      mempoolBlocks.updateMempoolBlocks(_memPool, true);
 | 
					      mempoolBlocks.updateMempoolBlocks(_memPool, true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,7 @@ interface IConfig {
 | 
				
			|||||||
    AUDIT: boolean;
 | 
					    AUDIT: boolean;
 | 
				
			||||||
    ADVANCED_GBT_AUDIT: boolean;
 | 
					    ADVANCED_GBT_AUDIT: boolean;
 | 
				
			||||||
    ADVANCED_GBT_MEMPOOL: boolean;
 | 
					    ADVANCED_GBT_MEMPOOL: boolean;
 | 
				
			||||||
 | 
					    RUST_GBT: boolean;
 | 
				
			||||||
    CPFP_INDEXING: boolean;
 | 
					    CPFP_INDEXING: boolean;
 | 
				
			||||||
    MAX_BLOCKS_BULK_QUERY: number;
 | 
					    MAX_BLOCKS_BULK_QUERY: number;
 | 
				
			||||||
    DISK_CACHE_BLOCK_INTERVAL: number;
 | 
					    DISK_CACHE_BLOCK_INTERVAL: number;
 | 
				
			||||||
@ -160,6 +161,7 @@ const defaults: IConfig = {
 | 
				
			|||||||
    'AUDIT': false,
 | 
					    'AUDIT': false,
 | 
				
			||||||
    'ADVANCED_GBT_AUDIT': false,
 | 
					    'ADVANCED_GBT_AUDIT': false,
 | 
				
			||||||
    'ADVANCED_GBT_MEMPOOL': false,
 | 
					    'ADVANCED_GBT_MEMPOOL': false,
 | 
				
			||||||
 | 
					    'RUST_GBT': false,
 | 
				
			||||||
    'CPFP_INDEXING': false,
 | 
					    'CPFP_INDEXING': false,
 | 
				
			||||||
    'MAX_BLOCKS_BULK_QUERY': 0,
 | 
					    'MAX_BLOCKS_BULK_QUERY': 0,
 | 
				
			||||||
    'DISK_CACHE_BLOCK_INTERVAL': 6,
 | 
					    'DISK_CACHE_BLOCK_INTERVAL': 6,
 | 
				
			||||||
 | 
				
			|||||||
@ -94,9 +94,11 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MempoolTransactionExtended extends TransactionExtended {
 | 
					export interface MempoolTransactionExtended extends TransactionExtended {
 | 
				
			||||||
 | 
					  order: number;
 | 
				
			||||||
  sigops: number;
 | 
					  sigops: number;
 | 
				
			||||||
  adjustedVsize: number;
 | 
					  adjustedVsize: number;
 | 
				
			||||||
  adjustedFeePerVsize: number;
 | 
					  adjustedFeePerVsize: number;
 | 
				
			||||||
 | 
					  inputs?: number[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AuditTransaction {
 | 
					export interface AuditTransaction {
 | 
				
			||||||
@ -126,9 +128,9 @@ export interface CompactThreadTransaction {
 | 
				
			|||||||
  weight: number;
 | 
					  weight: number;
 | 
				
			||||||
  sigops: number;
 | 
					  sigops: number;
 | 
				
			||||||
  feePerVsize: number;
 | 
					  feePerVsize: number;
 | 
				
			||||||
  effectiveFeePerVsize?: number;
 | 
					  effectiveFeePerVsize: number;
 | 
				
			||||||
  inputs: number[];
 | 
					  inputs: number[];
 | 
				
			||||||
  cpfpRoot?: string;
 | 
					  cpfpRoot?: number;
 | 
				
			||||||
  cpfpChecked?: boolean;
 | 
					  cpfpChecked?: boolean;
 | 
				
			||||||
  dirty?: boolean;
 | 
					  dirty?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,12 @@ WORKDIR /build
 | 
				
			|||||||
COPY . .
 | 
					COPY . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-get update
 | 
					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 install --omit=dev --omit=optional
 | 
				
			||||||
RUN npm run package
 | 
					RUN npm run package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,7 @@
 | 
				
			|||||||
    "AUDIT": __MEMPOOL_AUDIT__,
 | 
					    "AUDIT": __MEMPOOL_AUDIT__,
 | 
				
			||||||
    "ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__,
 | 
					    "ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__,
 | 
				
			||||||
    "ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
 | 
					    "ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
 | 
				
			||||||
 | 
					    "RUST_GBT": __MEMPOOL_RUST_GBT__,
 | 
				
			||||||
    "CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__,
 | 
					    "CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__,
 | 
				
			||||||
    "MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__,
 | 
					    "MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__,
 | 
				
			||||||
    "DISK_CACHE_BLOCK_INTERVAL": __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__,
 | 
					    "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_AUDIT__=${MEMPOOL_AUDIT:=false}
 | 
				
			||||||
__MEMPOOL_ADVANCED_GBT_AUDIT__=${MEMPOOL_ADVANCED_GBT_AUDIT:=false}
 | 
					__MEMPOOL_ADVANCED_GBT_AUDIT__=${MEMPOOL_ADVANCED_GBT_AUDIT:=false}
 | 
				
			||||||
__MEMPOOL_ADVANCED_GBT_MEMPOOL__=${MEMPOOL_ADVANCED_GBT_MEMPOOL:=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_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false}
 | 
				
			||||||
__MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0}
 | 
					__MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0}
 | 
				
			||||||
__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__=${MEMPOOL_DISK_CACHE_BLOCK_INTERVAL:=6}
 | 
					__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_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_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_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_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_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
 | 
					sed -i "s!__MEMPOOL_MAX_BLOCKS_BULK_QUERY__!${__MEMPOOL_MAX_BLOCKS_BULK_QUERY__}!g" mempool-config.json
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@
 | 
				
			|||||||
    "CPFP_INDEXING": true,
 | 
					    "CPFP_INDEXING": true,
 | 
				
			||||||
    "ADVANCED_GBT_AUDIT": true,
 | 
					    "ADVANCED_GBT_AUDIT": true,
 | 
				
			||||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
					    "ADVANCED_GBT_MEMPOOL": true,
 | 
				
			||||||
 | 
					    "RUST_GBT": true,
 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": true,
 | 
					    "USE_SECOND_NODE_FOR_MINFEE": true,
 | 
				
			||||||
    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
					    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@
 | 
				
			|||||||
    "AUDIT": true,
 | 
					    "AUDIT": true,
 | 
				
			||||||
    "ADVANCED_GBT_AUDIT": true,
 | 
					    "ADVANCED_GBT_AUDIT": true,
 | 
				
			||||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
					    "ADVANCED_GBT_MEMPOOL": true,
 | 
				
			||||||
 | 
					    "RUST_GBT": true,
 | 
				
			||||||
    "POLL_RATE_MS": 1000,
 | 
					    "POLL_RATE_MS": 1000,
 | 
				
			||||||
    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
					    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@
 | 
				
			|||||||
    "AUDIT": true,
 | 
					    "AUDIT": true,
 | 
				
			||||||
    "ADVANCED_GBT_AUDIT": true,
 | 
					    "ADVANCED_GBT_AUDIT": true,
 | 
				
			||||||
    "ADVANCED_GBT_MEMPOOL": true,
 | 
					    "ADVANCED_GBT_MEMPOOL": true,
 | 
				
			||||||
 | 
					    "RUST_GBT": true,
 | 
				
			||||||
    "POLL_RATE_MS": 1000,
 | 
					    "POLL_RATE_MS": 1000,
 | 
				
			||||||
    "DISK_CACHE_BLOCK_INTERVAL": 1
 | 
					    "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