Update regexes in regex.utils.ts
This commit is contained in:
		
							parent
							
								
									6cea6ceb7e
								
							
						
					
					
						commit
						bd34d71d8b
					
				| @ -38,11 +38,11 @@ export class SearchFormComponent implements OnInit { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   regexAddress = getRegex('address', 'mainnet'); // Default to mainnet
 |   regexAddress = getRegex('address', 'mainnet'); // Default to mainnet
 | ||||||
|   regexBlockhash = getRegex('blockhash'); |   regexBlockhash = getRegex('blockhash', 'mainnet'); | ||||||
|   regexTransaction = getRegex('transaction'); |   regexTransaction = getRegex('transaction'); | ||||||
|   regexBlockheight = getRegex('blockheight'); |   regexBlockheight = getRegex('blockheight'); | ||||||
|   regexDate = /^(?:\d{4}[-/]\d{1,2}[-/]\d{1,2}(?: \d{1,2}:\d{2})?)$/; |   regexDate = getRegex('date'); | ||||||
|   regexUnixTimestamp = /^\d{10}$/; |   regexUnixTimestamp = getRegex('timestamp'); | ||||||
| 
 | 
 | ||||||
|   focus$ = new Subject<string>(); |   focus$ = new Subject<string>(); | ||||||
|   click$ = new Subject<string>(); |   click$ = new Subject<string>(); | ||||||
| @ -72,6 +72,7 @@ export class SearchFormComponent implements OnInit { | |||||||
|       this.network = network; |       this.network = network; | ||||||
|       // TODO: Eventually change network type here from string to enum of consts
 |       // TODO: Eventually change network type here from string to enum of consts
 | ||||||
|       this.regexAddress = getRegex('address', network as any || 'mainnet'); |       this.regexAddress = getRegex('address', network as any || 'mainnet'); | ||||||
|  |       this.regexBlockhash = getRegex('blockhash', network as any || 'mainnet'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     this.router.events.subscribe((e: NavigationStart) => { // Reset search focus when changing page
 |     this.router.events.subscribe((e: NavigationStart) => { // Reset search focus when changing page
 | ||||||
| @ -181,8 +182,8 @@ export class SearchFormComponent implements OnInit { | |||||||
|           const lightningResults = result[1]; |           const lightningResults = result[1]; | ||||||
| 
 | 
 | ||||||
|           const matchesBlockHeight = this.regexBlockheight.test(searchText) && parseInt(searchText) <= this.stateService.latestBlockHeight; |           const matchesBlockHeight = this.regexBlockheight.test(searchText) && parseInt(searchText) <= this.stateService.latestBlockHeight; | ||||||
|           const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date'; |           const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date' && new Date(searchText).getTime() <= Date.now() && new Date(searchText).getTime() >= 1231006505000; | ||||||
|           const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText); |           const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText) && parseInt(searchText) <= Math.floor(Date.now() / 1000) && parseInt(searchText) >= 1231006505; // 1231006505 is the timestamp of the genesis block
 | ||||||
|           const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText); |           const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText); | ||||||
|           const matchesBlockHash = this.regexBlockhash.test(searchText); |           const matchesBlockHash = this.regexBlockhash.test(searchText); | ||||||
|           const matchesAddress = !matchesTxId && this.regexAddress.test(searchText); |           const matchesAddress = !matchesTxId && this.regexAddress.test(searchText); | ||||||
| @ -237,7 +238,7 @@ export class SearchFormComponent implements OnInit { | |||||||
|     if (searchText) { |     if (searchText) { | ||||||
|       this.isSearching = true; |       this.isSearching = true; | ||||||
| 
 | 
 | ||||||
|       const otherNetworks = findOtherNetworks(searchText, this.network as any); |       const otherNetworks = findOtherNetworks(searchText, this.network as any || 'mainnet'); | ||||||
|       if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) { |       if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) { | ||||||
|         this.navigate('/address/', searchText); |         this.navigate('/address/', searchText); | ||||||
|       } else if (otherNetworks.length > 0) { |       } else if (otherNetworks.length > 0) { | ||||||
| @ -269,6 +270,11 @@ export class SearchFormComponent implements OnInit { | |||||||
|       } else if (this.regexDate.test(searchText) || this.regexUnixTimestamp.test(searchText)) { |       } else if (this.regexDate.test(searchText) || this.regexUnixTimestamp.test(searchText)) { | ||||||
|         let timestamp: number; |         let timestamp: number; | ||||||
|         this.regexDate.test(searchText) ? timestamp = Math.floor(new Date(searchText).getTime() / 1000) : timestamp = searchText; |         this.regexDate.test(searchText) ? timestamp = Math.floor(new Date(searchText).getTime() / 1000) : timestamp = searchText; | ||||||
|  |         // Check if timestamp is too far in the future or before the genesis block
 | ||||||
|  |         if (timestamp > Math.floor(Date.now() / 1000) || timestamp < 1231006505) { | ||||||
|  |           this.isSearching = false; | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|         this.apiService.getBlockDataFromTimestamp$(timestamp).subscribe( |         this.apiService.getBlockDataFromTimestamp$(timestamp).subscribe( | ||||||
|           (data) => { this.navigate('/block/', data.hash); }, |           (data) => { this.navigate('/block/', data.hash); }, | ||||||
|           (error) => { console.log(error); this.isSearching = false; } |           (error) => { console.log(error); this.isSearching = false; } | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ export class RelativeUrlPipe implements PipeTransform { | |||||||
| 
 | 
 | ||||||
|   transform(value: string, swapNetwork?: string): string { |   transform(value: string, swapNetwork?: string): string { | ||||||
|     let network = swapNetwork || this.stateService.network; |     let network = swapNetwork || this.stateService.network; | ||||||
|  |     if (network === 'mainnet') network = ''; | ||||||
|     if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') { |     if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') { | ||||||
|       network = 'testnet'; |       network = 'testnet'; | ||||||
|     } else if (this.stateService.env.BASE_MODULE !== 'mempool') { |     } else if (this.stateService.env.BASE_MODULE !== 'mempool') { | ||||||
|  | |||||||
| @ -9,13 +9,16 @@ const BECH32_CHARS_UP = `[AC-HJ-NP-Z02-9]`; | |||||||
| const HEX_CHARS = `[a-fA-F0-9]`; | const HEX_CHARS = `[a-fA-F0-9]`; | ||||||
| 
 | 
 | ||||||
| // A regex to say "A single 0 OR any number with no leading zeroes"
 | // A regex to say "A single 0 OR any number with no leading zeroes"
 | ||||||
| // Capped at 13 digits so as to not be confused with lightning channel IDs (which are around 17 digits)
 | // Capped at 9 digits so as to not be confused with lightning channel IDs (which are around 17 digits)
 | ||||||
| // (?:             // Start a non-capturing group
 | // (?:             // Start a non-capturing group
 | ||||||
| //   0             // A single 0
 | //   0             // A single 0
 | ||||||
| //   |             // OR
 | //   |             // OR
 | ||||||
| //   [1-9][0-9]{0,12} // Any succession of numbers up to 13 digits starting with 1-9
 | //   [1-9][0-9]{0,8} // Any succession of numbers up to 9 digits starting with 1-9
 | ||||||
| // )               // End the non-capturing group.
 | // )               // End the non-capturing group.
 | ||||||
| const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9][0-9]{0,12})`; | const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9][0-9]{0,8})`; | ||||||
|  | 
 | ||||||
|  | // Simple digits only regex
 | ||||||
|  | const NUMBER_CHARS = `[0-9]`; | ||||||
| 
 | 
 | ||||||
| // Formatting of the address regex is for readability,
 | // Formatting of the address regex is for readability,
 | ||||||
| // We should ignore formatting it with automated formatting tools like prettier.
 | // We should ignore formatting it with automated formatting tools like prettier.
 | ||||||
| @ -48,7 +51,7 @@ const ADDRESS_CHARS: { | |||||||
|       + BASE58_CHARS |       + BASE58_CHARS | ||||||
|       + `{33,34}`, // m|n is 34 length, 2 is 35 length (We match the first letter separately)
 |       + `{33,34}`, // m|n is 34 length, 2 is 35 length (We match the first letter separately)
 | ||||||
|     bech32: `(?:` |     bech32: `(?:` | ||||||
|         + `tb1` // Starts with bc1
 |         + `tb1` // Starts with tb1
 | ||||||
|         + BECH32_CHARS_LW |         + BECH32_CHARS_LW | ||||||
|         + `{6,100}` // As per bech32, 6 char checksum is minimum
 |         + `{6,100}` // As per bech32, 6 char checksum is minimum
 | ||||||
|       + `|` |       + `|` | ||||||
| @ -76,18 +79,18 @@ const ADDRESS_CHARS: { | |||||||
|       + BASE58_CHARS |       + BASE58_CHARS | ||||||
|       + `{33}`, // All min-max lengths are 34
 |       + `{33}`, // All min-max lengths are 34
 | ||||||
|     bech32: `(?:` |     bech32: `(?:` | ||||||
|         + `(?:` // bech32 liquid starts with ex or lq
 |         + `(?:` // bech32 liquid starts with ex1 or lq1
 | ||||||
|           + `ex` |           + `ex1` | ||||||
|           + `|` |           + `|` | ||||||
|           + `lq` |           + `lq1` | ||||||
|         + `)` |         + `)` | ||||||
|         + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
 |         + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
 | ||||||
|         + `{6,100}` |         + `{6,100}` | ||||||
|       + `|` |       + `|` | ||||||
|         + `(?:` // Same as above but all upper case
 |         + `(?:` // Same as above but all upper case
 | ||||||
|           + `EX` |           + `EX1` | ||||||
|           + `|` |           + `|` | ||||||
|           + `LQ` |           + `LQ1` | ||||||
|         + `)` |         + `)` | ||||||
|         + BECH32_CHARS_UP |         + BECH32_CHARS_UP | ||||||
|         + `{6,100}` |         + `{6,100}` | ||||||
| @ -99,39 +102,39 @@ const ADDRESS_CHARS: { | |||||||
|       + `{33}`, // P2PKH is ???(TODO: find size), P2SH is 34
 |       + `{33}`, // P2PKH is ???(TODO: find size), P2SH is 34
 | ||||||
|     bech32: `(?:` |     bech32: `(?:` | ||||||
|         + `(?:` // bech32 liquid testnet starts with tex or tlq
 |         + `(?:` // bech32 liquid testnet starts with tex or tlq
 | ||||||
|           + `tex` // TODO: Why does mempool use this and not ert|el like in the elements source?
 |           + `tex1` // TODO: Why does mempool use this and not ert|el like in the elements source?
 | ||||||
|           + `|` |           + `|` | ||||||
|           + `tlq` // TODO: does this exist?
 |           + `tlq1` // TODO: does this exist?
 | ||||||
|         + `)` |         + `)` | ||||||
|         + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
 |         + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
 | ||||||
|         + `{6,100}` |         + `{6,100}` | ||||||
|       + `|` |       + `|` | ||||||
|         + `(?:` // Same as above but all upper case
 |         + `(?:` // Same as above but all upper case
 | ||||||
|           + `TEX` |           + `TEX1` | ||||||
|           + `|` |           + `|` | ||||||
|           + `TLQ` |           + `TLQ1` | ||||||
|         + `)` |         + `)` | ||||||
|         + BECH32_CHARS_UP |         + BECH32_CHARS_UP | ||||||
|         + `{6,100}` |         + `{6,100}` | ||||||
|       + `)`, |       + `)`, | ||||||
|   }, |   }, | ||||||
|   bisq: { |   bisq: { | ||||||
|     base58: `B1` // bisq base58 addrs start with B1
 |     base58: `(?:[bB][13]` // b or B at the start, followed by a single 1 or 3
 | ||||||
|       + BASE58_CHARS |       + BASE58_CHARS | ||||||
|       + `{33}`, // always length 35
 |       + `{26,33})`, | ||||||
|     bech32: `(?:` |     bech32: `(?:` | ||||||
|         + `bbc1` // Starts with bbc1
 |         + `[bB]bc1` // b or B at the start, followed by bc1
 | ||||||
|         + BECH32_CHARS_LW |         + BECH32_CHARS_LW | ||||||
|         + `{6,100}`  |         + `{6,100}`  | ||||||
|       + `|` |       + `|` | ||||||
|         + `BBC1` // All upper case version
 |         + `[bB]BC1` // b or B at the start, followed by BC1
 | ||||||
|         + BECH32_CHARS_UP |         + BECH32_CHARS_UP | ||||||
|         + `{6,100}` |         + `{6,100}` | ||||||
|       + `)`, |       + `)`, | ||||||
|   }, |   }, | ||||||
| } | } | ||||||
| type RegexTypeNoAddr = `blockhash` | `transaction` | `blockheight`; | type RegexTypeNoAddrNoBlockHash = | `transaction` | `blockheight` | `date` | `timestamp`; | ||||||
| export type RegexType = `address` | RegexTypeNoAddr; | export type RegexType = `address` | `blockhash` | RegexTypeNoAddrNoBlockHash; | ||||||
| 
 | 
 | ||||||
| export const NETWORKS = [`testnet`, `signet`, `liquid`, `liquidtestnet`, `bisq`, `mainnet`] as const; | export const NETWORKS = [`testnet`, `signet`, `liquid`, `liquidtestnet`, `bisq`, `mainnet`] as const; | ||||||
| export type Network = typeof NETWORKS[number]; // Turn const array into union type
 | export type Network = typeof NETWORKS[number]; // Turn const array into union type
 | ||||||
| @ -139,15 +142,15 @@ export type Network = typeof NETWORKS[number]; // Turn const array into union ty | |||||||
| export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS | export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS | ||||||
|   .map(network => [getRegex('address', network), network]) |   .map(network => [getRegex('address', network), network]) | ||||||
| 
 | 
 | ||||||
| export function findOtherNetworks(address: string, skipNetwork: Network): Network[] { | export function findOtherNetworks(address: string, skipNetwork: Network): {network: Network, address: string}[] { | ||||||
|   return ADDRESS_REGEXES.filter(([regex, network]) => |   return ADDRESS_REGEXES | ||||||
|     network !== skipNetwork && |     .filter(([regex, network]) => network !== skipNetwork && regex.test(address)) | ||||||
|     regex.test(address) |     .map(([, network]) => ({ network, address })); | ||||||
|   ).map(([, network]) => network); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getRegex(type: RegexTypeNoAddr): RegExp; | export function getRegex(type: RegexTypeNoAddrNoBlockHash): RegExp; | ||||||
| export function getRegex(type: 'address', network: Network): RegExp; | export function getRegex(type: 'address', network: Network): RegExp; | ||||||
|  | export function getRegex(type: 'blockhash', network: Network): RegExp; | ||||||
| export function getRegex(type: RegexType, network?: Network): RegExp { | export function getRegex(type: RegexType, network?: Network): RegExp { | ||||||
|   let regex = `^`; // ^ = Start of string
 |   let regex = `^`; // ^ = Start of string
 | ||||||
|   switch (type) { |   switch (type) { | ||||||
| @ -156,11 +159,37 @@ export function getRegex(type: RegexType, network?: Network): RegExp { | |||||||
|     case `blockheight`: |     case `blockheight`: | ||||||
|       regex += ZERO_INDEX_NUMBER_CHARS; // block height is a 0 indexed number
 |       regex += ZERO_INDEX_NUMBER_CHARS; // block height is a 0 indexed number
 | ||||||
|       break; |       break; | ||||||
|     // Match a 32 byte block hash in hex. Assumes at least 32 bits of difficulty.
 |     // Match a 32 byte block hash in hex.
 | ||||||
|     // [Testing Order]: Must always be tested before `transaction`
 |     // [Testing Order]: Must always be tested before `transaction`
 | ||||||
|     case `blockhash`: |     case `blockhash`: | ||||||
|       regex += `0{8}`; // Starts with exactly 8 zeroes in a row
 |       if (!network) { | ||||||
|       regex += `${HEX_CHARS}{56}`; // Continues with exactly 56 hex letters/numbers
 |         throw new Error(`Must pass network when type is blockhash`); | ||||||
|  |       } | ||||||
|  |       let leadingZeroes: number; | ||||||
|  |       switch (network) { | ||||||
|  |         case `mainnet`: | ||||||
|  |           leadingZeroes = 8; // Assumes at least 32 bits of difficulty
 | ||||||
|  |           break; | ||||||
|  |         case `testnet`: | ||||||
|  |           leadingZeroes = 8; // Assumes at least 32 bits of difficulty
 | ||||||
|  |           break; | ||||||
|  |         case `signet`: | ||||||
|  |           leadingZeroes = 5; | ||||||
|  |           break; | ||||||
|  |         case `liquid`: | ||||||
|  |           leadingZeroes = 8; // We are not interested in Liquid block hashes
 | ||||||
|  |           break; | ||||||
|  |         case `liquidtestnet`: | ||||||
|  |           leadingZeroes = 8; // We are not interested in Liquid block hashes
 | ||||||
|  |           break; | ||||||
|  |         case `bisq`: | ||||||
|  |           leadingZeroes = 8; // Assumes at least 32 bits of difficulty
 | ||||||
|  |           break; | ||||||
|  |         default: | ||||||
|  |           throw new Error(`Invalid Network ${network} (Unreachable error in TypeScript)`); | ||||||
|  |       } | ||||||
|  |       regex += `0{${leadingZeroes}}`; | ||||||
|  |       regex += `${HEX_CHARS}{${64 - leadingZeroes}}`; // Exactly 64 hex letters/numbers
 | ||||||
|       break; |       break; | ||||||
|     // Match a 32 byte tx hash in hex. Contains optional output index specifier.
 |     // Match a 32 byte tx hash in hex. Contains optional output index specifier.
 | ||||||
|     // [Testing Order]: Must always be tested after `blockhash`
 |     // [Testing Order]: Must always be tested after `blockhash`
 | ||||||
| @ -185,16 +214,28 @@ export function getRegex(type: RegexType, network?: Network): RegExp { | |||||||
|           regex += ADDRESS_CHARS.mainnet.base58; |           regex += ADDRESS_CHARS.mainnet.base58; | ||||||
|           regex += `|`; // OR
 |           regex += `|`; // OR
 | ||||||
|           regex += ADDRESS_CHARS.mainnet.bech32; |           regex += ADDRESS_CHARS.mainnet.bech32; | ||||||
|  |           regex += `|`; // OR
 | ||||||
|  |           regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey
 | ||||||
|  |           regex += `|`; // OR
 | ||||||
|  |           regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey
 | ||||||
|           break; |           break; | ||||||
|         case `testnet`: |         case `testnet`: | ||||||
|           regex += ADDRESS_CHARS.testnet.base58; |           regex += ADDRESS_CHARS.testnet.base58; | ||||||
|           regex += `|`; // OR
 |           regex += `|`; // OR
 | ||||||
|           regex += ADDRESS_CHARS.testnet.bech32; |           regex += ADDRESS_CHARS.testnet.bech32; | ||||||
|  |           regex += `|`; // OR
 | ||||||
|  |           regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey
 | ||||||
|  |           regex += `|`; // OR
 | ||||||
|  |           regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey
 | ||||||
|           break; |           break; | ||||||
|         case `signet`: |         case `signet`: | ||||||
|           regex += ADDRESS_CHARS.signet.base58; |           regex += ADDRESS_CHARS.signet.base58; | ||||||
|           regex += `|`; // OR
 |           regex += `|`; // OR
 | ||||||
|           regex += ADDRESS_CHARS.signet.bech32; |           regex += ADDRESS_CHARS.signet.bech32; | ||||||
|  |           regex += `|`; // OR
 | ||||||
|  |           regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey
 | ||||||
|  |           regex += `|`; // OR
 | ||||||
|  |           regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey
 | ||||||
|           break; |           break; | ||||||
|         case `liquid`: |         case `liquid`: | ||||||
|           regex += ADDRESS_CHARS.liquid.base58; |           regex += ADDRESS_CHARS.liquid.base58; | ||||||
| @ -216,6 +257,28 @@ export function getRegex(type: RegexType, network?: Network): RegExp { | |||||||
|       } |       } | ||||||
|       regex += `)`; // End the non-capturing group
 |       regex += `)`; // End the non-capturing group
 | ||||||
|       break; |       break; | ||||||
|  |     // Match a date in the format YYYY-MM-DD (optional: HH:MM)
 | ||||||
|  |     // [Testing Order]: any order is fine
 | ||||||
|  |     case `date`: | ||||||
|  |       regex += `(?:`;                  // Start a non-capturing group
 | ||||||
|  |       regex += `${NUMBER_CHARS}{4}`;   // Exactly 4 digits
 | ||||||
|  |       regex += `[-/]`;                 // 1 instance of the symbol "-" or "/"
 | ||||||
|  |       regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
 | ||||||
|  |       regex += `[-/]`;                 // 1 instance of the symbol "-" or "/"
 | ||||||
|  |       regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
 | ||||||
|  |       regex += `(?:`;                  // Start a non-capturing group
 | ||||||
|  |       regex += ` `;                    // 1 instance of the symbol " "
 | ||||||
|  |       regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
 | ||||||
|  |       regex += `:`;                    // 1 instance of the symbol ":"
 | ||||||
|  |       regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
 | ||||||
|  |       regex += `)?`;                   // End the non-capturing group. This group appears 0 or 1 times
 | ||||||
|  |       regex += `)`;                    // End the non-capturing group
 | ||||||
|  |       break; | ||||||
|  |     // Match a unix timestamp
 | ||||||
|  |     // [Testing Order]: any order is fine
 | ||||||
|  |     case `timestamp`: | ||||||
|  |       regex += `${NUMBER_CHARS}{10}`; // Exactly 10 digits
 | ||||||
|  |       break; | ||||||
|     default: |     default: | ||||||
|       throw new Error(`Invalid RegexType ${type} (Unreachable error in TypeScript)`); |       throw new Error(`Invalid RegexType ${type} (Unreachable error in TypeScript)`); | ||||||
|   } |   } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user