diff --git a/.github/workflows/package_for_test.yml b/.github/workflows/package_for_test.yml index 6527d3f7f2..1957a2fcc9 100644 --- a/.github/workflows/package_for_test.yml +++ b/.github/workflows/package_for_test.yml @@ -1,6 +1,6 @@ name: Package Neuron for Test -on: +on: issue_comment: types: [created] push: @@ -278,3 +278,82 @@ jobs: comment-id: ${{ github.event.comment.id }} body: Packageing failed in [${{ github.run_id }}](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}). @${{ github.event.comment.user.login }} edit-mode: append + + sync-test: + if: ${{ (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/package -test')) || github.event_name == 'push' }} + needs: [packaging] + strategy: + matrix: + node: + - 18.12.0 + os: + - macos-latest + - ubuntu-20.04 + - windows-2019 + + runs-on: ${{ matrix.os }} + + name: ${{ matrix.os }}(Node.js ${{ matrix.node }}) + + steps: + - name: Checkout + uses: actions/checkout@v3 + + + - name: Install libudev + if: matrix.os == 'ubuntu-20.04' + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev + - name: Download Neuron App Zip(x64) + if: matrix.os == 'macos-latest' + uses: actions/download-artifact@v4 + with: + name: Neuron-Mac-x64 + path: packages/sync-test + + - name: Download Neuron Win + if: matrix.os == 'windows-2019' + uses: actions/download-artifact@v4 + with: + name: Neuron-Win + path: packages/sync-test + + - name: Download Neuron Linux + if: matrix.os == 'ubuntu-20.04' + uses: actions/download-artifact@v4 + with: + name: Neuron-Linux + path: packages/sync-test + + - name: macos run + if: matrix.os == 'macos-latest' + run: | + cd packages/sync-test + yarn + bash scripts/prepare_neuron_macos.sh + yarn sync-test + - name: Windows run + if: matrix.os == 'windows-2019' + run: | + cd packages\sync-test + yarn + .\scripts\prepare_neuron_windows.bat + yarn sync-test + - name: ubuntu run + if: matrix.os == 'ubuntu-20.04' + run: | + cd packages/sync-test + yarn + bash scripts/prepare_neuron_linux.sh + xvfb-run --auto-servernum yarn sync-test + - name: tar result + if: always() + run: | + tar -zcvf tmp.${{ matrix.os }}.tar.gz packages/sync-test/tmp + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v3 + with: + name: jfoa-build-reports-${{ runner.os }} + path: tmp.${{ matrix.os }}.tar.gz diff --git a/packages/sync-test/.eslintignore b/packages/sync-test/.eslintignore new file mode 100644 index 0000000000..7a820a6a85 --- /dev/null +++ b/packages/sync-test/.eslintignore @@ -0,0 +1 @@ +jest.config.js diff --git a/packages/sync-test/.eslintrc.js b/packages/sync-test/.eslintrc.js new file mode 100644 index 0000000000..84f68f0b02 --- /dev/null +++ b/packages/sync-test/.eslintrc.js @@ -0,0 +1,59 @@ +module.exports = { + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], + settings: { + 'import/resolver': { + node: { + paths: ['src'], + extensions: ['.js', '.ts'], + }, + }, + }, + env: { + es6: true, + node: true, + browser: true, + jest: true, + }, + globals: { + BigInt: 'readonly', + }, + rules: { + // TODO: Some temporarily disabled rules will be re-enabled later, considering that many files are affected and will be addressed in the future. + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + 'prefer-const': 'off', + + // This is the configuration that was set when using eslint-plugin-prettier + // https://github.com/prettier/eslint-plugin-prettier#arrow-body-style-and-prefer-arrow-callback-issue + 'arrow-body-style': 'off', + 'prefer-arrow-callback': 'off', + + // TypeScript support + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + }, + ], + + // Unnecessary rules + 'no-plusplus': 'off', + 'max-classes-per-file': 'off', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/ban-ts-comment': 'warn', + 'no-console': 'off', + // Adjusted rules + 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], + }, + overrides: [ + { + files: ['*.test.ts', '*.test.tsx'], + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-var-requires': 'off', + 'no-console': 'off', + }, + }, + ], +} diff --git a/packages/sync-test/.prettierrc.js b/packages/sync-test/.prettierrc.js new file mode 100644 index 0000000000..4cba56a93f --- /dev/null +++ b/packages/sync-test/.prettierrc.js @@ -0,0 +1,11 @@ +module.exports = { + tabWidth: 2, + useTabs: false, + printWidth: 120, + trailingComma: 'es5', + semi: false, + singleQuote: true, + // TODO: This is the previous legacy configuration, should use the default settings, considering that more files are affected, and will be dealt with later. + // https://prettier.io/blog/2020/03/21/2.0.0.html#change-default-value-for-arrowparens-to-always-7430httpsgithubcomprettierprettierpull7430-by-kachkaevhttpsgithubcomkachkaev + arrowParens: 'avoid', +} diff --git a/packages/sync-test/README.md b/packages/sync-test/README.md new file mode 100644 index 0000000000..89ec4618d1 --- /dev/null +++ b/packages/sync-test/README.md @@ -0,0 +1,9 @@ +# sync-test + +notice: will remove your neuron config data + +```shell +./scripts/prepare_neuron_macos.sh +yarn +yarn test +``` diff --git a/packages/sync-test/jest.config.js b/packages/sync-test/jest.config.js new file mode 100644 index 0000000000..8d4f9da4df --- /dev/null +++ b/packages/sync-test/jest.config.js @@ -0,0 +1,21 @@ +process.env = Object.assign(process.env, { NODE_ENV: undefined }) + +module.exports = { + displayName: 'Unit Tests', + preset: 'ts-jest', + testEnvironment: 'node', + testRegex: '(/tests/.*.(test|spec))\\.(ts?|js?)$', + transform: { + '^.+\\.ts?$': 'ts-jest', + }, + roots: ['/src/'], + moduleDirectories: ['node_modules', 'src'], + moduleFileExtensions: ['ts', 'js', 'json', 'node'], + // setupFiles: ['/tests/setup.ts'], + moduleNameMapper: { + // FIXME: module mapper causes typeorm errors + // "electron": "/tests/mock/electron.ts", + // "logger": "/tests/mock/logger", + }, + testTimeout: 1800000, +} diff --git a/packages/sync-test/package.json b/packages/sync-test/package.json new file mode 100644 index 0000000000..b39ab07ce3 --- /dev/null +++ b/packages/sync-test/package.json @@ -0,0 +1,23 @@ +{ + "name": "sync-test", + "productName": "Neuron", + "description": "CKB Neuron Wallet", + "homepage": "https://www.nervos.org/", + "version": "0.116.2", + "private": true, + "author": { + "name": "Nervos Core Dev", + "email": "dev@nervos.org", + "url": "https://github.com/nervosnetwork/neuron" + }, + "repository": { + "type": "git", + "url": "https://github.com/nervosnetwork/neuron" + }, + "scripts": { + "sync-test": "jest sync-test-cases.spec.ts" + }, + "dependencies": { + "@types/tar": "6.1.10" + } +} diff --git a/packages/sync-test/scripts/prepare_neuron_linux.sh b/packages/sync-test/scripts/prepare_neuron_linux.sh new file mode 100644 index 0000000000..44623ad801 --- /dev/null +++ b/packages/sync-test/scripts/prepare_neuron_linux.sh @@ -0,0 +1,7 @@ +#wget http://github-test-logs.ckbapp.dev/neuron/sync/Neuron-v0.111.1-x86_64.AppImage +cp Neuron-*.AppImage Neuron.AppImage +chmod 777 Neuron.AppImage +./Neuron.AppImage --appimage-extract +mkdir neuron +mv squashfs-root neuron +cp -r neuron/squashfs-root/bin source/ diff --git a/packages/sync-test/scripts/prepare_neuron_macos.sh b/packages/sync-test/scripts/prepare_neuron_macos.sh new file mode 100644 index 0000000000..7835fe5a78 --- /dev/null +++ b/packages/sync-test/scripts/prepare_neuron_macos.sh @@ -0,0 +1,6 @@ +#wget http://github-test-logs.ckbapp.dev/neuron/sync/Neuron-v0.111.1-mac-x64.zip +cp Neuron*.zip Neuron.zip +unzip Neuron.zip +mv Neuron.app neuron +chmod 777 neuron/Contents +cp -r neuron/Contents/bin source/ diff --git a/packages/sync-test/scripts/prepare_neuron_windows.bat b/packages/sync-test/scripts/prepare_neuron_windows.bat new file mode 100644 index 0000000000..3533901a55 --- /dev/null +++ b/packages/sync-test/scripts/prepare_neuron_windows.bat @@ -0,0 +1,6 @@ +@REM curl -O -L http://github-test-logs.ckbapp.dev/neuron/sync/Neuron-v0.111.1-setup.exe +move .\Neuron-*.exe Neuron-setup.exe +.\Neuron-setup.exe /S /D=D:\a\neuron\neuron\packages\sync-test\neuron +mkdir ".\source\bin" +copy ".\neuron\bin\ckb.exe" ".\source\bin\ckb.exe" +copy ".\neuron\bin\ckb-light-client.exe" ".\source\bin\ckb-light-client.exe" diff --git a/packages/sync-test/source/ckb-data/db.2000.tar.gz b/packages/sync-test/source/ckb-data/db.2000.tar.gz new file mode 100644 index 0000000000..8c4ce127f5 Binary files /dev/null and b/packages/sync-test/source/ckb-data/db.2000.tar.gz differ diff --git a/packages/sync-test/source/ckb-light-client/config.toml b/packages/sync-test/source/ckb-light-client/config.toml new file mode 100644 index 0000000000..49506318ef --- /dev/null +++ b/packages/sync-test/source/ckb-light-client/config.toml @@ -0,0 +1,47 @@ +# chain = "mainnet" +# chain = "testnet" +# chain = "your_path_to/dev.toml" +chain = "../ckb/specs/dev.toml" + +[store] +path = "data/store" + +[network] +path = "data/network" + +listen_addresses = ["/ip4/0.0.0.0/tcp/8118"] +### Specify the public and routable network addresses +# public_addresses = [] + +# Node connects to nodes listed here to discovery other peers when there's no local stored peers. +# When chain.spec is changed, this usually should also be changed to the bootnodes in the new chain. +bootnodes = ["/ip4/127.0.0.1/tcp/8115/p2p/QmShJCAx1RzpEDFwBuQ6noqViNgNuYrYv2KCRhxmNQCe5J"] + +### Whitelist-only mode +# whitelist_only = false +### Whitelist peers connecting from the given IP addresses +# whitelist_peers = [] + +### Enable `SO_REUSEPORT` feature to reuse port on Linux, not supported on other OS yet +# reuse_port_on_linux = true + +max_peers = 125 +max_outbound_peers = 2 +# 2 minutes +ping_interval_secs = 120 +# 20 minutes +ping_timeout_secs = 1200 +connect_outbound_interval_secs = 15 +# If set to true, try to register upnp +upnp = false +# If set to true, network service will add discovered local address to peer store, it's helpful for private net development +discovery_local_address = false +# If set to true, random cleanup when there are too many inbound nodes +# Ensure that itself can continue to serve as a bootnode node +bootnode_mode = false + +[rpc] +# Light client rpc is designed for self hosting, exposing to public network is not recommended and may cause security issues. +# By default RPC only binds to localhost, thus it only allows accessing from the same machine. +listen_address = "127.0.0.1:9000" + diff --git a/packages/sync-test/source/ckb/ckb-miner.toml b/packages/sync-test/source/ckb/ckb-miner.toml new file mode 100644 index 0000000000..eecd9f19e5 --- /dev/null +++ b/packages/sync-test/source/ckb/ckb-miner.toml @@ -0,0 +1,29 @@ + +data_dir = 'data' + +[chain] + +spec = { file = 'specs/dev.toml' } + +[logger] + +filter = 'info' +color = true +log_to_file = true +log_to_stdout = true + +[sentry] + +dsn = '' + +[miner.client] + +rpc_url = 'http://127.0.0.1:8114' +block_on_submit = true +poll_interval = 500 + +[[miner.workers]] + +worker_type = 'Dummy' +delay_type = 'Constant' +value = 500 diff --git a/packages/sync-test/source/ckb/ckb.toml b/packages/sync-test/source/ckb/ckb.toml new file mode 100644 index 0000000000..52657533d1 --- /dev/null +++ b/packages/sync-test/source/ckb/ckb.toml @@ -0,0 +1,76 @@ + +data_dir = 'data' + +[chain] + +spec = { file = 'specs/dev.toml' } + +[logger] + +filter = 'info' +color = true +log_to_file = true +log_to_stdout = true + +[sentry] + +dsn = '' + +[db] + +cache_size = 134217728 +options_file = 'default.db-options' + +[network] + +listen_addresses = ['/ip4/0.0.0.0/tcp/8115'] +bootnodes = [] +max_peers = 125 +max_outbound_peers = 8 +ping_interval_secs = 120 +ping_timeout_secs = 1200 +connect_outbound_interval_secs = 15 +upnp = false +discovery_local_address = true +bootnode_mode = false +support_protocols = ['Ping', 'Discovery', 'Identify', 'Feeler', 'DisconnectMessage', 'Sync', 'Relay', 'Time', 'Alert', 'LightClient', 'Filter'] + +[rpc] + +listen_address = '127.0.0.1:8114' +max_request_body_size = 10485760 +modules = [ + 'Net', + 'Pool', + 'Miner', + 'Chain', + 'Stats', + 'Subscription', + 'Experiment', + 'Debug', + 'Indexer', +] +reject_ill_transactions = true +enable_deprecated_rpc = false + +[tx_pool] + +max_tx_pool_size = 180000000 +min_fee_rate = 1000 +max_tx_verify_cycles = 70000000 +max_ancestors_count = 25 + +[store] + +header_cache_size = 4096 +cell_data_cache_size = 128 +block_proposals_cache_size = 30 +block_tx_hashes_cache_size = 30 +block_uncles_cache_size = 30 + +[block_assembler] + +code_hash = '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8' +args = '0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7' +hash_type = 'type' +message = '0x' diff --git a/packages/sync-test/source/ckb/data/network/peer_store/addr_manager.db b/packages/sync-test/source/ckb/data/network/peer_store/addr_manager.db new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/packages/sync-test/source/ckb/data/network/peer_store/addr_manager.db @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/sync-test/source/ckb/data/network/peer_store/ban_list.db b/packages/sync-test/source/ckb/data/network/peer_store/ban_list.db new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/packages/sync-test/source/ckb/data/network/peer_store/ban_list.db @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/sync-test/source/ckb/data/network/secret_key b/packages/sync-test/source/ckb/data/network/secret_key new file mode 100644 index 0000000000..0e5291e890 --- /dev/null +++ b/packages/sync-test/source/ckb/data/network/secret_key @@ -0,0 +1 @@ +S># _}qRގOI<i \ No newline at end of file diff --git a/packages/sync-test/source/ckb/default.db-options b/packages/sync-test/source/ckb/default.db-options new file mode 100644 index 0000000000..bffbdc09e6 --- /dev/null +++ b/packages/sync-test/source/ckb/default.db-options @@ -0,0 +1,22 @@ +# This is a RocksDB option file. +# +# For detailed file format spec, please refer to the official documents +# in https://rocksdb.org/docs/ +# + +[DBOptions] +bytes_per_sync=1048576 +max_background_jobs=6 +max_total_wal_size=134217728 +keep_log_file_num=32 + +[CFOptions "default"] +level_compaction_dynamic_level_bytes=true +write_buffer_size=8388608 +min_write_buffer_number_to_merge=1 +max_write_buffer_number=2 +max_write_buffer_size_to_maintain=-1 + +[TableOptions/BlockBasedTable "default"] +cache_index_and_filter_blocks=true +pin_l0_filter_and_index_blocks_in_cache=true diff --git a/packages/sync-test/source/ckb/specs/dev.toml b/packages/sync-test/source/ckb/specs/dev.toml new file mode 100644 index 0000000000..cc4c850944 --- /dev/null +++ b/packages/sync-test/source/ckb/specs/dev.toml @@ -0,0 +1,102 @@ +name = "ckb_dev" + +[genesis] +version = 0 +parent_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +timestamp = 0 +compact_target = 0x20010000 +uncles_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +nonce = "0x0" + +[genesis.genesis_cell] +message = "1700057158022" + +[genesis.genesis_cell.lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# An array list paths to system cell files, which is absolute or relative to +# the directory containing this config file. +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_sighash_all" } +create_type_id = true +capacity = 100_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/dao" } +create_type_id = true +capacity = 16_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_data" } +create_type_id = false +capacity = 1_048_617_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_multisig_all" } +create_type_id = true +capacity = 100_000_0000_0000 + +[genesis.system_cells_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# Dep group cells +[[genesis.dep_groups]] +name = "secp256k1_blake160_sighash_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_sighash_all" }, +] +[[genesis.dep_groups]] +name = "secp256k1_blake160_multisig_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_multisig_all" }, +] + +# For first 11 block +[genesis.bootstrap_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "type" + +# Burn +[[genesis.issued_cells]] +capacity = 8_400_000_000_00000000 +lock.code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +lock.args = "0x62e907b15cbf27d5425399ebf6f0fb50ebb88f18" +lock.hash_type = "data" + +# issue for random generated private key: d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc +[[genesis.issued_cells]] +capacity = 20_000_000_000_00000000 +lock.code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +lock.args = "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" +lock.hash_type = "type" + +# issue for random generated private key: 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d +[[genesis.issued_cells]] +capacity = 5_198_735_037_00000000 +lock.code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +lock.args = "0x470dcdc5e44064909650113a274b3b36aecb6dc7" +lock.hash_type = "type" + +[params] +initial_primary_epoch_reward = 1_917_808_21917808 +secondary_epoch_reward = 613_698_63013698 +max_block_cycles = 10_000_000_000 +cellbase_maturity = 0 +primary_epoch_reward_halving_interval = 8760 +epoch_duration_target = 80 +genesis_epoch_length = 1000 +# For development and testing purposes only. +# Keep difficulty be permanent if the pow is Dummy. (default: false) +permanent_difficulty_in_dummy = true +starting_block_limiting_dao_withdrawing_lock = 0 + +[params.hardfork] +ckb2023 = 100000000 + + +[pow] +func = "Dummy" diff --git a/packages/sync-test/source/neuron-cell-data/2000/fullNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite b/packages/sync-test/source/neuron-cell-data/2000/fullNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite new file mode 100644 index 0000000000..6d0e6c6563 Binary files /dev/null and b/packages/sync-test/source/neuron-cell-data/2000/fullNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite differ diff --git a/packages/sync-test/source/neuron-cell-data/2000/lightNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite b/packages/sync-test/source/neuron-cell-data/2000/lightNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite new file mode 100644 index 0000000000..548f9d2690 Binary files /dev/null and b/packages/sync-test/source/neuron-cell-data/2000/lightNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite differ diff --git a/packages/sync-test/source/neuron/.env b/packages/sync-test/source/neuron/.env new file mode 100644 index 0000000000..ba2d0ac72a --- /dev/null +++ b/packages/sync-test/source/neuron/.env @@ -0,0 +1,120 @@ +# mainnet +MAINNET_SUDT_DEP_TXHASH=0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5 +MAINNET_SUDT_DEP_INDEX=0 +MAINNET_SUDT_DEP_TYPE=code +MAINNET_SUDT_SCRIPT_CODEHASH=0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5 +MAINNET_SUDT_SCRIPT_HASHTYPE=type + +# TODO replace with MainNet TXHash +MAINNET_SUDT_INFO_DEP_TXHASH=0x81eeaaedc2909faf471cc17f8aeb66dd5e78d50ad1b7eb56e41ab821ee356330 +MAINNET_SUDT_INFO_DEP_INDEX=0 +MAINNET_SUDT_INFO_DEP_TYPE=code +MAINNET_SUDT_INFO_SCRIPT_CODEHASH=0x72f3d72944f29511eedf806d4b12d77ca0a5cfbb2000d059d8898d283971b579 +MAINNET_SUDT_INFO_SCRIPT_HASHTYPE=type + +MAINNET_ACP_DEP_TXHASH=0x4153a2014952d7cac45f285ce9a7c5c0c0e1b21f2d378b82ac1433cb11c25c4d +MAINNET_ACP_DEP_INDEX=0 +MAINNET_ACP_DEP_TYPE=depGroup +MAINNET_ACP_SCRIPT_CODEHASH=0xd369597ff47f29fbc0d47d2e3775370d1250b85140c670e4718af712983a2354 +MAINNET_ACP_SCRIPT_HASHTYPE=type + +LEGACY_MAINNET_ACP_DEP_TXHASH=0xa05f28c9b867f8c5682039c10d8e864cf661685252aa74a008d255c33813bb81 +LEGACY_MAINNET_ACP_DEP_INDEX=0 +LEGACY_MAINNET_ACP_DEP_TYPE=depGroup +LEGACY_MAINNET_ACP_SCRIPT_CODEHASH=0x0fb343953ee78c9986b091defb6252154e0bb51044fd2879fde5b27314506111 +LEGACY_MAINNET_ACP_SCRIPT_HASHTYPE=data + +MAINNET_PW_ACP_DEP_TXHASH=0x1d60cb8f4666e039f418ea94730b1a8c5aa0bf2f7781474406387462924d15d4 +MAINNET_PW_ACP_DEP_INDEX=0 +MAINNET_PW_ACP_DEP_TYPE=code +MAINNET_PW_ACP_SCRIPT_CODEHASH=0xbf43c3602455798c1a61a596e0d95278864c552fafe231c063b3fabf97a8febc +MAINNET_PW_ACP_SCRIPT_HASHTYPE=type + +MAINNET_CHEQUE_DEP_TXHASH=0x04632cc459459cf5c9d384b43dee3e36f542a464bdd4127be7d6618ac6f8d268 +MAINNET_CHEQUE_DEP_INDEX=0 +MAINNET_CHEQUE_DEP_TYPE=depGroup +MAINNET_CHEQUE_SCRIPT_CODEHASH=0xe4d4ecc6e5f9a059bf2f7a82cca292083aebc0c421566a52484fe2ec51a9fb0c +MAINNET_CHEQUE_SCRIPT_HASHTYPE=type + +# NFT +MAINNET_NFT_ISSUER_DEP_TXHASH=0x5dce8acab1750d4790059f22284870216db086cb32ba118ee5e08b97dc21d471 +MAINNET_NFT_ISSUER_DEP_INDEX=0 +MAINNET_NFT_ISSUER_DEP_TYPE=code +MAINNET_NFT_ISSUER_SCRIPT_CODEHASH=0x24b04faf80ded836efc05247778eec4ec02548dab6e2012c0107374aa3f68b81 +MAINNET_NFT_ISSUER_SCRIPT_HASH_TYPE=type + +MAINNET_NFT_CLASS_DEP_TXHASH=0x5dce8acab1750d4790059f22284870216db086cb32ba118ee5e08b97dc21d471 +MAINNET_NFT_CLASS_DEP_INDEX=1 +MAINNET_NFT_CLASS_DEP_TYPE=code +MAINNET_NFT_CLASS_SCRIPT_CODEHASH=0xd51e6eaf48124c601f41abe173f1da550b4cbca9c6a166781906a287abbb3d9a +MAINNET_NFT_CLASS_SCRIPT_HASH_TYPE=type + +MAINNET_NFT_DEP_TXHASH=0x5dce8acab1750d4790059f22284870216db086cb32ba118ee5e08b97dc21d471 +MAINNET_NFT_DEP_INDEX=2 +MAINNET_NFT_DEP_TYPE=code +MAINNET_NFT_SCRIPT_CODEHASH=0x2b24f0d644ccbdd77bbf86b27c8cca02efa0ad051e447c212636d9ee7acaaec9 +MAINNET_NFT_SCRIPT_HASH_TYPE=type + +# testnet +TESTNET_SUDT_DEP_TXHASH=0xcae208c2947f12f14dbaa4cd8866faea7b062a6537b1f8b0bfdcea739cd071c4 +TESTNET_SUDT_DEP_INDEX=0 +TESTNET_SUDT_DEP_TYPE=code +TESTNET_SUDT_SCRIPT_CODEHASH=0x17a058ba48314b22c94a0c4fe4541504f64b219ab2d08b64b2f152283ca45bb6 +TESTNET_SUDT_SCRIPT_HASHTYPE=type + +TESTNET_SUDT_INFO_DEP_TXHASH=0x81eeaaedc2909faf471cc17f8aeb66dd5e78d50ad1b7eb56e41ab821ee356330 +TESTNET_SUDT_INFO_DEP_INDEX=0 +TESTNET_SUDT_INFO_DEP_TYPE=code +TESTNET_SUDT_INFO_SCRIPT_CODEHASH=0x72f3d72944f29511eedf806d4b12d77ca0a5cfbb2000d059d8898d283971b579 +TESTNET_SUDT_INFO_SCRIPT_HASHTYPE=type + +TESTNET_ACP_DEP_TXHASH=0xae96f346044086a8263f5aa1f40bdd6231bd3f1f817af81c214dd30f92054725 +TESTNET_ACP_DEP_INDEX=0 +TESTNET_ACP_DEP_TYPE=code +TESTNET_ACP_SCRIPT_CODEHASH=0x4d9b8dc69fd5fefe94340b449615700845ff2d06813c2cbf59e88c16786663f0 +TESTNET_ACP_SCRIPT_HASHTYPE=type + +LEGACY_TESTNET_ACP_DEP_TXHASH=0x4f32b3e39bd1b6350d326fdfafdfe05e5221865c3098ae323096f0bfc69e0a8c +LEGACY_TESTNET_ACP_DEP_INDEX=0 +LEGACY_TESTNET_ACP_DEP_TYPE=depGroup +LEGACY_TESTNET_ACP_SCRIPT_CODEHASH=0x86a1c6987a4acbe1a887cca4c9dd2ac9fcb07405bbeda51b861b18bbf7492c4b +LEGACY_TESTNET_ACP_SCRIPT_HASHTYPE=type + +TESTNET_PW_ACP_DEP_TXHASH=0x57a62003daeab9d54aa29b944fc3b451213a5ebdf2e232216a3cfed0dde61b38 +TESTNET_PW_ACP_DEP_INDEX=0 +TESTNET_PW_ACP_DEP_TYPE=code +TESTNET_PW_ACP_SCRIPT_CODEHASH=0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63 +TESTNET_PW_ACP_SCRIPT_HASHTYPE=type + +TESTNET_CHEQUE_DEP_TXHASH=0xbd306018d56d8db901c403a1d5fefbe5e2b0bcb814acf2f37e6e7c1efd69c3b2 +TESTNET_CHEQUE_DEP_INDEX=0 +TESTNET_CHEQUE_DEP_TYPE=code +TESTNET_CHEQUE_SCRIPT_CODEHASH=0x46acb82851956fe8e80a7617bff91469cdae4ebc0460a84c4cb1109211d0071e +TESTNET_CHEQUE_SCRIPT_HASHTYPE=type + +# NFT +TESTNET_NFT_ISSUER_DEP_TXHASH=0xf06f97a3c666a42e308df9f1271686ecca8f521d0fbb676bb819952c5f6171dd +TESTNET_NFT_ISSUER_DEP_INDEX=0 +TESTNET_NFT_ISSUER_DEP_TYPE=code +TESTNET_NFT_ISSUER_SCRIPT_CODEHASH=0xa590c0c410920271cc9f3a3b4e2b5bcadbcbc45a76ad62301e66e196d2a6be8f +TESTNET_NFT_ISSUER_SCRIPT_HASH_TYPE=type + +TESTNET_NFT_CLASS_DEP_TXHASH=0x107c91377098169ce4ff65a222e6605f54df86b48c7b83c0b29e147cbf5e51fa +TESTNET_NFT_CLASS_DEP_INDEX=0 +TESTNET_NFT_CLASS_DEP_TYPE=code +TESTNET_NFT_CLASS_SCRIPT_CODEHASH=0x5c49d5dd729540180ea8d9ba102e3635e0ea473e6efcfae02be43164a28ea9b7 +TESTNET_NFT_CLASS_SCRIPT_HASH_TYPE=type + +TESTNET_NFT_DEP_TXHASH=0x09cb03c8bf1215232312569715be3521e6ba04fd07418cb3fe6474b3486d7c70 +TESTNET_NFT_DEP_INDEX=0 +TESTNET_NFT_DEP_TYPE=code +TESTNET_NFT_SCRIPT_CODEHASH=0xb58dc92917aba8c30653409e96502dcb10c2036fb2febfd51aaa52dff73f66b9 +TESTNET_NFT_SCRIPT_HASH_TYPE=type + +# DEFAULT SCRIPT +SECP256K1_CODE_HASH=0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8 +DAO_CODE_HASH=0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e +MULTISIG_CODE_HASH=0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8 + +# CKB NODE OPTIONS +CKB_NODE_ASSUME_VALID_TARGET='0x79cecdd6f41361e2474290224751284312a018528d1d92f4e18dd6d542feddfe' diff --git a/packages/sync-test/source/neuron/dev-wallet1/dev/networks/index.dev.json b/packages/sync-test/source/neuron/dev-wallet1/dev/networks/index.dev.json new file mode 100644 index 0000000000..4624820ede --- /dev/null +++ b/packages/sync-test/source/neuron/dev-wallet1/dev/networks/index.dev.json @@ -0,0 +1,53 @@ +{ + "selected": "e8f41a40-37a8-468b-97c9-0b5e478d8f9a", + "networks": [ + { + "id": "mainnet", + "name": "Internal Node", + "remote": "http://127.0.0.1:8114", + "genesisHash": "0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5", + "type": 0, + "chain": "ckb", + "readonly": true + }, + { + "id": "light_client", + "name": "Light Client", + "remote": "http://127.0.0.1:9000", + "genesisHash": "0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5", + "type": 2, + "chain": "light_client_mainnet", + "readonly": true + }, + { + "id": "light_client_testnet", + "name": "Light Client", + "remote": "http://127.0.0.1:9000", + "genesisHash": "0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606", + "type": 2, + "chain": "light_client_testnet", + "readonly": true + }, + { + "id": "e8f41a40-37a8-468b-97c9-0b5e478d8f9a", + "name": "dev", + "remote": "http://127.0.0.1:8114", + "type": 1, + "genesisHash": "0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717", + "chain": "ckb_dev", + "readonly": false + }, + { + "id": "471a2055-fda3-4ef2-b84d-e3a54f2e67d9", + "name": "light-light", + "remote": "http://127.0.0.1:9000", + "type": 2, + "genesisHash": "0x", + "chain": "light_client_testnet", + "readonly": false + } + ], + "AddedLightNetwork": true, + "MigrateNetwork": true, + "AddInternalNetwork": true +} \ No newline at end of file diff --git a/packages/sync-test/source/neuron/dev-wallet1/dev/networks/index.light.json b/packages/sync-test/source/neuron/dev-wallet1/dev/networks/index.light.json new file mode 100644 index 0000000000..ba197d81e7 --- /dev/null +++ b/packages/sync-test/source/neuron/dev-wallet1/dev/networks/index.light.json @@ -0,0 +1,53 @@ +{ + "selected": "471a2055-fda3-4ef2-b84d-e3a54f2e67d9", + "networks": [ + { + "id": "mainnet", + "name": "Internal Node", + "remote": "http://127.0.0.1:8114", + "genesisHash": "0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5", + "type": 0, + "chain": "ckb", + "readonly": true + }, + { + "id": "light_client", + "name": "Light Client", + "remote": "http://127.0.0.1:9000", + "genesisHash": "0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5", + "type": 2, + "chain": "light_client_mainnet", + "readonly": true + }, + { + "id": "light_client_testnet", + "name": "Light Client", + "remote": "http://127.0.0.1:9000", + "genesisHash": "0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606", + "type": 2, + "chain": "light_client_testnet", + "readonly": true + }, + { + "id": "e8f41a40-37a8-468b-97c9-0b5e478d8f9a", + "name": "dev", + "remote": "http://127.0.0.1:8114", + "type": 1, + "genesisHash": "0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717", + "chain": "ckb_dev", + "readonly": false + }, + { + "id": "471a2055-fda3-4ef2-b84d-e3a54f2e67d9", + "name": "light-light", + "remote": "http://127.0.0.1:9000", + "type": 2, + "genesisHash": "0x", + "chain": "light_client_testnet", + "readonly": false + } + ], + "AddedLightNetwork": true, + "MigrateNetwork": true, + "AddInternalNetwork": true +} \ No newline at end of file diff --git a/packages/sync-test/source/neuron/dev-wallet1/dev/wallets/aced9a1c-85c8-40eb-811d-9399212a92b9.json b/packages/sync-test/source/neuron/dev-wallet1/dev/wallets/aced9a1c-85c8-40eb-811d-9399212a92b9.json new file mode 100644 index 0000000000..4ad43837c0 --- /dev/null +++ b/packages/sync-test/source/neuron/dev-wallet1/dev/wallets/aced9a1c-85c8-40eb-811d-9399212a92b9.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "crypto": { + "ciphertext": "bb0399c847d14c97be3d500335521d97616471e64c1a39cc416b2d1e7e61f158ced344242b7c08fd02b0ad2fdbecce6732caa25cf1fbd8ca082210d31ee27ea7", + "cipherparams": { + "iv": "b14ce7069aecfd85b144cdf47d5457c0" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "ba13fb819cf64f62c7bb4815eb556e4aa6e13f5c2fcc9693135a761a4e86b118", + "n": 262144, + "r": 8, + "p": 1 + }, + "mac": "1ab3ec93edf8d0c2e412cbd896344cc1b291a32763124212a67868f19616781a" + }, + "id": "aced9a1c-85c8-40eb-811d-9399212a92b9" +} \ No newline at end of file diff --git a/packages/sync-test/source/neuron/dev-wallet1/dev/wallets/wallets.json b/packages/sync-test/source/neuron/dev-wallet1/dev/wallets/wallets.json new file mode 100644 index 0000000000..5fa8fe876d --- /dev/null +++ b/packages/sync-test/source/neuron/dev-wallet1/dev/wallets/wallets.json @@ -0,0 +1,11 @@ +{ + "wallets": [ + { + "id": "aced9a1c-85c8-40eb-811d-9399212a92b9", + "name": "Wallet 1", + "extendedKey": "03620ce1a683278c0b3c0a18c90920eef22ddea14ffa3dfa03c3cf186498bce9ae31e660a979fb6dff2be24b2c1d3bfda7abe7b3c5e31aba96e3b5a394db008be2", + "isHD": true + } + ], + "current": "aced9a1c-85c8-40eb-811d-9399212a92b9" +} \ No newline at end of file diff --git a/packages/sync-test/source/neuron/lumos.json b/packages/sync-test/source/neuron/lumos.json new file mode 100644 index 0000000000..d6be45a206 --- /dev/null +++ b/packages/sync-test/source/neuron/lumos.json @@ -0,0 +1,104 @@ +{ + "PREFIX": "ckt", + "SCRIPTS": { + "SECP256K1_BLAKE160": { + "HASH_TYPE": "type", + "CODE_HASH": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "TX_HASH": "0xf73ad8855fa59f8413f26b0d716a061bdd1056a1abcda8b5ce240fb546d877f3", + "INDEX": "0x0", + "DEP_TYPE": "depGroup" + }, + "SECP256K1_BLAKE160_MULTISIG": { + "CODE_HASH": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8", + "HASH_TYPE": "type", + "TX_HASH": "0xf73ad8855fa59f8413f26b0d716a061bdd1056a1abcda8b5ce240fb546d877f3", + "INDEX": "0x1", + "DEP_TYPE": "depGroup", + "SHORT_ID": 1 + }, + "DAO": { + "HASH_TYPE": "type", + "CODE_HASH": "0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e", + "TX_HASH": "0x302e1206a4bece66ad1275f3078b4d1e4d453f279442e7efdbc9eb79859731ba", + "INDEX": "0x2", + "DEP_TYPE": "code" + }, + "SUDT": { + "CODE_HASH": "0x17a058ba48314b22c94a0c4fe4541504f64b219ab2d08b64b2f152283ca45bb6", + "HASH_TYPE": "type", + "TX_HASH": "0xcae208c2947f12f14dbaa4cd8866faea7b062a6537b1f8b0bfdcea739cd071c4", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "ANYONE_CAN_PAY": { + "CODE_HASH": "0x4d9b8dc69fd5fefe94340b449615700845ff2d06813c2cbf59e88c16786663f0", + "HASH_TYPE": "type", + "TX_HASH": "0xae96f346044086a8263f5aa1f40bdd6231bd3f1f817af81c214dd30f92054725", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "OMNILOCK": { + "CODE_HASH": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", + "HASH_TYPE": "type", + "TX_HASH": "0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "CHEQUE": { + "CODE_HASH": "0x46acb82851956fe8e80a7617bff91469cdae4ebc0460a84c4cb1109211d0071e", + "HASH_TYPE": "type", + "TX_HASH": "0xbd306018d56d8db901c403a1d5fefbe5e2b0bcb814acf2f37e6e7c1efd69c3b2", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "CLASS_TYPE": { + "CODE_HASH": "0x5c49d5dd729540180ea8d9ba102e3635e0ea473e6efcfae02be43164a28ea9b7", + "HASH_TYPE": "type", + "TX_HASH": "0x107c91377098169ce4ff65a222e6605f54df86b48c7b83c0b29e147cbf5e51fa", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "ISSUER_TYPE": { + "CODE_HASH": "0xa590c0c410920271cc9f3a3b4e2b5bcadbcbc45a76ad62301e66e196d2a6be8f", + "HASH_TYPE": "type", + "TX_HASH": "0xf06f97a3c666a42e308df9f1271686ecca8f521d0fbb676bb819952c5f6171dd", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "NFT_TYPE": { + "CODE_HASH": "0xb58dc92917aba8c30653409e96502dcb10c2036fb2febfd51aaa52dff73f66b9", + "HASH_TYPE": "type", + "TX_HASH": "0x09cb03c8bf1215232312569715be3521e6ba04fd07418cb3fe6474b3486d7c70", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "NRC_721_FACTORY": { + "CODE_HASH": "0xd2d8fa5d6fb2aa7ed70f136d1aa0902dc9b5c71c061a52dfe9602d078a743257", + "HASH_TYPE": "type", + "TX_HASH": "0xe71678ff1c9ac6841a18c2441e50a7a0e8ce5ae7832be19a5be594f506687498", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "OMNI_LOCK": { + "CODE_HASH": "0xb213f97bbf88943bc4ba244631a0629ac635955ae45ce436a52670fa1fbed43d", + "HASH_TYPE": "type", + "TX_HASH": "0xb6485dff1a790da5486c363a9d2d0c570090fd92ee279aaf5d0757522b2fbdd0", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "SPORE": { + "CODE_HASH": "0x1946ec4785ee43c25f4db46a06eef4b28cc42356c60497bb3c334aa1bb1327b0", + "HASH_TYPE": "type", + "TX_HASH": "0x83e910e2f42ca0b4ea514d548a6daf08be2285dd69f61c3f780e6d00a8309681", + "INDEX": "0x0", + "DEP_TYPE": "code" + }, + "SPORE_CLUSTER": { + "CODE_HASH": "0x8faecbd8eb1acfa23905c1e6c566321e67a9e5e72a00bedf9854640265a55385", + "HASH_TYPE": "type", + "TX_HASH": "0x2875df5d4847f7870e357401c38fa15a1a656871b468d6007600dd843021b83f", + "INDEX": "0x0", + "DEP_TYPE": "code" + } + } +} \ No newline at end of file diff --git a/packages/sync-test/source/neuron/mn b/packages/sync-test/source/neuron/mn new file mode 100644 index 0000000000..a8a7c1a845 --- /dev/null +++ b/packages/sync-test/source/neuron/mn @@ -0,0 +1,2 @@ +"brush scan basic know movie next time soccer speak loop balcony describe" +"equip slim poem depth struggle tonight define stool brave sustain spy cabbage" diff --git a/packages/sync-test/source/neuron/multiAccountInfo b/packages/sync-test/source/neuron/multiAccountInfo new file mode 100644 index 0000000000..993fc1e1da --- /dev/null +++ b/packages/sync-test/source/neuron/multiAccountInfo @@ -0,0 +1,9 @@ +txHash: +0xb7d2cd257a73bf9f0fb89ec26128954b6ccd9c1e445570c424f1d491a4f23e27 +multiAccount: +ckt1qpw9q60tppt7l3j7r09qcp7lxnp3vcanvgha8pmvsa3jplykxn32sqgckk3tt8sljaezfjtcn2d3rt0tm57f2sqqh2mmg + +txHash: +0x7aacef4732fdd8d3034db146c99a009f023179acd9c1f90471375166e242005a +multiAccount: +ckt1qpw9q60tppt7l3j7r09qcp7lxnp3vcanvgha8pmvsa3jplykxn32sqvj49lazvl7fvckw3gycsegn5d5znymfqqtjjvaw diff --git a/packages/sync-test/src/services/ckb-runner.ts b/packages/sync-test/src/services/ckb-runner.ts new file mode 100644 index 0000000000..769e3367ab --- /dev/null +++ b/packages/sync-test/src/services/ckb-runner.ts @@ -0,0 +1,114 @@ +import { BI, RPC } from '@ckb-lumos/lumos' + +import { ChildProcess, StdioNull, StdioPipe, spawn } from 'child_process' +import { mkdirSync, cpSync } from 'node:fs' +import { extractTarGz, platform, retry, rm } from '../utils/utils' +import path from 'path' + +export const CKB_HOST = `127.0.0.1` +export const CKB_RPC_PORT = 8114 + +export const CKB_RPC_URL = `http://${CKB_HOST}:${CKB_RPC_PORT}` + +let ckb: ChildProcess | null = null +let ckbMiner: ChildProcess | null = null + +const ckbBinary = (binPath: string): string => { + const binary = `${binPath}/ckb` + switch (platform()) { + case 'win': + return binary + '.exe' + case 'mac': + //todo check intel + return binary + default: + return binary + } +} + +export const startCkbNodeWithData = async (option: { + binPath: string + configPath: string + dataPath: string + decPath: string +}) => { + if (ckb !== null) { + console.info(`CKB:\tckb is not closed, close it before start...`) + await stopCkbNode() + await cleanCkbNode(option.decPath) + } + console.log('start ckb node ') + mkdirSync(option.decPath, { recursive: true }) + cpSync(option.configPath, option.decPath, { recursive: true }) + await extractTarGz(option.dataPath, path.join(option.decPath, ...['data'])) + console.log('run start ckb cmd') + const options = ['run', '-C', option.decPath, '--indexer'] + const stdio: (StdioNull | StdioPipe)[] = ['ignore', 'ignore', 'pipe'] + ckb = spawn(ckbBinary(option.binPath), options, { stdio }) + let ckbRpc = new RPC(CKB_RPC_URL) + + const tipBlock = await retry( + () => + ckbRpc.getTipBlockNumber().then(res => { + if (Number(res) <= 0) return Promise.reject() + return res + }), + { + timeout: 15_000, + delay: 100, + retries: 100, + } + ) + console.info('CKB started', BI.from(tipBlock).toNumber()) +} + +export const startCkbMiner = (option: { decPath: string; binPath: string }) => { + if (ckb == null) { + console.error(`CKB:\tckb is not started, please start ckb before starting miner...`) + return + } + if (ckbMiner !== null) { + console.log('ckb miner already start ') + return + } + const options = ['miner', '-C', option.decPath] + const stdio: (StdioNull | StdioPipe)[] = ['ignore', 'ignore', 'pipe'] + ckbMiner = spawn(ckbBinary(option.binPath), options, { stdio }) + console.log('start miner successful') +} + +export const stopCkbNode = async () => { + console.log('stop ckb node ') + const promises: Promise[] = [] + promises.push( + new Promise(innerResolve => { + if (!ckbMiner) { + innerResolve() + } else { + ckbMiner.once('close', () => innerResolve()) + ckbMiner.kill() + ckbMiner = null + } + }) + ) + promises.push( + new Promise(innerResolve => { + if (ckb) { + console.info('CKB:\tkilling node') + ckb.once('close', () => innerResolve()) + ckb.kill() + ckb = null + } else { + innerResolve() + } + }) + ) + return new Promise(resolve => { + Promise.all(promises).then(() => resolve()) + }) +} + +export const cleanCkbNode = async (decPath: string) => { + console.log('clean ckb node env:', decPath) + rm(decPath) +} diff --git a/packages/sync-test/src/services/light-runner.ts b/packages/sync-test/src/services/light-runner.ts new file mode 100644 index 0000000000..0e307d482f --- /dev/null +++ b/packages/sync-test/src/services/light-runner.ts @@ -0,0 +1,75 @@ +import { ChildProcess, spawn } from 'child_process' +import { platform, rm } from '../utils/utils' +import { cpSync, mkdirSync } from 'node:fs' +import * as fs from 'fs' + +let ckbLight: ChildProcess | null = null +let ckbLightLog: fs.WriteStream | null = null + +const ckbLightBinary = (binPath: string): string => { + const binary = `${binPath}/ckb-light-client` + switch (platform()) { + case 'win': + return binary + '.exe' + case 'mac': + //todo check intel + return binary + default: + return binary + } +} +export const startCkbLightNodeWithConfig = async (option: { binPath: string; configPath: string; decPath: string }) => { + console.log('start ckb node ') + if (ckbLight !== null) { + console.info(`CKB:\tLight client is not closed, close it before start...`) + await stopLightCkbNode() + await cleanLightCkbNode(option.decPath) + } + mkdirSync(option.decPath, { recursive: true }) + cpSync(option.configPath, option.decPath, { recursive: true }) + cpSync(ckbLightBinary(option.binPath), ckbLightBinary(option.decPath)) + const options = ['run', '--config-file', 'config.toml'] + ckbLight = spawn( + './' + ckbLightBinary(option.binPath).split('/')[ckbLightBinary(option.binPath).split('/').length - 1], + options, + { + stdio: ['ignore', 'pipe', 'pipe'], + cwd: option.decPath, + env: { RUST_LOG: 'info', ckb_light_client: 'info' }, + } + ) + // let logPath = path.join(option.decPath, "light.log") + let logPath = `tmp/light-${Date.now()}.log` + ckbLightLog = fs.createWriteStream(logPath) + ckbLight.stderr && + ckbLight.stderr.on('data', data => { + ckbLightLog?.write(data) + }) + ckbLight.stdout && + ckbLight.stdout.on('data', data => { + ckbLightLog?.write(data) + }) +} + +export const stopLightCkbNode = async () => { + console.log('stop ckb light node ') + return new Promise(resolve => { + if (ckbLight) { + console.info('CKB light:\tkilling node') + ckbLight.once('close', () => resolve()) + ckbLight.kill() + if (ckbLightLog) { + ckbLightLog.close() + ckbLightLog = null + } + ckbLight = null + } else { + resolve() + } + }) +} + +export const cleanLightCkbNode = async (path: string) => { + console.log('clean ckb light node env:', path) + rm(path) +} diff --git a/packages/sync-test/src/services/neuron-runner.ts b/packages/sync-test/src/services/neuron-runner.ts new file mode 100644 index 0000000000..3d09c0cc31 --- /dev/null +++ b/packages/sync-test/src/services/neuron-runner.ts @@ -0,0 +1,177 @@ +import * as os from 'os' +import { platform, rm } from '../utils/utils' +import * as path from 'path' +import { cpSync } from 'node:fs' +import { ChildProcess, spawn } from 'child_process' +import * as fs from 'fs' +import { CKB_RPC_URL } from './ckb-runner' +import { BI, RPC } from '@ckb-lumos/lumos' +import { scheduler } from 'timers/promises' + +let neuron: ChildProcess | null = null + +let syncResult: { + result: boolean + syncTipNumTimes: number + tipNum: number +} = { + result: false, + syncTipNumTimes: 0, + tipNum: 0, +} + +export const getNeuronPath = () => { + switch (platform()) { + case 'win': + //C:\Users\linguopeng_112963420\AppData\Roaming\Neuron + return path.join(os.homedir(), ...['AppData', 'Roaming', 'Neuron']) + case 'mac': + //todo check intel + return path.join(os.homedir(), ...['Library', 'Application Support', 'Neuron']) + case 'linux': + return path.join(os.homedir(), ...['.config', 'Neuron']) + default: + throw new Error('not support ') + } +} + +export const getNeuronEnvPath = () => { + switch (platform()) { + case 'win': + //C:\Users\linguopeng_112963420\AppData\Roaming\Neuron + return ['resources', 'app', '.env'] + case 'mac': + return ['Contents', 'Resources', 'app', '.env'] + case 'linux': + return ['squashfs-root', 'resources', 'app', '.env'] + default: + throw new Error('not support ') + } +} + +export const getNeuronStartCmd = () => { + switch (platform()) { + case 'win': + //C:\Users\linguopeng_112963420\AppData\Roaming\Neuron + return '.\\Neuron.exe' + case 'mac': + return './Contents/MacOS/neuron' + case 'linux': + return './squashfs-root/AppRun' + default: + throw new Error('not support ') + } +} + +export const startNeuronWithConfig = async (option: { + envPath: string + network: { + indexJsonPath: string + selectNetwork?: string + } + wallets: { + walletsPath: string + selectWallet?: string + } + cleanCells: boolean + logPath: string + neuronCodePath: string +}) => { + let ckbRpc = new RPC(CKB_RPC_URL) + let tipNumber = await ckbRpc.getTipBlockNumber() + syncResult = { result: false, syncTipNumTimes: 0, tipNum: BI.from(tipNumber).toNumber() } + console.log('start neuron') + + if (option.cleanCells) { + cleanNeuronSyncCells() + } + // cp env + cpSync(option.envPath, path.join(option.neuronCodePath, ...getNeuronEnvPath())) + + // cp network file + let decPath = path.join(getNeuronPath(), ...['networks', 'index.json']) + cpSync(option.network.indexJsonPath, decPath) + + // cp wallet file + cpSync(option.wallets.walletsPath, path.join(getNeuronPath(), ...['wallets']), { recursive: true }) + + // start + neuron = spawn(getNeuronStartCmd(), { + cwd: option.neuronCodePath, + stdio: ['ignore', 'pipe', 'pipe'], + // detached: true, + // shell: true, + }) + let log = fs.createWriteStream(option.logPath) + neuron.stderr && + neuron.stderr.on('data', data => { + log.write(data) + }) + neuron.stdout && + neuron.stdout.on('data', data => { + if (!syncResult.result && data.toString().includes('saved synced block')) { + let result = checkLogForNumber(data.toString()) + if (result) { + syncResult.syncTipNumTimes += 1 + } + if (syncResult.syncTipNumTimes >= 3) { + syncResult.result = true + } + } + log.write(data) + }) +} + +function checkLogForNumber(log: string): boolean { + const regex = /#(\d+)/ + const match = log.match(regex) + if (match) { + const number = parseInt(match[1], 10) + console.log( + `neuron sync:${number},neuron tipNum:${syncResult.tipNum},syncTipNumTimes:${syncResult.syncTipNumTimes}` + ) + if (number > syncResult.tipNum) { + syncResult.tipNum = number + return true + } + } + return false +} + +export const waitNeuronSyncSuccess = async (retries: number) => { + for (let i = 0; i < retries; i++) { + if (syncResult.result) { + return syncResult.result + } + await scheduler.wait(1000) + } + return Promise.reject('waitNeuronSyncSuccess time out ') +} + +export const stopNeuron = async () => { + console.log('stop neuron') + return new Promise(resolve => { + if (neuron) { + console.info('neuron:\tkilling neuron') + neuron.once('close', () => resolve()) + neuron.kill() + console.log('neuron: stop succ') + neuron = null + syncResult = { + syncTipNumTimes: 0, + result: false, + tipNum: 0, + } + } else { + resolve() + } + }) +} + +export const cleanNeuronSyncCells = () => { + rm(path.join(getNeuronPath(), ...['cells'])) +} + +export const backupNeuronCells = (decPath: string) => { + cpSync(path.join(getNeuronPath(), ...['cells']), decPath, { recursive: true }) +} diff --git a/packages/sync-test/src/services/neuron-sql-server.ts b/packages/sync-test/src/services/neuron-sql-server.ts new file mode 100644 index 0000000000..0306a2f4cf --- /dev/null +++ b/packages/sync-test/src/services/neuron-sql-server.ts @@ -0,0 +1,72 @@ +import { ComparisonOptions, SqliteDataComparator } from './sqlite-data-comparator' + +const transaction_options: ComparisonOptions = { + fileName: 'transaction_result.md', + excludedFields: ['createdAt', 'updatedAt'], + tableToCompare: 'transaction', +} +const asset_account_options: ComparisonOptions = { + fileName: 'asset_account_result.md', + excludedFields: ['id'], + tableToCompare: 'asset_account', +} +const hd_public_key_info_options: ComparisonOptions = { + fileName: 'hd_public_key_info_result.md', + excludedFields: ['id', 'createdAt'], // id + tableToCompare: 'hd_public_key_info', +} +const input_options: ComparisonOptions = { + fileName: 'input_result.md', + excludedFields: ['id'], + tableToCompare: 'input', +} +const output_options: ComparisonOptions = { + fileName: 'output_result.md', + excludedFields: ['id'], + tableToCompare: 'output', +} + +const sudt_token_info_options: ComparisonOptions = { + fileName: 'sudt_token_info_result.md', + excludedFields: ['id'], + tableToCompare: 'sudt_token_info', +} +const tx_lock_options: ComparisonOptions = { + fileName: 'tx_lock_result.md', + excludedFields: [], + tableToCompare: 'tx_lock', +} + +const indexer_tx_hash_cache_options: ComparisonOptions = { + fileName: 'indexer_tx_hash_cache_result.md', + excludedFields: ['id', 'createdAt', 'updatedAt', 'blockHash', 'blockTimestamp', 'address'], + tableToCompare: 'indexer_tx_hash_cache', +} + +export const compareNeuronDatabase = async (database1Path: string, database2Path: string, resultSavePath: string) => { + let comparator = new SqliteDataComparator(database1Path, database2Path) + + try { + let compare_tables = [ + transaction_options, + asset_account_options, + hd_public_key_info_options, + indexer_tx_hash_cache_options, + input_options, + output_options, + sudt_token_info_options, + tx_lock_options, + ] + for (let i = 0; i < compare_tables.length; i++) { + let table = compare_tables[i] + await comparator.compare(table, resultSavePath) + } + console.table(comparator.compareResult) + } catch (e) { + console.error(e) + return false + } finally { + comparator.close() + } + return comparator.compareResult.result +} diff --git a/packages/sync-test/src/services/sqlite-data-comparator.ts b/packages/sync-test/src/services/sqlite-data-comparator.ts new file mode 100644 index 0000000000..4c9d1752f5 --- /dev/null +++ b/packages/sync-test/src/services/sqlite-data-comparator.ts @@ -0,0 +1,125 @@ +import sqlite3 from 'sqlite3' +import fs from 'fs' +import path from 'path' + +export interface ComparisonOptions { + fileName: string + excludedFields: string[] + tableToCompare: string +} + +interface RowData { + [key: string]: any +} + +interface CompareResult { + result: boolean + + [key: string]: boolean +} + +export class SqliteDataComparator { + private db1: sqlite3.Database + private db2: sqlite3.Database + public compareResult: CompareResult + + constructor(database1Path: string, database2Path: string) { + this.db1 = new sqlite3.Database(database1Path) + this.db2 = new sqlite3.Database(database2Path) + this.compareResult = { result: true } + } + + private formatTableMarkdown(rows: any[]): string { + if (rows.length === 0) { + return 'No records found.' + } + + const keys = Object.keys(rows[0]) + const header = `| ${keys.join(' | ')} |\n| ${keys.map(() => '---').join(' | ')} |` + + const formattedRows = rows.map( + row => `| ${keys.map(key => (row[key] !== null && row[key] !== undefined ? row[key] : 'NULL')).join(' | ')} |` + ) + + return `${header}\n${formattedRows.join('\n')}` + } + + private formatOutputToFile(path: string, output: string) { + fs.appendFileSync(path, `${output}\n\n`) + } + + private queryDb(db: sqlite3.Database, tableName: string): Promise { + return new Promise((resolve, reject) => { + db.all(`SELECT * FROM [${tableName}]`, (err, rows: RowData[]) => { + if (err) { + reject(err) + } else { + resolve(rows) + } + }) + }) + } + + private async compareTableData(path: string, tableName: string, excludedFields: string[]): Promise { + try { + const rowsDb1 = await this.queryDb(this.db1, tableName) + const rowsDb2 = await this.queryDb(this.db2, tableName) + + const uniqueToDb1 = rowsDb1.filter( + row1 => + !rowsDb2.some(row2 => + Object.keys(row1) + .filter(key => !excludedFields.includes(key)) + .every(key => row1[key] === row2[key]) + ) + ) + + const uniqueToDb2 = rowsDb2.filter( + row2 => + !rowsDb1.some(row1 => + Object.keys(row2) + .filter(key => !excludedFields.includes(key)) + .every(key => row1[key] === row2[key]) + ) + ) + + let ret = uniqueToDb1.length === 0 && uniqueToDb2.length === 0 + console.log(`${tableName} uniqueToDb1.length:${uniqueToDb1.length},uniqueToDb2.length:${uniqueToDb2.length} `) + + if (!ret) { + fs.writeFileSync(path, '') + const output = + `## Table: ${tableName}\n\n` + + `### Unique to Database 1:\n${this.formatTableMarkdown(uniqueToDb1)}\n\n` + + `### Unique to Database 2:\n${this.formatTableMarkdown(uniqueToDb2)}\n\n` + this.formatOutputToFile(path, output) + console.log('compare failed ') + this.compareResult[tableName] = false + this.compareResult.result = false + } else { + this.compareResult[tableName] = true + console.log('compare successful ') + } + } catch (err) { + console.error(err) + } + } + + async compare(options: ComparisonOptions, dirName: string) { + const { excludedFields, tableToCompare } = options + console.log(`compare:${options.fileName}`) + let savePath = path.join(dirName, options.fileName) + try { + await this.compareTableData(savePath, tableToCompare, excludedFields) + console.log('compare finished :', options.fileName) + } catch (error) { + console.error('Error:', error) + this.compareResult.result = false + } + } + + close() { + this.db1.close() + this.db2.close() + } +} diff --git a/packages/sync-test/src/tests/common.ts b/packages/sync-test/src/tests/common.ts new file mode 100644 index 0000000000..812d21306b --- /dev/null +++ b/packages/sync-test/src/tests/common.ts @@ -0,0 +1,58 @@ +export const CKB_CONFIG = { + ckbConfigPath: 'source/ckb', + ckbLightClientConfigPath: 'source/ckb-light-client', + binPath: 'source/bin', +} + +export const CKB_CHAIN_DATA = { + dbBlock2000: 'source/ckb-data/db.2000.tar.gz', + accounts: [ + 'brush scan basic know movie next time soccer speak loop balcony describe', + 'equip slim poem depth struggle tonight define stool brave sustain spy cabbage', + ], +} + +export const NEURON_CONFIG_DATA = { + binPath: 'neuron', + envPath: { + dbBlock2000: 'source/neuron/.env', + }, + networks: { + dev: 'source/neuron/dev-wallet1/dev/networks/index.dev.json', + light: 'source/neuron/dev-wallet1/dev/networks/index.light.json', + }, + accounts: { + account1: { + path: 'source/neuron/dev-wallet1/dev/wallets', + wallets: 'wallets.json', + passwd: 'neuron.123456', + account: 'aced9a1c-85c8-40eb-811d-9399212a92b9.json', + walletsSelectId: 'c114f8e1-141f-49b1-937f-db0ab50faee5', + }, + }, +} + +// SQLite数据路径 +export const SQLITE_DATA_PATH = { + dbBlock2000: { + version: '', + fullNode: + 'source/neuron-cell-data/2000/fullNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite', + lightNode: + 'source/neuron-cell-data/2000/lightNode/wallet1/cell-0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717.sqlite', + }, +} +export const fixtures = [ + { + name: 'Sync account1 with 10000 blocks', + neuronVersion: '', + genesisHash: '0x9c96d0b369b5fd42d7e6b30d6dfdb46e32dac7293bf84de9d1e2d11ca7930717', + ckbConfig: CKB_CONFIG, + neuronConfig: NEURON_CONFIG_DATA, + syncAccount: NEURON_CONFIG_DATA.accounts.account1, + ckbDataDb: CKB_CHAIN_DATA.dbBlock2000, + compareFullNodeSqlitePath: SQLITE_DATA_PATH.dbBlock2000.fullNode, + compareLightNodeSqlitePath: SQLITE_DATA_PATH.dbBlock2000.lightNode, + tmpPath: 'tmp', + }, +] diff --git a/packages/sync-test/src/tests/sync-test-cases.spec.ts b/packages/sync-test/src/tests/sync-test-cases.spec.ts new file mode 100644 index 0000000000..8fe48f2ac2 --- /dev/null +++ b/packages/sync-test/src/tests/sync-test-cases.spec.ts @@ -0,0 +1,88 @@ +import { cleanCkbNode, startCkbMiner, startCkbNodeWithData, stopCkbNode } from '../services/ckb-runner' +import { cleanLightCkbNode, startCkbLightNodeWithConfig, stopLightCkbNode } from '../services/light-runner' +import { backupNeuronCells, startNeuronWithConfig, stopNeuron, waitNeuronSyncSuccess } from '../services/neuron-runner' +import { scheduler } from 'timers/promises' + +import { compareNeuronDatabase } from '../services/neuron-sql-server' + +import { fixtures } from './common' + +describe('sync test', function () { + fixtures.forEach((fixture, idx) => { + beforeEach(async () => { + console.log('before each') + await startCkbNodeWithData({ + binPath: fixture.ckbConfig.binPath, + dataPath: fixture.ckbDataDb, + configPath: fixture.ckbConfig.ckbConfigPath, + decPath: `${fixture.tmpPath}/ckb`, + }) + await startCkbMiner({ + binPath: fixture.ckbConfig.binPath, + decPath: `${fixture.tmpPath}/ckb`, + }) + await startCkbLightNodeWithConfig({ + binPath: fixture.ckbConfig.binPath, + configPath: fixture.ckbConfig.ckbLightClientConfigPath, + decPath: `${fixture.tmpPath}/ckb-light-client`, + }) + }) + + it('full node sync wallet 1', async () => { + console.log('full node sync start ') + await startNeuronWithConfig({ + cleanCells: true, + envPath: fixture.neuronConfig.envPath.dbBlock2000, + network: { indexJsonPath: fixture.neuronConfig.networks.dev }, + wallets: { + walletsPath: fixture.syncAccount.path, + }, + neuronCodePath: fixture.neuronConfig.binPath, + logPath: `${fixture.tmpPath}/neuron-full-node-wallet-${idx}.log`, + }) + console.log('wait sync ') + await waitNeuronSyncSuccess(30 * 60) + await stopNeuron() + await backupNeuronCells(`${fixture.tmpPath}/fullNode/wallet1`) + let result = await compareNeuronDatabase( + fixture.compareFullNodeSqlitePath, + `${fixture.tmpPath}/fullNode/wallet1/full-${fixture.genesisHash}.sqlite`, + `${fixture.tmpPath}/fullNode/wallet1` + ) + expect(result).toEqual(true) + }) + + it('light node sync wallet 1', async () => { + await startNeuronWithConfig({ + cleanCells: true, + envPath: fixture.neuronConfig.envPath.dbBlock2000, + network: { indexJsonPath: fixture.neuronConfig.networks.light }, + wallets: { + walletsPath: fixture.syncAccount.path, + }, + neuronCodePath: fixture.neuronConfig.binPath, + logPath: `${fixture.tmpPath}/neuron-light-node-wallet-${idx}.log`, + }) + await waitNeuronSyncSuccess(60 * 60) + await stopNeuron() + console.log('backupNeuronCells') + await backupNeuronCells(`${fixture.tmpPath}/lightNode/wallet1`) + console.log('compareNeuronDatabase') + const result = await compareNeuronDatabase( + fixture.compareLightNodeSqlitePath, + `${fixture.tmpPath}/lightNode/wallet1/light-${fixture.genesisHash}.sqlite`, + `${fixture.tmpPath}/lightNode/wallet1` + ) + expect(result).toEqual(true) + }) + + afterEach(async () => { + await stopCkbNode() + await stopLightCkbNode() + await scheduler.wait(3 * 1000) + await cleanCkbNode(`${fixture.tmpPath}/ckb`) + await cleanLightCkbNode(`${fixture.tmpPath}/ckb-light-client`) + await stopNeuron() + }) + }) +}) diff --git a/packages/sync-test/src/utils/utils.ts b/packages/sync-test/src/utils/utils.ts new file mode 100644 index 0000000000..b1835aafdb --- /dev/null +++ b/packages/sync-test/src/utils/utils.ts @@ -0,0 +1,114 @@ +import * as process from 'process' +import { rmSync } from 'node:fs' +import fs from 'fs' +import * as tar from 'tar' +import { scheduler } from 'timers/promises' + +export const platform = (): string => { + switch (process.platform) { + case 'win32': + return 'win' + case 'linux': + return 'linux' + case 'darwin': + return 'mac' + default: + return '' + } +} +function createTimeoutError(message?: string): Error { + const err = new Error(message) + err.name = 'TimeoutError' + return err +} + +/** + * Delay for `milliseconds` + * @param milliseconds + */ +export function delay(milliseconds: number): Promise { + return scheduler.wait(milliseconds) +} + +export interface TimeoutOptions { + milliseconds?: number + message?: string | Error +} + +/** + * Timeout a promise after `milliseconds` + * @param promise + * @param options + */ +export function timeout(promise: Promise, options: TimeoutOptions | number = {}): Promise { + const milliseconds: number = typeof options === 'number' ? options : options.milliseconds ?? 1000 + const message = typeof options === 'number' ? undefined : options.message + + const timeoutPromise = delay(milliseconds).then(() => + Promise.reject(message instanceof Error ? message : createTimeoutError(message)) + ) + + return Promise.race([promise, timeoutPromise]) +} + +export interface RetryOptions { + retries?: number + timeout?: number + delay?: number +} + +/** + * Retry a promise + * @param run + * @param options + */ +export function retry(run: () => T | Promise, options: RetryOptions = {}): Promise { + const { retries = 10, timeout: timeoutMs = 1000, delay: delayMs = 0 } = options + + let lastErr: unknown + let times = 0 + + const retryPromise = new Promise((resolve, reject) => { + function retryRun() { + times++ + if (times > retries) { + reject(lastErr) + return + } + Promise.resolve(run()).then(resolve, e => { + lastErr = e + delay(delayMs).then(retryRun) + }) + } + + retryRun() + }) + + return timeout(retryPromise, { milliseconds: timeoutMs }) +} +export function rm(path: string) { + try { + rmSync(path, { force: true, recursive: true, maxRetries: 10 }) + } catch (e) { + console.log(e) + } +} + +export async function extractTarGz(tarFilePath: string, outputFolderPath: string): Promise { + return new Promise((resolve, reject) => { + const readStream = fs.createReadStream(tarFilePath) + + readStream + .pipe( + tar.x({ + cwd: outputFolderPath, + }) + ) + .on('error', err => { + reject(err) + }) + .on('end', () => { + resolve() + }) + }) +} diff --git a/packages/sync-test/tsconfig.json b/packages/sync-test/tsconfig.json new file mode 100644 index 0000000000..4df538ca62 --- /dev/null +++ b/packages/sync-test/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "sourceMap": true, + "declaration": true, + "emitDecoratorMetadata": true, + "incremental": true, + "outDir": "dist", + "baseUrl": ".", + "lib": [ + "es2019", + "dom" + ] + }, + "include": ["src/**/*"] +}