diff options
166 files changed, 3475 insertions, 2072 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index ac17e2eeb6..ed2ab49554 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,12 +7,16 @@ clone_depth: 5 environment: APPVEYOR_SAVE_CACHE_ON_ERROR: true CLCACHE_SERVER: 1 - PACKAGES: berkeleydb boost-filesystem boost-signals2 boost-test libevent openssl rapidcheck zeromq + PACKAGES: berkeleydb boost-filesystem boost-signals2 boost-test libevent openssl rapidcheck zeromq double-conversion PATH: 'C:\Python37-x64;C:\Python37-x64\Scripts;%PATH%' PYTHONUTF8: 1 + QT_DOWNLOAD_URL: 'https://github.com/sipsorcery/qt_win_binary/releases/download/v1.0/Qt5.9.7_ssl_x64_static_vs2017.zip' + QT_DOWNLOAD_HASH: 'D4D35B8112302B67E5610A03421BB3E43FE13F14D9A5F637C22AE60DCEC0E0F5' + QT_LOCAL_PATH: 'C:\Qt5.9.7_ssl_x64_static_vs2017' cache: -- C:\tools\vcpkg\installed -> .appveyor.yml +- C:\tools\vcpkg\installed - C:\Users\appveyor\clcache -> .appveyor.yml, build_msvc\**, **\Makefile.am, **\*.vcxproj.in +- C:\Qt5.9.7_ssl_x64_static_vs2017 install: - cmd: pip install --quiet git+https://github.com/frerich/clcache.git@v4.2.0 # Disable zmq test for now since python zmq library on Windows would cause Access violation sometimes. @@ -22,6 +26,23 @@ install: - cmd: vcpkg install --triplet %PLATFORM%-windows-static %PACKAGES% > NUL before_build: - ps: clcache -M 536870912 +- ps: | + if(!(Test-Path -Path ($env:QT_LOCAL_PATH))) { + Write-Host "Downloading Qt binaries."; + Invoke-WebRequest -Uri $env:QT_DOWNLOAD_URL -Out qtdownload.zip; + Write-Host "Qt binaries successfully downloaded, checking hash against $env:QT_DOWNLOAD_HASH..."; + if((Get-FileHash qtdownload.zip).Hash -eq $env:QT_DOWNLOAD_HASH) { + Expand-Archive qtdownload.zip -DestinationPath $env:QT_LOCAL_PATH; + Write-Host "Qt binary download matched the expected hash."; + } + else { + Write-Host "ERROR: Qt binary download did not match the expected hash."; + Exit-AppveyorBuild; + } + } + else { + Write-Host "Qt binaries already present."; + } - cmd: python build_msvc\msvc-autogen.py - ps: $files = (Get-ChildItem -Recurse | where {$_.extension -eq ".vcxproj"}).FullName - ps: for (${i} = 0; ${i} -lt ${files}.length; ${i}++) { @@ -37,10 +58,13 @@ build_script: after_build: - ps: fsutil behavior set disablelastaccess 1 # Disable Access time feature on Windows (better performance) - ps: clcache -z +#- 7z a bitcoin-%APPVEYOR_BUILD_VERSION%.zip %APPVEYOR_BUILD_FOLDER%\build_msvc\%platform%\%configuration%\*.exe test_script: - cmd: src\test_bitcoin.exe -k stdout -e stdout 2> NUL - cmd: src\bench_bitcoin.exe -evals=1 -scaling=0 > NUL - ps: python test\util\bitcoin-util-test.py - cmd: python test\util\rpcauth-test.py - cmd: python test\functional\test_runner.py --ci --quiet --combinedlogslen=4000 --failfast +artifacts: +#- path: bitcoin-%APPVEYOR_BUILD_VERSION%.zip deploy: off diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 8768a8ca6b..35b42424ad 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -17,7 +17,7 @@ If the node is "stuck" during sync or giving "block checksum mismatch" errors, p <!-- What type of machine are you observing the error on (OS/CPU and disk type)? --> -<!-- For the GUI-related issue on Linux provide names and versions of a distro, a desktop environment and a graphical shell (if relevant). --> +<!-- GUI-related issue? What is your operating system and its version? If Linux, what is your desktop environment and graphical shell? --> <!-- Any extra information that might be useful in the debugging process. --> <!--- This is normally the contents of a `debug.log` or `config.log` file. Raw text or a link to a pastebin type site are preferred. --> diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..bf094e8325 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,41 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: Bug +assignees: '' + +--- + +<!-- This issue tracker is only for technical issues related to Bitcoin Core. + +General bitcoin questions and/or support requests are best directed to the Bitcoin StackExchange at https://bitcoin.stackexchange.com. + +For reporting security issues, please read instructions at https://bitcoincore.org/en/contact/. + +If the node is "stuck" during sync or giving "block checksum mismatch" errors, please ensure your hardware is stable by running memtest and observe CPU temperature with a load-test tool such as linpack before creating an issue! --> + +<!-- Describe the issue --> + +**Expected behavior** + +<!--- What behavior did you expect? --> + +**Actual behavior** + +<!--- What was the actual behavior (provide screenshots if the issue is GUI-related)? --> + +**To reproduce** + +<!--- How reliably can you reproduce the issue, what are the steps to do so? --> + +**System information** + +<!-- What version of Bitcoin Core are you using, where did you get it (website, self-compiled, etc)? --> + +<!-- What type of machine are you observing the error on (OS/CPU and disk type)? --> + +<!-- GUI-related issue? What is your operating system and its version? If Linux, what is your desktop environment and graphical shell? --> + +<!-- Any extra information that might be useful in the debugging process. --> +<!--- This is normally the contents of a `debug.log` or `config.log` file. Raw text or a link to a pastebin type site are preferred. --> diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..2d5685185e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: Feature +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] --> + +**Describe the solution you'd like** +<!-- A clear and concise description of what you want to happen. --> + +**Describe alternatives you've considered** +<!-- A clear and concise description of any alternative solutions or features you've considered. --> + +**Additional context** +<!-- Add any other context or screenshots about the feature request here. --> diff --git a/.travis.yml b/.travis.yml index f515ab2b87..ec08ab2efb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ cache: directories: - $TRAVIS_BUILD_DIR/depends/built - $TRAVIS_BUILD_DIR/depends/sdk-sources - - $HOME/.ccache + - $TRAVIS_BUILD_DIR/ci/scratch/.ccache stages: - lint - test @@ -99,7 +99,7 @@ jobs: FILE_ENV="./ci/test/00_setup_env_win64.sh" - stage: test - name: '32-bit + dash [GOAL: install] [GUI: no BIP70]' + name: '32-bit + dash [GOAL: install] [GUI: BIP70 enabled]' env: >- FILE_ENV="./ci/test/00_setup_env_i686.sh" diff --git a/.tx/config b/.tx/config index 743510a7f2..0e18a0df98 100644 --- a/.tx/config +++ b/.tx/config @@ -1,7 +1,7 @@ [main] host = https://www.transifex.com -[bitcoin.qt-translation-018x] +[bitcoin.qt-translation-019x] file_filter = src/qt/locale/bitcoin_<lang>.ts source_file = src/qt/locale/bitcoin_en.ts source_lang = en diff --git a/build_msvc/.gitignore b/build_msvc/.gitignore index 8ba65dda8f..4d4aef7e35 100644 --- a/build_msvc/.gitignore +++ b/build_msvc/.gitignore @@ -10,3 +10,5 @@ packages/* *.vcxproj.user *.vcxproj */Win32 +libbitcoin_qt/QtGeneratedFiles/* +test_bitcoin-qt/QtGeneratedFiles/* diff --git a/build_msvc/README.md b/build_msvc/README.md index 2e93979aca..88b1f514bd 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -3,13 +3,23 @@ Building Bitcoin Core with Visual Studio Introduction --------------------- -Solution and project files to build the Bitcoin Core applications (except Qt dependent ones) with Visual Studio 2017 can be found in the build_msvc directory. +Solution and project files to build the Bitcoin Core applications `msbuild` or Visual Studio can be found in the build_msvc directory. The build has been tested with Visual Studio 2017 and 2019. Building with Visual Studio is an alternative to the Linux based [cross-compiler build](https://github.com/bitcoin/bitcoin/blob/master/doc/build-windows.md). +Quick Start +--------------------- +The minimal steps required to build Bitcoin Core with the msbuild toolchain are below. More detailed instructions are contained in the following sections. + +``` +vcpkg install --triplet x64-windows-static boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb rapidcheck double-conversion +py -3 build_msvc\msvc-autogen.py +msbuild /m build_msvc\bitcoin.sln /p:Platform=x64 /p:Configuration=Release /t:build +``` + Dependencies --------------------- -A number of [open source libraries](https://github.com/bitcoin/bitcoin/blob/master/doc/dependencies.md) are required in order to be able to build Bitcoin. +A number of [open source libraries](https://github.com/bitcoin/bitcoin/blob/master/doc/dependencies.md) are required in order to be able to build Bitcoin Core. Options for installing the dependencies in a Visual Studio compatible manner are: @@ -17,18 +27,30 @@ Options for installing the dependencies in a Visual Studio compatible manner are - Download the source code, build each dependency, add the required include paths, link libraries and binary tools to the Visual Studio project files. - Use [nuget](https://www.nuget.org/) packages with the understanding that any binary files have been compiled by an untrusted third party. -The external dependencies required for the Visual Studio build are (see [dependencies.md](https://github.com/bitcoin/bitcoin/blob/master/doc/dependencies.md) for more info): +The [external dependencies](https://github.com/bitcoin/bitcoin/blob/master/doc/dependencies.md) required for building are: - Berkeley DB -- OpenSSL - Boost +- DoubleConversion - libevent -- ZeroMQ +- OpenSSL +- Qt5 - RapidCheck +- ZeroMQ + +Qt +--------------------- +All the Bitcoin Core applications are configured to build with static linking. In order to build the Bitcoin Core Qt applications a static build of Qt is required. + +The runtime library version (e.g. v141, v142) and platform type (x86 or x64) must also match. OpenSSL must also be linked into the Qt binaries in order to provide full functionality of the Bitcoin Core Qt programs. An example of the configure command to build Qtv5.9.7 locally to link with Bitcoin Core is shown below (adjust paths accordingly), note it can be expected that the configure and subsequent build will fail numerous times until dependency issues are resolved. + +```` +..\Qtv5.9.7_src\configure -developer-build -confirm-license -debug-and-release -opensource -platform win32-msvc -opengl desktop -no-shared -static -no-static-runtime -mp -qt-zlib -qt-pcre -qt-libpng -ltcg -make libs -make tools -no-libjpeg -nomake examples -no-compile-examples -no-dbus -no-libudev -no-qml-debug -no-icu -no-gtk -no-opengles3 -no-angle -no-sql-sqlite -no-sql-odbc -no-sqlite -no-libudev -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcanvas3d -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdeclarative -skip qtdoc -skip qtgamepad -skip qtgraphicaleffects -skip qtimageformats -skip qtlocation -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquickcontrols -skip qtquickcontrols2 -skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtx11extras -skip qtxmlpatterns -nomake tests -openssl-linked -IC:\Dev\github\vcpkg\installed\x64-windows-static\include -LC:\Dev\github\vcpkg\installed\x64-windows-static\lib OPENSSL_LIBS="-llibeay32 -lssleay32 -lgdi32 -luser32 -lwsock32 -ladvapi32" -prefix C:\Qt5.9.7_ssl_x64_static_vs2017 +```` + +A prebuilt version for x64 and Visual C++ runtime v141 (Visual Studio 2017) can be downloaded from [here](https://github.com/sipsorcery/qt_win_binary/releases). Please be aware this download is NOT an officially sanctioned Bitcoin Core distribution and is provided for developer convenience. It should NOT be used for builds that will be used in a production environment or with real funds. -Additional dependencies required from the [bitcoin-core](https://github.com/bitcoin-core) GitHub repository are: -- libsecp256k1 -- LevelDB +To build Bitcoin Core without Qt unload or disable the bitcoin-qt, libbitcoin_qt and test_bitcoin-qt projects. Building --------------------- @@ -38,7 +60,7 @@ The instructions below use `vcpkg` to install the dependencies. - Install the required packages (replace x64 with x86 as required): ``` - PS >.\vcpkg install --triplet x64-windows-static boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb secp256k1 leveldb rapidcheck + PS >.\vcpkg install --triplet x64-windows-static boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb rapidcheck double-conversion ``` - Use Python to generate *.vcxproj from Makefile @@ -47,4 +69,27 @@ The instructions below use `vcpkg` to install the dependencies. PS >py -3 msvc-autogen.py ``` -- Build in Visual Studio. +- An optional step is to adjust the settings in the build_msvc directory and the common.init.vcxproj file. This project file contains settings that are common to all projects such as the runtime library version and target Windows SDK version. The Qt directories can also be set. + +- Build with Visual Studio 2017 or msbuild. + +``` +msbuild /m bitcoin.sln /p:Platform=x64 /p:Configuration=Release /t:build +``` + +- Build with Visual Studio 2019 or msbuild. + +``` +msbuild /m bitcoin.sln /p:Platform=x64 /p:Configuration=Release /p:PlatformToolset=v142 /t:build +``` + +AppVeyor +--------------------- +The .appveyor.yml in the root directory is suitable to perform builds on [AppVeyor](https://www.appveyor.com/) Continuous Integration servers. The simplest way to perform an AppVeyor build is to fork Bitcoin Core and then configure a new AppVeyor Project pointing to the forked repository. + +For safety reasons the Bitcoin Core .appveyor.yml file has the artifact options disabled. The build will be performed but no executable files will be available. To enable artifacts on a forked repository uncomment the lines shown below: + +``` + #- 7z a bitcoin-%APPVEYOR_BUILD_VERSION%.zip %APPVEYOR_BUILD_FOLDER%\build_msvc\%platform%\%configuration%\*.exe + #- path: bitcoin-%APPVEYOR_BUILD_VERSION%.zip +``` diff --git a/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj b/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj.in index e64614c09d..56401d7618 100644 --- a/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj +++ b/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj.in @@ -9,27 +9,7 @@ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> </PropertyGroup> <ItemGroup> - <ClCompile Include="..\..\src\test\util.cpp" /> - <ClCompile Include="..\..\src\test\setup_common.cpp" /> - <ClCompile Include="..\..\src\bench\base58.cpp" /> - <ClCompile Include="..\..\src\bench\bech32.cpp" /> - <ClCompile Include="..\..\src\bench\bench.cpp" /> - <ClCompile Include="..\..\src\bench\bench_bitcoin.cpp" /> - <ClCompile Include="..\..\src\bench\ccoins_caching.cpp" /> - <ClCompile Include="..\..\src\bench\checkblock.cpp" /> - <ClCompile Include="..\..\src\bench\checkqueue.cpp" /> - <ClCompile Include="..\..\src\bench\coin_selection.cpp" /> - <ClCompile Include="..\..\src\bench\crypto_hash.cpp" /> - <ClCompile Include="..\..\src\bench\data.cpp" /> - <ClCompile Include="..\..\src\bench\examples.cpp" /> - <ClCompile Include="..\..\src\bench\lockedpool.cpp" /> - <ClCompile Include="..\..\src\bench\mempool_eviction.cpp" /> - <ClCompile Include="..\..\src\bench\rpc_blockchain.cpp" /> - <ClCompile Include="..\..\src\bench\rpc_mempool.cpp" /> - <ClCompile Include="..\..\src\bench\merkle_root.cpp" /> - <ClCompile Include="..\..\src\bench\rollingbloom.cpp" /> - <ClCompile Include="..\..\src\bench\wallet_balance.cpp" /> - <ClCompile Include="..\..\src\bench\verify_script.cpp" /> +@SOURCE_FILES@ </ItemGroup> <ItemGroup> <ProjectReference Include="..\libbitcoinconsensus\libbitcoinconsensus.vcxproj"> diff --git a/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj b/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj new file mode 100644 index 0000000000..fdeec55ee8 --- /dev/null +++ b/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\common.init.vcxproj" /> + <Import Project="..\common.qt.init.vcxproj" /> + <PropertyGroup Label="Globals"> + <ProjectGuid>{7E99172D-7FF2-4CB6-B736-AC9B76ED412A}</ProjectGuid> + <ConfigurationType>Application</ConfigurationType> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> + </PropertyGroup> + <ItemGroup> + <ClCompile Include="..\..\src\qt\main.cpp" /> + <ResourceCompile Include="..\..\src\qt\res\bitcoin-qt-res.rc" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\libbitcoinconsensus\libbitcoinconsensus.vcxproj"> + <Project>{2b384fa8-9ee1-4544-93cb-0d733c25e8ce}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_cli\libbitcoin_cli.vcxproj"> + <Project>{0667528c-d734-4009-adf9-c0d6c4a5a5a6}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_common\libbitcoin_common.vcxproj"> + <Project>{7c87e378-df58-482e-aa2f-1bc129bc19ce}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_crypto\libbitcoin_crypto.vcxproj"> + <Project>{6190199c-6cf4-4dad-bfbd-93fa72a760c1}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_qt\libbitcoin_qt.vcxproj"> + <Project>{2b4abff8-d1fd-4845-88c9-1f3c0a6512bf}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_server\libbitcoin_server.vcxproj"> + <Project>{460fee33-1fe1-483f-b3bf-931ff8e969a5}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_util\libbitcoin_util.vcxproj"> + <Project>{b53a5535-ee9d-4c6f-9a26-f79ee3bc3754}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_wallet\libbitcoin_wallet.vcxproj"> + <Project>{93b86837-b543-48a5-a89b-7c87abb77df2}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_zmq\libbitcoin_zmq.vcxproj"> + <Project>{792d487f-f14c-49fc-a9de-3fc150f31c3f}</Project> + </ProjectReference> + <ProjectReference Include="..\libleveldb\libleveldb.vcxproj"> + <Project>{18430fef-6b61-4c53-b396-718e02850f1b}</Project> + </ProjectReference> + <ProjectReference Include="..\libsecp256k1\libsecp256k1.vcxproj"> + <Project>{bb493552-3b8c-4a8c-bf69-a6e7a51d2ea6}</Project> + </ProjectReference> + <ProjectReference Include="..\libunivalue\libunivalue.vcxproj"> + <Project>{5724ba7d-a09a-4ba8-800b-c4c1561b3d69}</Project> + </ProjectReference> + </ItemGroup> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <AdditionalIncludeDirectories>$(QtIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <AdditionalDependencies>$(QtReleaseLibraries);%(AdditionalDependencies)</AdditionalDependencies> + </Link> + <ResourceCompile> + <AdditionalIncludeDirectories>..\..\src;</AdditionalIncludeDirectories> + <PreprocessorDefinitions>HAVE_CONFIG_H;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ResourceCompile> + </ItemDefinitionGroup> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <AdditionalIncludeDirectories>$(QtIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <AdditionalDependencies>$(QtDebugLibraries);%(AdditionalDependencies)</AdditionalDependencies> + </Link> + <ResourceCompile> + <AdditionalIncludeDirectories>..\..\src;</AdditionalIncludeDirectories> + <PreprocessorDefinitions>HAVE_CONFIG_H;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ResourceCompile> + </ItemDefinitionGroup> + + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> +</Project> diff --git a/build_msvc/bitcoin.sln b/build_msvc/bitcoin.sln index 5a6eaf54ad..d4b83b6529 100644 --- a/build_msvc/bitcoin.sln +++ b/build_msvc/bitcoin.sln @@ -40,6 +40,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsecp256k1", "libsecp256k EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libleveldb", "libleveldb\libleveldb.vcxproj", "{18430FEF-6B61-4C53-B396-718E02850F1B}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbitcoin_qt", "libbitcoin_qt\libbitcoin_qt.vcxproj", "{2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bitcoin-qt", "bitcoin-qt\bitcoin-qt.vcxproj", "{7E99172D-7FF2-4CB6-B736-AC9B76ED412A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -200,11 +204,27 @@ Global {18430FEF-6B61-4C53-B396-718E02850F1B}.Release|x64.Build.0 = Release|x64 {18430FEF-6B61-4C53-B396-718E02850F1B}.Release|x86.ActiveCfg = Release|Win32 {18430FEF-6B61-4C53-B396-718E02850F1B}.Release|x86.Build.0 = Release|Win32 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Debug|x64.ActiveCfg = Debug|x64 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Debug|x64.Build.0 = Debug|x64 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Debug|x86.ActiveCfg = Debug|Win32 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Debug|x86.Build.0 = Debug|Win32 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Release|x64.ActiveCfg = Release|x64 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Release|x64.Build.0 = Release|x64 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Release|x86.ActiveCfg = Release|Win32 + {2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}.Release|x86.Build.0 = Release|Win32 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Debug|x64.ActiveCfg = Debug|x64 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Debug|x64.Build.0 = Debug|x64 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Debug|x86.ActiveCfg = Debug|Win32 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Debug|x86.Build.0 = Debug|Win32 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Release|x64.ActiveCfg = Release|x64 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Release|x64.Build.0 = Release|x64 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Release|x86.ActiveCfg = Release|Win32 + {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DA7D16A6-E5F0-45B3-B194-C3FE64F1BFCD} + SolutionGuid = {8AA72EDA-2CD4-4564-B1E4-688B760EEEE9} EndGlobalSection EndGlobal diff --git a/build_msvc/common.init.vcxproj b/build_msvc/common.init.vcxproj index 0d186b5af2..77f6a5c621 100644 --- a/build_msvc/common.init.vcxproj +++ b/build_msvc/common.init.vcxproj @@ -6,7 +6,8 @@ <VCProjectVersion>16.0</VCProjectVersion> <VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows-static</VcpkgTriplet> <VcpkgTriplet Condition="'$(Platform)'=='x64'">x64-windows-static</VcpkgTriplet> -</PropertyGroup> + </PropertyGroup> + <PropertyGroup Condition="'$(WindowsTargetPlatformVersion)'=='' and !Exists('$(WindowsSdkDir)\DesignTime\CommonConfiguration\Neutral\Windows.props')"> <WindowsTargetPlatformVersion_10 Condition="'$(WindowsTargetPlatformVersion_10)' == ''">$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0@ProductVersion)</WindowsTargetPlatformVersion_10> <WindowsTargetPlatformVersion_10 Condition="'$(WindowsTargetPlatformVersion_10)' == ''">$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0@ProductVersion)</WindowsTargetPlatformVersion_10> @@ -20,7 +21,7 @@ <Configuration>Release</Configuration> <Platform>x64</Platform> </ProjectConfiguration> - <ProjectConfiguration Include="Debug|x64"> + <ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> </ProjectConfiguration> @@ -66,6 +67,7 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ClCompile> <Optimization>Disabled</Optimization> @@ -75,6 +77,7 @@ <AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions> </ClCompile> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ClCompile> <Optimization>MaxSpeed</Optimization> @@ -88,6 +91,7 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ClCompile> <Optimization>Disabled</Optimization> @@ -103,7 +107,7 @@ <WarningLevel>Level3</WarningLevel> <PrecompiledHeader>NotUsing</PrecompiledHeader> <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions> - <DisableSpecificWarnings>4018;4221;4244;4267;4715;4805;</DisableSpecificWarnings> + <DisableSpecificWarnings>4018;4221;4244;4267;4334;4715;4805;</DisableSpecificWarnings> <TreatWarningAsError>true</TreatWarningAsError> <PreprocessorDefinitions>ZMQ_STATIC;NOMINMAX;WIN32;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;_CONSOLE;_WIN32_WINNT=0x0601;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>..\..\src;..\..\src\univalue\include;..\..\src\secp256k1\include;..\..\src\leveldb\include;..\..\src\leveldb\helpers\memenv;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> @@ -113,6 +117,9 @@ <GenerateDebugInformation>true</GenerateDebugInformation> <AdditionalDependencies>crypt32.lib;Iphlpapi.lib;ws2_32.lib;Shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> + <Lib> + <AdditionalOptions>/ignore:4221</AdditionalOptions> + </Lib> </ItemDefinitionGroup> <Import Project="common.init.vcxproj.user" Condition="Exists('common.init.vcxproj.user')" /> </Project> diff --git a/build_msvc/common.qt.init.vcxproj b/build_msvc/common.qt.init.vcxproj new file mode 100644 index 0000000000..e21288e26b --- /dev/null +++ b/build_msvc/common.qt.init.vcxproj @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + + <PropertyGroup Label="QtGlobals"> + <QtBaseDir>C:\Qt5.9.7_ssl_x64_static_vs2017</QtBaseDir> + <QtPluginsLibraryDir>$(QtBaseDir)\plugins</QtPluginsLibraryDir> + <QtLibraryDir>$(QtBaseDir)\lib</QtLibraryDir> + <QtIncludeDir>$(QtBaseDir)\include</QtIncludeDir> + <QtIncludes>$(QtIncludeDir);$(QtIncludeDir)\QtNetwork;$(QtIncludeDir)\QtCore;$(QtIncludeDir)\QtWidgets;$(QtIncludeDir)\QtGui;</QtIncludes> + <GeneratedFilesOutDir>.\QtGeneratedFiles\qt</GeneratedFilesOutDir> + <QtToolsDir>$(QtBaseDir)\bin</QtToolsDir> + <QtReleaseLibraries>$(QtPluginsLibraryDir)\platforms\qminimal.lib;$(QtPluginsLibraryDir)\platforms\qwindows.lib;$(QtLibraryDir)\qtfreetype.lib;$(QtLibraryDir)\qtharfbuzz.lib;$(QtLibraryDir)\qtlibpng.lib;$(QtLibraryDir)\qtpcre2.lib;$(QtLibraryDir)\Qt5AccessibilitySupport.lib;$(QtLibraryDir)\Qt5Core.lib;$(QtLibraryDir)\Qt5Concurrent.lib;$(QtLibraryDir)\Qt5EventDispatcherSupport.lib;$(QtLibraryDir)\Qt5FontDatabaseSupport.lib;$(QtLibraryDir)\Qt5Gui.lib;$(QtLibraryDir)\Qt5Network.lib;$(QtLibraryDir)\Qt5PlatformCompositorSupport.lib;$(QtLibraryDir)\Qt5ThemeSupport.lib;$(QtLibraryDir)\Qt5Widgets.lib;$(QtLibraryDir)\Qt5WinExtras.lib;$(QtLibraryDir)\qtmain.lib;userenv.lib;netapi32.lib;imm32.lib;Dwmapi.lib;version.lib;winmm.lib;UxTheme.lib</QtReleaseLibraries> + <QtDebugLibraries>$(QtPluginsLibraryDir)\platforms\qwindowsd.lib;$(QtPluginsLibraryDir)\platforms\qminimald.lib;$(QtLibraryDir)\*d.lib;crypt32.lib;userenv.lib;netapi32.lib;imm32.lib;Dwmapi.lib;version.lib;winmm.lib;UxTheme.lib</QtDebugLibraries> + </PropertyGroup> + +</Project> diff --git a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj index f21ba7a82b..992f64ec2e 100644 --- a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj +++ b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj @@ -1,946 +1,230 @@ <?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Label="configInitTarget" Project="..\common.init.vcxproj" /> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Debug|x64"> - <Configuration>Debug</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|x64"> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - </ItemGroup> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\common.init.vcxproj" /> + <Import Project="..\common.qt.init.vcxproj" /> <PropertyGroup Label="Globals"> - <VCProjectVersion>15.0</VCProjectVersion> - <VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows-static</VcpkgTriplet> - <VcpkgTriplet Condition="'$(Platform)'=='x64'">x64-windows-static</VcpkgTriplet> <ProjectGuid>{2B4ABFF8-D1FD-4845-88C9-1F3C0A6512BF}</ProjectGuid> + <ConfigurationType>StaticLibrary</ConfigurationType> </PropertyGroup> <ItemGroup> - <CustomBuild Include="..\..\src\qt\bitcoin.qrc"> - <Command>"$(QTDIR)\bincc.exe" -name bitcoin "%(Fullpath)" -o .\GeneratedFiles\qrc_bitcoin.cpp</Command> - <Message>Qt rcc generation for %(Identity)</Message> - <Outputs>.\GeneratedFiles\qrc_bitcoin.cpp</Outputs> - <AdditionalInputs>(QTDIR)\bincc.exe</AdditionalInputs> - </CustomBuild> - <CustomBuild Include="..\..\src\qt\bitcoin_locale.qrc"> - <Command>"$(QTDIR)\bincc.exe" -name bitcoin_locale "%(Fullpath)" -o .\GeneratedFiles\qrc_bitcoin_locale.cpp</Command> - <Message>Qt rcc generation for %(Identity)</Message> - <Outputs>.\GeneratedFiles\qrc_bitcoin_locale.cpp</Outputs> - <AdditionalInputs>(QTDIR)\bincc.exe</AdditionalInputs> - </CustomBuild> - <None Include="..\..\src\qt\forms\addressbookpage.ui" /> - <None Include="..\..\src\qt\forms\askpassphrasedialog.ui" /> - <None Include="..\..\src\qt\forms\coincontroldialog.ui" /> - <None Include="..\..\src\qt\forms\debugwindow.ui" /> - <None Include="..\..\src\qt\forms\editaddressdialog.ui" /> - <None Include="..\..\src\qt\forms\helpmessagedialog.ui" /> - <None Include="..\..\src\qt\forms\intro.ui" /> - <None Include="..\..\src\qt\forms\modaloverlay.ui" /> - <None Include="..\..\src\qt\forms\openuridialog.ui" /> - <None Include="..\..\src\qt\forms\optionsdialog.ui" /> - <None Include="..\..\src\qt\forms\overviewpage.ui" /> - <None Include="..\..\src\qt\formseceivecoinsdialog.ui" /> - <None Include="..\..\src\qt\formseceiverequestdialog.ui" /> - <None Include="..\..\src\qt\forms\sendcoinsdialog.ui" /> - <None Include="..\..\src\qt\forms\sendcoinsentry.ui" /> - <None Include="..\..\src\qt\forms\signverifymessagedialog.ui" /> - <None Include="..\..\src\qt\forms\transactiondescdialog.ui" /> - <None Include="..\..\src\qt\locale\bitcoin_af.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_af_ZA.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_am.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ar.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_be_BY.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_bg.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_bg_BG.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ca.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ca%40valencia.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ca_ES.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_cs.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_cy.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_da.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_de.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_el.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_el_GR.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_en.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_en_GB.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_eo.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_AR.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_CL.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_CO.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_DO.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_ES.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_MX.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_UY.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_es_VE.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_et.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_et_EE.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_eu_ES.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_fa.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_fa_IR.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_fi.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_fr.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_fr_CA.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_fr_FR.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_gl.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_he.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_hi_IN.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_hr.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_hu.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_id.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_id_ID.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_is.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_it.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_it_IT.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ja.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ka.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_kk_KZ.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ko.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ko_KR.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ku_IQ.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ky.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_la.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_lt.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_lv_LV.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_mk_MK.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ml.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_mn.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ms_MY.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_nb.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ne.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_nl.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_pam.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_pl.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_pt_BR.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_pt_PT.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ro.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ro_RO.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ru.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ru_RU.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_sk.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_sl_SI.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_sn.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_sq.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_sr.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_sr%40latin.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_sv.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_szl.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ta.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_th_TH.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_tr.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_tr_TR.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_uk.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_ur_PK.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_uz%40Cyrl.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_vi.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_vi_VN.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_zh.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_zh_CN.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_zh_HK.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qt\locale\bitcoin_zh_TW.ts"> - <DeploymentContent>true</DeploymentContent> - </None> - <CustomBuild Include="..\..\src\qt\paymentrequest.proto"> - <FileType>Document</FileType> - <Command>F:\Dependencies\protobuf-cpp-3.4.1\protobuf-3.4.1\cmake\build\vs\Debug\protoc.exe --proto_path=%(RootDir)%(Directory) %(Fullpath) --cpp_out=.\GeneratedFiles</Command> - <Message>ProtoBuf source generation %(RootDir)%(Directory) %(Filename)</Message> - <Outputs>.\GeneratedFiles\%(Filename).pb.h;.\GeneratedFiles\(%Filename).pb.cc</Outputs> - <AdditionalInputs>F:\Dependencies\protobuf-cpp-3.4.1\protobuf-3.4.1\cmake\build\vs\Debug\protoc.exe</AdditionalInputs> - <LinkObjects>false</LinkObjects> - <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">F:\deps\protobuf\protobuf-3.4.1\cmake\build\vs\Debug\protoc.exe --proto_path=%(RootDir)%(Directory) %(Fullpath) --cpp_out=.\GeneratedFiles</Command> - <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">F:\deps\protobuf\protobuf-3.4.1\cmake\build\vs\Debug\protoc.exe</AdditionalInputs> - </CustomBuild> - <None Include="..\..\src\qt\macdockiconhandler.mm" /> - <None Include="..\..\src\qt\macnotificationhandler.mm" /> - <None Include="..\..\src\qtes\icons\bitcoin.icns"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\bitcoin.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\clock_0.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\clock_1.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\clock_2.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\clock_3.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\clock_4.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\connect-0.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\connect-1.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\connect-2.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\connect-3.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\connect-4.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\hd_disabled.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\hd_enabled.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\mine.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\network_disabled.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\qt.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\transaction0.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\tx_in.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="..\..\src\qtes\src\tx_inout.svg"> - <DeploymentContent>true</DeploymentContent> - </None> - <None Include="GeneratedFiles\bitcoin.moc" /> - <None Include="GeneratedFiles\bitcoinamountfield.moc" /> - <None Include="GeneratedFiles\intro.moc" /> - <None Include="GeneratedFiles\overviewpage.moc" /> - <None Include="GeneratedFilespcconsole.moc" /> - </ItemGroup> - <ItemGroup> - <Image Include="..\..\src\qtes\icons\add.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\address-book.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\bitcoin.ico"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\bitcoin.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\bitcoin_testnet.ico"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\chevron.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\clock1.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\clock2.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\clock3.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\clock4.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\clock5.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\connect0.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\connect1.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\connect2.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\connect3.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\connect4.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\edit.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\editcopy.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\editpaste.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\export.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\eye.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\eye_minus.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\eye_plus.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\fontbigger.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\fontsmaller.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\hd_disabled.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\hd_enabled.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\history.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\info.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\lock_closed.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\lock_open.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\network_disabled.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\overview.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\iconseceive.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\iconsemove.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\send.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\synced.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\transaction0.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\transaction2.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\transaction_abandoned.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\transaction_conflicted.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\tx_inout.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\tx_input.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\tx_mined.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\tx_output.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\icons\warning.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-000.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-001.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-002.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-003.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-004.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-005.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-006.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-007.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-008.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-009.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-010.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-011.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-012.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-013.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-014.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-015.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-016.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-017.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-018.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-019.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-020.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-021.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-022.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-023.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-024.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-025.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-026.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-027.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-028.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-029.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-030.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-031.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-032.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-033.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-034.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\movies\spinner-035.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - <Image Include="..\..\src\qtes\src\spinner.png"> - <DeploymentContent>true</DeploymentContent> - </Image> - </ItemGroup> - <ItemGroup> - <ClCompile Include="GeneratedFiles\qrc_bitcoin.cpp" /> - <ClCompile Include="GeneratedFiles\qrc_bitcoin_locale.cpp" /> - <CustomBuild Include="..\..\src\qt\bitcoin.cpp"> - <Command>"$(QTDIR)\bin\moc.exe" "%(Fullpath)" -o .\GeneratedFiles\%(Filename).moc $(MOC_DEF)</Command> - <Message>Qt moc generation for %(Identity)</Message> - <Outputs>.\GeneratedFiles\%(Filename).moc</Outputs> - <AdditionalInputs>(QTDIR)\bin\moc.exe</AdditionalInputs> - </CustomBuild> - <CustomBuild Include="..\..\src\qt\bitcoinamountfield.cpp"> - <Command>"$(QTDIR)\bin\moc.exe" "%(Fullpath)" -o .\GeneratedFiles\%(Filename).moc $(MOC_DEF)</Command> - <Message>Qt moc generation for %(Identity)</Message> - <Outputs>.\GeneratedFiles\%(Filename).moc</Outputs> - <AdditionalInputs>(QTDIR)\bin\moc.exe</AdditionalInputs> - </CustomBuild> - <CustomBuild Include="..\..\src\qt\intro.cpp"> - <Command>"$(QTDIR)\bin\moc.exe" "%(Fullpath)" -o .\GeneratedFiles\%(Filename).moc $(MOC_DEF)</Command> - <Message>Qt moc generation for %(Identity)</Message> - <Outputs>.\GeneratedFiles\%(Filename).moc</Outputs> - <AdditionalInputs>(QTDIR)\bin\moc.exe</AdditionalInputs> - </CustomBuild> - <CustomBuild Include="..\..\src\qt\overviewpage.cpp"> - <Command>"$(QTDIR)\bin\moc.exe" "%(Fullpath)" -o .\GeneratedFiles\%(Filename).moc $(MOC_DEF)</Command> - <Message>Qt moc generation for %(Identity)</Message> - <Outputs>.\GeneratedFiles\%(Filename).moc</Outputs> - <AdditionalInputs>(QTDIR)\bin\moc.exe</AdditionalInputs> - </CustomBuild> - <CustomBuild Include="..\..\src\qtpcconsole.cpp"> - <Command>"$(QTDIR)\bin\moc.exe" "%(Fullpath)" -o .\GeneratedFiles\%(Filename).moc $(MOC_DEF)</Command> - <Message>Qt moc generation for %(Identity)</Message> - <Outputs>.\GeneratedFiles\%(Filename).moc</Outputs> - <AdditionalInputs>(QTDIR)\bin\moc.exe</AdditionalInputs> - </CustomBuild> - <ClCompile Include="GeneratedFiles\moc_addressbookpage.cpp" /> - <ClCompile Include="GeneratedFiles\moc_addresstablemodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_askpassphrasedialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_bantablemodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_bitcoinaddressvalidator.cpp" /> - <ClCompile Include="GeneratedFiles\moc_bitcoinamountfield.cpp" /> - <ClCompile Include="GeneratedFiles\moc_bitcoingui.cpp" /> - <ClCompile Include="GeneratedFiles\moc_bitcoinunits.cpp" /> - <ClCompile Include="GeneratedFiles\moc_callback.cpp" /> - <ClCompile Include="GeneratedFiles\moc_clientmodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_coincontroldialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_coincontroltreewidget.cpp" /> - <ClCompile Include="GeneratedFiles\moc_csvmodelwriter.cpp" /> - <ClCompile Include="GeneratedFiles\moc_editaddressdialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_guiconstants.cpp" /> - <ClCompile Include="GeneratedFiles\moc_guiutil.cpp" /> - <ClCompile Include="GeneratedFiles\moc_intro.cpp" /> - <ClCompile Include="GeneratedFiles\moc_macdockiconhandler.cpp" /> - <ClCompile Include="GeneratedFiles\moc_macnotificationhandler.cpp" /> - <ClCompile Include="GeneratedFiles\moc_modaloverlay.cpp" /> - <ClCompile Include="GeneratedFiles\moc_networkstyle.cpp" /> - <ClCompile Include="GeneratedFiles\moc_notificator.cpp" /> - <ClCompile Include="GeneratedFiles\moc_openuridialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_optionsdialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_optionsmodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_overviewpage.cpp" /> - <ClCompile Include="GeneratedFiles\moc_paymentrequestplus.cpp" /> - <ClCompile Include="GeneratedFiles\moc_paymentserver.cpp" /> - <ClCompile Include="GeneratedFiles\moc_peertablemodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_platformstyle.cpp" /> - <ClCompile Include="GeneratedFiles\moc_qvalidatedlineedit.cpp" /> - <ClCompile Include="GeneratedFiles\moc_qvaluecombobox.cpp" /> - <ClCompile Include="GeneratedFiles\moc_receivecoinsdialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_receiverequestdialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_recentrequeststablemodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_rpcconsole.cpp" /> - <ClCompile Include="GeneratedFiles\moc_sendcoinsdialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_sendcoinsentry.cpp" /> - <ClCompile Include="GeneratedFiles\moc_signverifymessagedialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_splashscreen.cpp" /> - <ClCompile Include="GeneratedFiles\moc_trafficgraphwidget.cpp" /> - <ClCompile Include="GeneratedFiles\moc_transactiondesc.cpp" /> - <ClCompile Include="GeneratedFiles\moc_transactiondescdialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_transactionfilterproxy.cpp" /> - <ClCompile Include="GeneratedFiles\moc_transactionrecord.cpp" /> - <ClCompile Include="GeneratedFiles\moc_transactiontablemodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_transactionview.cpp" /> - <ClCompile Include="GeneratedFiles\moc_utilitydialog.cpp" /> - <ClCompile Include="GeneratedFiles\moc_walletframe.cpp" /> - <ClCompile Include="GeneratedFiles\moc_walletmodel.cpp" /> - <ClCompile Include="GeneratedFiles\moc_walletmodeltransaction.cpp" /> - <ClCompile Include="GeneratedFiles\moc_walletview.cpp" /> - <ClCompile Include="GeneratedFiles\moc_winshutdownmonitor.cpp" /> - <ClCompile Include="GeneratedFiles\paymentrequest.pb.cc" /> - </ItemGroup> - <ItemGroup> - <ClInclude Include="GeneratedFiles\paymentrequest.pb.h" /> - <ClInclude Include="GeneratedFiles\ui_addressbookpage.h" /> - <ClInclude Include="GeneratedFiles\ui_askpassphrasedialog.h" /> - <ClInclude Include="GeneratedFiles\ui_coincontroldialog.h" /> - <ClInclude Include="GeneratedFiles\ui_debugwindow.h" /> - <ClInclude Include="GeneratedFiles\ui_editaddressdialog.h" /> - <ClInclude Include="GeneratedFiles\ui_helpmessagedialog.h" /> - <ClInclude Include="GeneratedFiles\ui_intro.h" /> - <ClInclude Include="GeneratedFiles\ui_modaloverlay.h" /> - <ClInclude Include="GeneratedFiles\ui_openuridialog.h" /> - <ClInclude Include="GeneratedFiles\ui_optionsdialog.h" /> - <ClInclude Include="GeneratedFiles\ui_overviewpage.h" /> - <ClInclude Include="GeneratedFiles\ui_receivecoinsdialog.h" /> - <ClInclude Include="GeneratedFiles\ui_receiverequestdialog.h" /> - <ClInclude Include="GeneratedFiles\ui_sendcoinsdialog.h" /> - <ClInclude Include="GeneratedFiles\ui_sendcoinsentry.h" /> - <ClInclude Include="GeneratedFiles\ui_signverifymessagedialog.h" /> - <ClInclude Include="GeneratedFiles\ui_transactiondescdialog.h" /> + <ClCompile Include="..\..\src\qt\addressbookpage.cpp" /> + <ClCompile Include="..\..\src\qt\addresstablemodel.cpp" /> + <ClCompile Include="..\..\src\qt\askpassphrasedialog.cpp" /> + <ClCompile Include="..\..\src\qt\bantablemodel.cpp" /> + <ClCompile Include="..\..\src\qt\bitcoin.cpp" /> + <ClCompile Include="..\..\src\qt\bitcoinaddressvalidator.cpp" /> + <ClCompile Include="..\..\src\qt\bitcoinamountfield.cpp" /> + <ClCompile Include="..\..\src\qt\bitcoingui.cpp" /> + <ClCompile Include="..\..\src\qt\bitcoinstrings.cpp" /> + <ClCompile Include="..\..\src\qt\bitcoinunits.cpp" /> + <ClCompile Include="..\..\src\qt\clientmodel.cpp" /> + <ClCompile Include="..\..\src\qt\coincontroldialog.cpp" /> + <ClCompile Include="..\..\src\qt\coincontroltreewidget.cpp" /> + <ClCompile Include="..\..\src\qt\createwalletdialog.cpp" /> + <ClCompile Include="..\..\src\qt\csvmodelwriter.cpp" /> + <ClCompile Include="..\..\src\qt\editaddressdialog.cpp" /> + <ClCompile Include="..\..\src\qt\guiutil.cpp" /> + <ClCompile Include="..\..\src\qt\intro.cpp" /> + <ClCompile Include="..\..\src\qt\modaloverlay.cpp" /> + <ClCompile Include="..\..\src\qt\networkstyle.cpp" /> + <ClCompile Include="..\..\src\qt\notificator.cpp" /> + <ClCompile Include="..\..\src\qt\openuridialog.cpp" /> + <ClCompile Include="..\..\src\qt\optionsdialog.cpp" /> + <ClCompile Include="..\..\src\qt\optionsmodel.cpp" /> + <ClCompile Include="..\..\src\qt\overviewpage.cpp" /> + <ClCompile Include="..\..\src\qt\paymentserver.cpp" /> + <ClCompile Include="..\..\src\qt\peertablemodel.cpp" /> + <ClCompile Include="..\..\src\qt\platformstyle.cpp" /> + <ClCompile Include="..\..\src\qt\qrimagewidget.cpp" /> + <ClCompile Include="..\..\src\qt\qvalidatedlineedit.cpp" /> + <ClCompile Include="..\..\src\qt\qvaluecombobox.cpp" /> + <ClCompile Include="..\..\src\qt\receivecoinsdialog.cpp" /> + <ClCompile Include="..\..\src\qt\receiverequestdialog.cpp" /> + <ClCompile Include="..\..\src\qt\recentrequeststablemodel.cpp" /> + <ClCompile Include="..\..\src\qt\rpcconsole.cpp" /> + <ClCompile Include="..\..\src\qt\sendcoinsdialog.cpp" /> + <ClCompile Include="..\..\src\qt\sendcoinsentry.cpp" /> + <ClCompile Include="..\..\src\qt\signverifymessagedialog.cpp" /> + <ClCompile Include="..\..\src\qt\splashscreen.cpp" /> + <ClCompile Include="..\..\src\qt\trafficgraphwidget.cpp" /> + <ClCompile Include="..\..\src\qt\transactiondesc.cpp" /> + <ClCompile Include="..\..\src\qt\transactiondescdialog.cpp" /> + <ClCompile Include="..\..\src\qt\transactionfilterproxy.cpp" /> + <ClCompile Include="..\..\src\qt\transactionrecord.cpp" /> + <ClCompile Include="..\..\src\qt\transactiontablemodel.cpp" /> + <ClCompile Include="..\..\src\qt\transactionview.cpp" /> + <ClCompile Include="..\..\src\qt\utilitydialog.cpp" /> + <ClCompile Include="..\..\src\qt\walletcontroller.cpp" /> + <ClCompile Include="..\..\src\qt\walletframe.cpp" /> + <ClCompile Include="..\..\src\qt\walletmodel.cpp" /> + <ClCompile Include="..\..\src\qt\walletmodeltransaction.cpp" /> + <ClCompile Include="..\..\src\qt\walletview.cpp" /> + <ClCompile Include="..\..\src\qt\winshutdownmonitor.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_addressbookpage.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_addresstablemodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_askpassphrasedialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_bantablemodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_bitcoin.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_bitcoinaddressvalidator.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_bitcoinamountfield.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_bitcoingui.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_bitcoinunits.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_clientmodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_coincontroldialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_coincontroltreewidget.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_createwalletdialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_csvmodelwriter.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_editaddressdialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_guiutil.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_intro.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_modaloverlay.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_networkstyle.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_notificator.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_openuridialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_optionsdialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_optionsmodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_overviewpage.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_paymentserver.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_peertablemodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_platformstyle.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_qrimagewidget.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_qvalidatedlineedit.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_qvaluecombobox.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_receivecoinsdialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_receiverequestdialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_recentrequeststablemodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_rpcconsole.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_sendcoinsdialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_sendcoinsentry.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_signverifymessagedialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_splashscreen.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_trafficgraphwidget.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_transactiondesc.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_transactiondescdialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_transactionfilterproxy.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_transactionrecord.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_transactiontablemodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_transactionview.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_utilitydialog.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_walletcontroller.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_walletframe.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_walletmodel.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_walletmodeltransaction.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_walletview.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_winshutdownmonitor.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\rcc\qrc_bitcoin.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\rcc\qrc_bitcoin_locale.cpp" /> </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>StaticLibrary</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>StaticLibrary</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> - <ConfigurationType>StaticLibrary</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> - <ConfigurationType>StaticLibrary</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="Shared"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - <Linkage-protobuf>static</Linkage-protobuf> - <CustomBuildBeforeTargets>ClCompile</CustomBuildBeforeTargets> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - <Linkage-protobuf>static</Linkage-protobuf> - <CustomBuildBeforeTargets>ClCompile</CustomBuildBeforeTargets> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> - <LinkIncremental>true</LinkIncremental> - <Linkage-protobuf>static</Linkage-protobuf> - <CustomBuildBeforeTargets>ClCompile</CustomBuildBeforeTargets> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <LinkIncremental>false</LinkIncremental> - <Linkage-protobuf>static</Linkage-protobuf> - <CustomBuildBeforeTargets>ClCompile</CustomBuildBeforeTargets> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>false</IntrinsicFunctions> - <PreprocessorDefinitions>_X86_;WIN32;HAVE_CONFIG_H;_SCL_SECURE_NO_WARNINGS;WIN32;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> - <AdditionalIncludeDirectories>.\GeneratedFiles;..\..\src;..\..\src\univalue\include;.\QtGenerated\mocheaders</AdditionalIncludeDirectories> - <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <PreprocessorDefinitions>_AMD64_;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>$(QtIncludes);$(GeneratedFilesOutDir)\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_X86_;WIN32;HAVE_CONFIG_H;_SCL_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> - <AdditionalIncludeDirectories>.\GeneratedFiles;..\..\src;..\..\src\univalue\include;.\QtGenerated\mocheaders</AdditionalIncludeDirectories> - <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <PreprocessorDefinitions>_AMD64_;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>$(QtIncludes);$(GeneratedFilesOutDir)\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>WIN32;HAVE_CONFIG_H;_SCL_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> - <AdditionalIncludeDirectories>.\GeneratedFiles;..\..\src;..\..\src\univalue\include;.\QtGenerated\mocheaders</AdditionalIncludeDirectories> - <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <PreprocessorDefinitions>_X86_;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>$(QtIncludes);$(GeneratedFilesOutDir)\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;HAVE_CONFIG_H;_SCL_SECURE_NO_WARNINGS;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> - <AdditionalIncludeDirectories>.\GeneratedFiles;..\..\src;..\..\src\univalue\include;.\QtGenerated\mocheaders</AdditionalIncludeDirectories> - <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <PreprocessorDefinitions>_X86_;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>$(QtIncludes);$(GeneratedFilesOutDir)\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> </ItemDefinitionGroup> - <Import Label="configTarget" Project="..\common.vcxproj" /> - <Target Name="QtHeadersMocCodeGeneration" BeforeTargets="PrepareForBuild"> + + <ItemGroup> + <QT_MOC Include="..\..\src\qt\bitcoinamountfield.cpp" /> + <QT_MOC Include="..\..\src\qt\intro.cpp" /> + <QT_MOC Include="..\..\src\qt\overviewpage.cpp" /> + <QT_MOC Include="..\..\src\qt\rpcconsole.cpp" /> + <MocHeaderFiles Include="..\..\src\qt\*.h" /> + <ResourceTemplates Include="..\..\src\qt\*.qrc" /> + <UiFormFiles Include="..\..\src\qt\forms\*.ui" /> + <TranslationFiles Include="..\..\src\qt\locale\*.ts" /> + </ItemGroup> + + <Target Name="moccode" Inputs="@(QT_MOC)" Outputs="@(QT_MOC->'$(GeneratedFilesOutDir)\%(Filename).moc')"> <PropertyGroup> - <ErrorText>There was an error executing the Qt headers moc code generation tasks.</ErrorText> + <ErrorText>There was an error executing the libbitcoin_qt moc code include generation task.</ErrorText> </PropertyGroup> - <ItemGroup> - <QtMocHeaderFiles Include="..\..\src\qt\*.h" /> - </ItemGroup> - <Exec Command="$(QTDIR)\bin\moc.exe "%(QtMocHeaderFiles.Identity)" -o .\GeneratedFiles\moc_%(Filename).cpp $(MOC_DEF)" /> + <MakeDir Directories="$(GeneratedFilesOutDir)" /> + <Exec Command="echo Performing libbitcoin_qt moc code include generation task, output path $(GeneratedFilesOutDir)." /> + <Exec Command="echo $(QtToolsDir)\moc.exe $(MOC_DEFINES) "%(QT_MOC.Identity)" -o $(GeneratedFilesOutDir)\%(Filename).moc." /> + <Exec Command="$(QtToolsDir)\moc.exe $(MOC_DEFINES) "%(QT_MOC.Identity)" -o $(GeneratedFilesOutDir)\%(Filename).moc" /> </Target> - <Target Name="QtFormsCodeGeneration" BeforeTargets="PrepareForBuild"> + + <Target Name="mocheader" Inputs="@(MocHeaderFiles)" Outputs="@(MocHeaderFiles->'$(GeneratedFilesOutDir)\moc\moc_%(Filename).cpp')"> <PropertyGroup> - <ErrorText>There was an error executing the Qt forms code generation tasks.</ErrorText> + <ErrorText>There was an error executing the libbitcoin_qt moc header generation task.</ErrorText> </PropertyGroup> - <ItemGroup> - <QtFormFiles Include="..\..\src\qt\forms\*.ui" /> - </ItemGroup> - <Exec Command="$(QTDIR)\bin\uic.exe "%(QtFormFiles.Identity)" -o .\GeneratedFiles\ui_%(Filename).h" /> + <Exec Command="echo Performing libbitcoin_qt moc header generation task, output path $(GeneratedFilesOutDir)\moc." /> + <Exec Command="echo $(QtToolsDir)\moc.exe $(MOC_DEFINES) "%(MocHeaderFiles.Identity)" -o $(GeneratedFilesOutDir)\moc\moc_%(Filename).cpp." /> + <MakeDir Directories="$(GeneratedFilesOutDir)\moc\" /> + <Exec Command="$(QtToolsDir)\moc.exe $(MOC_DEFINES) "%(MocHeaderFiles.Identity)" -o $(GeneratedFilesOutDir)\moc\moc_%(Filename).cpp" /> </Target> - <Target Name="QtLocaleCodeGeneration" BeforeTargets="PrepareForBuild"> + + <Target Name="forms" Inputs="@(UiFormFiles)" Outputs="@(UiFormFiles->'$(GeneratedFilesOutDir)\forms\ui_%(Filename).h')"> <PropertyGroup> - <ErrorText>There was an error executing the Qt local code generation tasks.</ErrorText> + <ErrorText>There was an error executing the libbitcoin_qt forms header generation task.</ErrorText> </PropertyGroup> - <ItemGroup> - <QtLocaleFiles Include="..\..\src\qt\locale\*.ts" /> - </ItemGroup> - <Exec Command="$(QTDIR)\bin\lrelease.exe "%(QtLocaleFiles.Identity)" -qm ..\..\src\qt\locale\%(Filename).qm" /> + <Exec Command="echo Performing libbitcoin_qt forms header generation task, output path $(GeneratedFilesOutDir)\forms." /> + <MakeDir Directories="$(GeneratedFilesOutDir)\forms\" /> + <Exec Command="$(QtToolsDir)\uic.exe "%(UiFormFiles.Identity)" -o $(GeneratedFilesOutDir)\forms\ui_%(Filename).h" /> </Target> - <ImportGroup Label="ExtensionTargets"> - <!--<Import Label="berkleyDbTarget" Project="f:\deps\db-4.8.30\db.targets" /> - <Import Label="opensslTarget" Project="f:\deps\openssl\1.0.2\openssl.targets" /> - <Import Label="qtTarget" Project="F:\deps\qt\5.9.2-git-ssl\vc141-x86elease\qt.targets" /> - <Import Label="protobufTarget" Project="f:\deps\protobuf\protobuf.targets" />--> - </ImportGroup> - <ProjectExtensions> - <VisualStudio> - <UserProperties MocDir=".\GeneratedFiles" UicDir=".\GeneratedFiles" RccDir=".\GeneratedFiles" lupdateOptions="" lupdateOnBuild="0" lreleaseOptions="" Qt5Version_x0020_Win32="5.9.1_vs140_x86" Qt5Version_x0020_x64="5.9.1_vs140_x86" MocOptions="DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets"" /> - </VisualStudio> - </ProjectExtensions> + + <Target Name="translation" Inputs="@(TranslationFiles)" Outputs="@(TranslationFiles->'..\..\src\qt\locale\%(Filename).qm')"> + <PropertyGroup> + <ErrorText>There was an error executing the libbitcoin_qt translation file generation task.</ErrorText> + </PropertyGroup> + <Exec Command="echo Performing libbitcoin_qt translation file generation task." /> + <Exec Command="$(QtToolsDir)\lrelease.exe "%(TranslationFiles.Identity)" -qm ..\..\src\qt\locale\%(Filename).qm" /> + </Target> + + <Target Name="resource" Inputs="@(ResourceTemplates)" Outputs="@(ResourceTemplates->'$(GeneratedFilesOutDir)\rcc\qrc_%(Filename).cpp')" DependsOnTargets="translation"> + <PropertyGroup> + <ErrorText>There was an error executing the libbitcoin_qt resource code generation task.</ErrorText> + </PropertyGroup> + <Exec Command="echo Performing libbitcoin_qt resource code generation task, output path $(GeneratedFilesOutDir)\rcc." /> + <MakeDir Directories="$(GeneratedFilesOutDir)\rcc\" /> + <Exec Command="$(QtToolsDir)\rcc.exe --verbose --name %(Filename) "%(ResourceTemplates.Identity)" -o $(GeneratedFilesOutDir)\rcc\qrc_%(Filename).cpp" /> + </Target> + + <Target Name="qtclean"> + <Exec Command="echo Clean libbitcoin_qt generated files from $(GeneratedFilesOutDir)." /> + <RemoveDir Directories="$(GeneratedFilesOutDir)\forms;$(GeneratedFilesOutDir)\moc;$(GeneratedFilesOutDir)\rcc;" /> + <RemoveDir Directories="$(GeneratedFilesOutDir)" /> + </Target> + + <PropertyGroup> + <BuildDependsOn> + moccode; + mocheader; + forms; + translation; + resource; + $(BuildDependsOn); + </BuildDependsOn> + </PropertyGroup> + <PropertyGroup> + <CleanDependsOn> + qtclean; + $(CleanDependsOn); + </CleanDependsOn> + </PropertyGroup> + </Project> diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py index b612467bf3..5ddda3c03e 100644 --- a/build_msvc/msvc-autogen.py +++ b/build_msvc/msvc-autogen.py @@ -17,6 +17,7 @@ libs = [ 'libbitcoin_wallet_tool', 'libbitcoin_wallet', 'libbitcoin_zmq', + 'bench_bitcoin', ] ignore_list = [ diff --git a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj index a5d666c114..8e54bc7653 100644 --- a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj +++ b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj @@ -1,146 +1,123 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Label="configInitTarget" Project="..\common.init.vcxproj" /> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Debug|x64"> - <Configuration>Debug</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|x64"> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - </ItemGroup> + <Import Project="..\common.init.vcxproj" /> + <Import Project="..\common.qt.init.vcxproj" /> <PropertyGroup Label="Globals"> - <VCProjectVersion>15.0</VCProjectVersion> <ProjectGuid>{51201D5E-D939-4854-AE9D-008F03FF518E}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <RootNamespace>test_bitcoinqt</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <CharacterSet>Unicode</CharacterSet> </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <PropertyGroup Label="Configuration"> <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <PlatformToolset>v141</PlatformToolset> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="Shared"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ItemGroup> + <ClCompile Include="..\..\src\test\setup_common.cpp" /> + <ClCompile Include="..\..\src\qt\test\addressbooktests.cpp" /> + <ClCompile Include="..\..\src\qt\test\apptests.cpp" /> + <ClCompile Include="..\..\src\qt\test\compattests.cpp" /> + <ClCompile Include="..\..\src\qt\test\rpcnestedtests.cpp" /> + <ClCompile Include="..\..\src\qt\test\test_main.cpp" /> + <ClCompile Include="..\..\src\qt\test\uritests.cpp" /> + <ClCompile Include="..\..\src\qt\test\util.cpp" /> + <ClCompile Include="..\..\src\qt\test\wallettests.cpp" /> + <ClCompile Include="..\..\src\wallet\test\wallet_test_fixture.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_addressbooktests.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_apptests.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_compattests.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_rpcnestedtests.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_uritests.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_wallettests.cpp" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\libbitcoinconsensus\libbitcoinconsensus.vcxproj"> + <Project>{2b384fa8-9ee1-4544-93cb-0d733c25e8ce}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_cli\libbitcoin_cli.vcxproj"> + <Project>{0667528c-d734-4009-adf9-c0d6c4a5a5a6}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_common\libbitcoin_common.vcxproj"> + <Project>{7c87e378-df58-482e-aa2f-1bc129bc19ce}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_crypto\libbitcoin_crypto.vcxproj"> + <Project>{6190199c-6cf4-4dad-bfbd-93fa72a760c1}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_qt\libbitcoin_qt.vcxproj"> + <Project>{2b4abff8-d1fd-4845-88c9-1f3c0a6512bf}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_server\libbitcoin_server.vcxproj"> + <Project>{460fee33-1fe1-483f-b3bf-931ff8e969a5}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_util\libbitcoin_util.vcxproj"> + <Project>{b53a5535-ee9d-4c6f-9a26-f79ee3bc3754}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_wallet\libbitcoin_wallet.vcxproj"> + <Project>{93b86837-b543-48a5-a89b-7c87abb77df2}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_zmq\libbitcoin_zmq.vcxproj"> + <Project>{792d487f-f14c-49fc-a9de-3fc150f31c3f}</Project> + </ProjectReference> + <ProjectReference Include="..\libleveldb\libleveldb.vcxproj"> + <Project>{18430fef-6b61-4c53-b396-718e02850f1b}</Project> + </ProjectReference> + <ProjectReference Include="..\libsecp256k1\libsecp256k1.vcxproj"> + <Project>{bb493552-3b8c-4a8c-bf69-a6e7a51d2ea6}</Project> + </ProjectReference> + <ProjectReference Include="..\libunivalue\libunivalue.vcxproj"> + <Project>{5724ba7d-a09a-4ba8-800b-c4c1561b3d69}</Project> + </ProjectReference> + </ItemGroup> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> + <AdditionalIncludeDirectories>..\libbitcoin_qt\$(GeneratedFilesOutDir)\..\;$(QtIncludeDir)\QtTest;$(QtIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>$(QtReleaseLibaries);%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ClCompile> - <PrecompiledHeader>NotUsing</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <SDLCheck>true</SDLCheck> + <AdditionalIncludeDirectories>..\libbitcoin_qt\$(GeneratedFilesOutDir)\..\;$(QtIncludeDir)\QtTest;$(QtIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> - <SubSystem>Console</SubSystem> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>$(QtDebugLibraries);%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> - <Import Label="configTarget" Project="..\common.vcxproj" /> -</Project> + + <ItemGroup> + <MocTestFiles Include="..\..\src\qt\test\addressbooktests.h" /> + <MocTestFiles Include="..\..\src\qt\test\apptests.h" /> + <MocTestFiles Include="..\..\src\qt\test\compattests.h" /> + <MocTestFiles Include="..\..\src\qt\test\paymentservertests.h" /> + <MocTestFiles Include="..\..\src\qt\test\rpcnestedtests.h" /> + <MocTestFiles Include="..\..\src\qt\test\uritests.h" /> + <MocTestFiles Include="..\..\src\qt\test\wallettests.h" /> + </ItemGroup> + <Target Name="moccode" Inputs="@(MocTestFiles)" Outputs="@(MocTestFiles->'$(GeneratedFilesOutDir)\moc\moc_%(Filename).cpp')"> + <PropertyGroup> + <ErrorText>There was an error executing the test_bitcoin-qt moc code generation task.</ErrorText> + </PropertyGroup> + <Exec Command="echo Performing test_bitcoin-qt moc generation task, output path $(GeneratedFilesOutDir)\moc." /> + <MakeDir Directories="$(GeneratedFilesOutDir)\moc\" /> + <Exec Command="$(QtToolsDir)\moc.exe $(MOC_DEFINES) "%(MocTestFiles.Identity)" -o $(GeneratedFilesOutDir)\moc\moc_%(Filename).cpp" /> + </Target> + <Target Name="QtTestCleanGeneratedFiles"> + <Exec Command="echo Clean test_bitcoin-qt generated files from $(GeneratedFilesOutDir)." /> + <RemoveDir Directories="$(GeneratedFilesOutDir)\moc\*" /> + <RemoveDir Directories="$(GeneratedFilesOutDir)\moc" /> + </Target> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <PropertyGroup> + <BuildDependsOn> + moccode; + $(BuildDependsOn); + </BuildDependsOn> + </PropertyGroup> + <PropertyGroup> + <CleanDependsOn> + QtTestCleanGeneratedFiles; + $(CleanDependsOn); + </CleanDependsOn> + </PropertyGroup> + </Project> diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh index 20bff368a5..01322f61e6 100755 --- a/ci/lint/04_install.sh +++ b/ci/lint/04_install.sh @@ -7,7 +7,7 @@ export LC_ALL=C travis_retry pip3 install codespell==1.15.0 -travis_retry pip3 install flake8==3.5.0 +travis_retry pip3 install flake8==3.7.8 travis_retry pip3 install vulture==0.29 SHELLCHECK_VERSION=v0.6.0 diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index 09b37f8240..51b5cfdd3f 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -11,7 +11,10 @@ echo "Setting default values in env" BASE_ROOT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../../ >/dev/null 2>&1 && pwd ) export BASE_ROOT_DIR -export MAKEJOBS=${MAKEJOBS:--j3} +# The number of parallel jobs to pass down to make and test_runner.py +export MAKEJOBS=${MAKEJOBS:--j4} +# A folder for the ci system to put temporary files (ccache, datadirs for tests, ...) +export BASE_SCRATCH_DIR=${BASE_SCRATCH_DIR:-$BASE_ROOT_DIR/ci/scratch/} export HOST=${HOST:-x86_64-unknown-linux-gnu} export RUN_UNIT_TESTS=${RUN_UNIT_TESTS:-true} export RUN_FUNCTIONAL_TESTS=${RUN_FUNCTIONAL_TESTS:-true} @@ -21,7 +24,7 @@ export BOOST_TEST_RANDOM=${BOOST_TEST_RANDOM:-1$TRAVIS_BUILD_ID} export CCACHE_SIZE=${CCACHE_SIZE:-100M} export CCACHE_TEMPDIR=${CCACHE_TEMPDIR:-/tmp/.ccache-temp} export CCACHE_COMPRESS=${CCACHE_COMPRESS:-1} -export CCACHE_DIR=${CCACHE_DIR:-$HOME/.ccache} +export CCACHE_DIR=${CCACHE_DIR:-$BASE_SCRATCH_DIR/.ccache} export BASE_BUILD_DIR=${BASE_BUILD_DIR:-${TRAVIS_BUILD_DIR:-$BASE_ROOT_DIR}} export BASE_OUTDIR=${BASE_OUTDIR:-$BASE_BUILD_DIR/out/$HOST} export SDK_URL=${SDK_URL:-https://bitcoincore.org/depends-sources/sdks} diff --git a/ci/test/00_setup_env_amd64_asan.sh b/ci/test/00_setup_env_amd64_asan.sh index 9d20b6a72b..46b870e145 100644 --- a/ci/test/00_setup_env_amd64_asan.sh +++ b/ci/test/00_setup_env_amd64_asan.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export HOST=x86_64-unknown-linux-gnu -export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" +export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libqrencode-dev" export NO_DEPENDS=1 export GOAL="install" export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=address,integer,undefined CC=clang CXX=clang++" diff --git a/ci/test/00_setup_env_amd64_qt5.sh b/ci/test/00_setup_env_amd64_qt5.sh index 77b1531be4..55820ea835 100644 --- a/ci/test/00_setup_env_amd64_qt5.sh +++ b/ci/test/00_setup_env_amd64_qt5.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export HOST=x86_64-unknown-linux-gnu -export PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools protobuf-compiler libdbus-1-dev libharfbuzz-dev libprotobuf-dev" +export PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libdbus-1-dev libharfbuzz-dev" export DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1" export TEST_RUNNER_EXTRA="--coverage --extended --exclude feature_dbcrash" # Run extended tests so that coverage does not fail, but exclude the very slow dbcrash export GOAL="install" diff --git a/ci/test/00_setup_env_amd64_trusty.sh b/ci/test/00_setup_env_amd64_trusty.sh index cc0f1196e7..51e98788c7 100644 --- a/ci/test/00_setup_env_amd64_trusty.sh +++ b/ci/test/00_setup_env_amd64_trusty.sh @@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8 export HOST=x86_64-unknown-linux-gnu export DOCKER_NAME_TAG=ubuntu:14.04 -export PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libicu-dev libpng-dev libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.1++-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" +export PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libicu-dev libpng-dev libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.1++-dev libzmq3-dev libqrencode-dev" export NO_DEPENDS=1 export RUN_FUNCTIONAL_TESTS=false export GOAL="install" diff --git a/ci/test/00_setup_env_amd64_tsan.sh b/ci/test/00_setup_env_amd64_tsan.sh index c127e284bd..82ac988c41 100644 --- a/ci/test/00_setup_env_amd64_tsan.sh +++ b/ci/test/00_setup_env_amd64_tsan.sh @@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8 export HOST=x86_64-unknown-linux-gnu export DOCKER_NAME_TAG=ubuntu:16.04 -export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" +export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libqrencode-dev" export NO_DEPENDS=1 export GOAL="install" export BITCOIN_CONFIG="--enable-zmq --disable-wallet --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=thread --disable-hardening --disable-asm CC=clang CXX=clang++" diff --git a/ci/test/00_setup_env_i686.sh b/ci/test/00_setup_env_i686.sh index 768e2ac558..63068dc95d 100644 --- a/ci/test/00_setup_env_i686.sh +++ b/ci/test/00_setup_env_i686.sh @@ -7,7 +7,8 @@ export LC_ALL=C.UTF-8 export HOST=i686-pc-linux-gnu +export DEP_OPTS="PROTOBUF=1" export PACKAGES="g++-multilib python3-zmq" export GOAL="install" -export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --disable-bip70 --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" +export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-bip70 --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" export CONFIG_SHELL="/bin/dash" diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index 3535746e83..54d7a9b814 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -6,6 +6,7 @@ export LC_ALL=C.UTF-8 +mkdir -p "${BASE_SCRATCH_DIR}" ccache echo "Creating ccache dir if it didn't already exist" if [ ! -d ${DIR_QA_ASSETS} ]; then diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh index d179ce81c0..ea7beae85f 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -16,7 +16,7 @@ fi if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then BEGIN_FOLD functional-tests - DOCKER_EXEC test/functional/test_runner.py --ci --ansi --combinedlogslen=4000 ${TEST_RUNNER_EXTRA} --quiet --failfast + DOCKER_EXEC test/functional/test_runner.py --ci $MAKEJOBS --tmpdirprefix "${BASE_SCRATCH_DIR}/test_runner/" --ansi --combinedlogslen=4000 ${TEST_RUNNER_EXTRA} --quiet --failfast END_FOLD fi diff --git a/configure.ac b/configure.ac index 35bb0c0231..e1b7281c30 100644 --- a/configure.ac +++ b/configure.ac @@ -223,10 +223,10 @@ AC_ARG_ENABLE([zmq], [use_zmq=$enableval], [use_zmq=yes]) AC_ARG_ENABLE([bip70], - [AS_HELP_STRING([--disable-bip70], - [disable BIP70 (payment protocol) support in GUI (enabled by default)])], + [AS_HELP_STRING([--enable-bip70], + [enable BIP70 (payment protocol) support in the GUI (default is to disable)])], [enable_bip70=$enableval], - [enable_bip70=auto]) + [enable_bip70=no]) AC_ARG_WITH([protoc-bindir],[AS_HELP_STRING([--with-protoc-bindir=BIN_DIR],[specify protoc bin path])], [protoc_bin_path=$withval], []) @@ -585,7 +585,7 @@ case $host in fi AX_CHECK_LINK_FLAG([[-Wl,-headerpad_max_install_names]], [LDFLAGS="$LDFLAGS -Wl,-headerpad_max_install_names"]) - CPPFLAGS="$CPPFLAGS -DMAC_OSX" + CPPFLAGS="$CPPFLAGS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0" OBJCXXFLAGS="$CXXFLAGS" ;; *android*) diff --git a/contrib/bitcoin-qt.pro b/contrib/bitcoin-qt.pro index b8133bf789..0e4eeee0a7 100644 --- a/contrib/bitcoin-qt.pro +++ b/contrib/bitcoin-qt.pro @@ -16,6 +16,7 @@ FORMS += \ ../src/qt/forms/sendcoinsentry.ui \ ../src/qt/forms/signverifymessagedialog.ui \ ../src/qt/forms/transactiondescdialog.ui \ + ../src/qt/forms/createwalletdialog.ui RESOURCES += \ ../src/qt/bitcoin.qrc diff --git a/contrib/devtools/clang-format-diff.py b/contrib/devtools/clang-format-diff.py index f322b3a880..98eee67f43 100755 --- a/contrib/devtools/clang-format-diff.py +++ b/contrib/devtools/clang-format-diff.py @@ -106,7 +106,7 @@ def main(): filename = None lines_by_file = {} for line in sys.stdin: - match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) + match = re.search(r'^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) if match: filename = match.group(2) if filename is None: @@ -119,7 +119,7 @@ def main(): if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): continue - match = re.search('^@@.*\+(\d+)(,(\d+))?', line) + match = re.search(r'^@@.*\+(\d+)(,(\d+))?', line) if match: start_line = int(match.group(1)) line_count = 1 diff --git a/contrib/devtools/copyright_header.py b/contrib/devtools/copyright_header.py index fc01e570aa..67e77bc63d 100755 --- a/contrib/devtools/copyright_header.py +++ b/contrib/devtools/copyright_header.py @@ -71,7 +71,7 @@ def get_filenames_to_examine(base_directory): ################################################################################ -COPYRIGHT_WITH_C = 'Copyright \(c\)' +COPYRIGHT_WITH_C = r'Copyright \(c\)' COPYRIGHT_WITHOUT_C = 'Copyright' ANY_COPYRIGHT_STYLE = '(%s|%s)' % (COPYRIGHT_WITH_C, COPYRIGHT_WITHOUT_C) @@ -85,21 +85,21 @@ ANY_COPYRIGHT_STYLE_OR_YEAR_STYLE = ("%s %s" % (ANY_COPYRIGHT_STYLE, ANY_COPYRIGHT_COMPILED = re.compile(ANY_COPYRIGHT_STYLE_OR_YEAR_STYLE) def compile_copyright_regex(copyright_style, year_style, name): - return re.compile('%s %s,? %s' % (copyright_style, year_style, name)) + return re.compile(r'%s %s,? %s( +\*)?\n' % (copyright_style, year_style, name)) EXPECTED_HOLDER_NAMES = [ - "Satoshi Nakamoto\n", - "The Bitcoin Core developers\n", - "BitPay Inc\.\n", - "University of Illinois at Urbana-Champaign\.\n", - "Pieter Wuille\n", - "Wladimir J. van der Laan\n", - "Jeff Garzik\n", - "Jan-Klaas Kollhof\n", - "ArtForz -- public domain half-a-node\n", - "Intel Corporation", - "The Zcash developers", - "Jeremy Rubin", + r"Satoshi Nakamoto", + r"The Bitcoin Core developers", + r"BitPay Inc\.", + r"University of Illinois at Urbana-Champaign\.", + r"Pieter Wuille", + r"Wladimir J\. van der Laan", + r"Jeff Garzik", + r"Jan-Klaas Kollhof", + r"ArtForz -- public domain half-a-node", + r"Intel Corporation ?", + r"The Zcash developers", + r"Jeremy Rubin", ] DOMINANT_STYLE_COMPILED = {} @@ -329,7 +329,7 @@ def write_file_lines(filename, file_lines): # update header years execution ################################################################################ -COPYRIGHT = 'Copyright \(c\)' +COPYRIGHT = r'Copyright \(c\)' YEAR = "20[0-9][0-9]" YEAR_RANGE = '(%s)(-%s)?' % (YEAR, YEAR) HOLDER = 'The Bitcoin Core developers' diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index dd35d862c9..d8b684026c 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -141,7 +141,7 @@ def read_libraries(filename): for line in stdout.splitlines(): tokens = line.split() if len(tokens)>2 and tokens[1] == '(NEEDED)': - match = re.match('^Shared library: \[(.*)\]$', ' '.join(tokens[2:])) + match = re.match(r'^Shared library: \[(.*)\]$', ' '.join(tokens[2:])) if match: libraries.append(match.group(1)) else: @@ -171,5 +171,3 @@ if __name__ == '__main__': retval = 1 sys.exit(retval) - - diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index 56b972a5cb..ee207a957c 100644 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -30,23 +30,38 @@ fi # Given a package name and an output name, return the path of that output in our # current guix environment store_path() { - grep --extended-regexp "/[^-]{32}-${1}-cross-${HOST}-[^-]+${2:+-${2}}" "${GUIX_ENVIRONMENT}/manifest" \ + grep --extended-regexp "/[^-]{32}-${1}-[^-]+${2:+-${2}}" "${GUIX_ENVIRONMENT}/manifest" \ | head --lines=1 \ | sed --expression='s|^[[:space:]]*"||' \ --expression='s|"[[:space:]]*$||' } # Determine output paths to use in CROSS_* environment variables -CROSS_GLIBC="$(store_path glibc)" -CROSS_GLIBC_STATIC="$(store_path glibc static)" -CROSS_KERNEL="$(store_path linux-libre-headers)" -CROSS_GCC="$(store_path gcc)" +CROSS_GLIBC="$(store_path glibc-cross-${HOST})" +CROSS_GLIBC_STATIC="$(store_path glibc-cross-${HOST} static)" +CROSS_KERNEL="$(store_path linux-libre-headers-cross-${HOST})" +CROSS_GCC="$(store_path gcc-cross-${HOST})" +CROSS_GCC_LIBS=( "${CROSS_GCC}/lib/gcc/${HOST}"/* ) # This expands to an array of directories... +CROSS_GCC_LIB="${CROSS_GCC_LIBS[0]}" # ...we just want the first one (there should only be one) # Set environment variables to point Guix's cross-toolchain to the right # includes/libs for $HOST -export CROSS_C_INCLUDE_PATH="${CROSS_GCC}/include:${CROSS_GLIBC}/include:${CROSS_KERNEL}/include" -export CROSS_CPLUS_INCLUDE_PATH="${CROSS_GCC}/include/c++:${CROSS_GLIBC}/include:${CROSS_KERNEL}/include" -export CROSS_LIBRARY_PATH="${CROSS_GLIBC}/lib:${CROSS_GLIBC_STATIC}/lib:${CROSS_GCC}/lib:${CROSS_GCC}/${HOST}/lib:${CROSS_KERNEL}/lib" +# +# NOTE: CROSS_C_INCLUDE_PATH is missing ${CROSS_GCC_LIB}/include-fixed, because +# the limits.h in it is missing a '#include_next <limits.h>' +# +export CROSS_C_INCLUDE_PATH="${CROSS_GCC_LIB}/include:${CROSS_GLIBC}/include:${CROSS_KERNEL}/include" +export CROSS_CPLUS_INCLUDE_PATH="${CROSS_GCC}/include/c++:${CROSS_GCC}/include/c++/${HOST}:${CROSS_GCC}/include/c++/backward:${CROSS_C_INCLUDE_PATH}" +export CROSS_LIBRARY_PATH="${CROSS_GCC}/lib:${CROSS_GCC}/${HOST}/lib:${CROSS_GCC_LIB}:${CROSS_GLIBC}/lib:${CROSS_GLIBC_STATIC}/lib" + +# Sanity check CROSS_*_PATH directories +IFS=':' read -ra PATHS <<< "${CROSS_C_INCLUDE_PATH}:${CROSS_CPLUS_INCLUDE_PATH}:${CROSS_LIBRARY_PATH}" +for p in "${PATHS[@]}"; do + if [ ! -d "$p" ]; then + echo "'$p' doesn't exist or isn't a directory... Aborting..." + exit 1 + fi +done # Disable Guix ld auto-rpath behavior export GUIX_LD_WRAPPER_DISABLE_RPATH=yes @@ -121,17 +136,10 @@ DISTNAME="$(basename "$SOURCEDIST" '.tar.gz')" # Binary Tarball Building # ########################### -# Create a spec file to normalize ssp linking behaviour -spec_file="$(mktemp)" -cat << EOF > "$spec_file" -*link_ssp: -%{fstack-protector|fstack-protector-all|fstack-protector-strong|fstack-protector-explicit:} -EOF - # Similar flags to Gitian CONFIGFLAGS="--enable-glibc-back-compat --enable-reduce-exports --disable-bench --disable-gui-tests" -HOST_CFLAGS="-O2 -g -specs=${spec_file} -ffile-prefix-map=${PWD}=." -HOST_CXXFLAGS="-O2 -g -specs=${spec_file} -ffile-prefix-map=${PWD}=." +HOST_CFLAGS="-O2 -g -ffile-prefix-map=${PWD}=." +HOST_CXXFLAGS="-O2 -g -ffile-prefix-map=${PWD}=." HOST_LDFLAGS="-Wl,--as-needed -Wl,--dynamic-linker=$glibc_dynamic_linker -static-libstdc++" # Make $HOST-specific native binaries from depends available in $PATH diff --git a/contrib/init/bitcoind.service b/contrib/init/bitcoind.service index cfc5f77580..34c3e7b3ab 100644 --- a/contrib/init/bitcoind.service +++ b/contrib/init/bitcoind.service @@ -5,8 +5,9 @@ # See "man systemd.service" for details. # Note that almost all daemon options could be specified in -# /etc/bitcoin/bitcoin.conf, except for those explicitly specified as arguments -# in ExecStart= +# /etc/bitcoin/bitcoin.conf, but keep in mind those explicitly +# specified as arguments in ExecStart= will override those in the +# config file. [Unit] Description=Bitcoin daemon @@ -18,6 +19,10 @@ ExecStart=/usr/bin/bitcoind -daemon \ -conf=/etc/bitcoin/bitcoin.conf \ -datadir=/var/lib/bitcoind +# Make sure the config directory is readable by the service user +PermissionsStartOnly=true +ExecStartPre=/bin/chgrp bitcoin /etc/bitcoin + # Process management #################### @@ -53,6 +58,9 @@ PrivateTmp=true # Mount /usr, /boot/ and /etc read-only for the process. ProtectSystem=full +# Deny access to /home, /root and /run/user +ProtectHome=true + # Disallow the process and all of its children to gain # new privileges through execve(). NoNewPrivileges=true diff --git a/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py index 468aec04b5..95754ab937 100755 --- a/contrib/linearize/linearize-data.py +++ b/contrib/linearize/linearize-data.py @@ -263,12 +263,12 @@ if __name__ == '__main__': f = open(sys.argv[1], encoding="utf8") for line in f: # skip comment lines - m = re.search('^\s*#', line) + m = re.search(r'^\s*#', line) if m: continue # parse key=value lines - m = re.search('^(\w+)\s*=\s*(\S.*)$', line) + m = re.search(r'^(\w+)\s*=\s*(\S.*)$', line) if m is None: continue settings[m.group(1)] = m.group(2) diff --git a/contrib/linearize/linearize-hashes.py b/contrib/linearize/linearize-hashes.py index 8529470e09..02c96d2a75 100755 --- a/contrib/linearize/linearize-hashes.py +++ b/contrib/linearize/linearize-hashes.py @@ -106,12 +106,12 @@ if __name__ == '__main__': f = open(sys.argv[1], encoding="utf8") for line in f: # skip comment lines - m = re.search('^\s*#', line) + m = re.search(r'^\s*#', line) if m: continue # parse key=value lines - m = re.search('^(\w+)\s*=\s*(\S.*)$', line) + m = re.search(r'^(\w+)\s*=\s*(\S.*)$', line) if m is None: continue settings[m.group(1)] = m.group(2) diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index 9da03e5b02..d8088aa123 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -19,6 +19,7 @@ import subprocess, sys, re, os, shutil, stat, os.path, time from string import Template from argparse import ArgumentParser +from typing import List, Optional # This is ported from the original macdeployqt with modifications @@ -48,18 +49,18 @@ class FrameworkInfo(object): return False def __str__(self): - return """ Framework name: %s - Framework directory: %s - Framework path: %s - Binary name: %s - Binary directory: %s - Binary path: %s - Version: %s - Install name: %s - Deployed install name: %s - Source file Path: %s - Deployed Directory (relative to bundle): %s -""" % (self.frameworkName, + return """ Framework name: {} + Framework directory: {} + Framework path: {} + Binary name: {} + Binary directory: {} + Binary path: {} + Version: {} + Install name: {} + Deployed install name: {} + Source file Path: {} + Deployed Directory (relative to bundle): {} +""".format(self.frameworkName, self.frameworkDirectory, self.frameworkPath, self.binaryName, @@ -85,7 +86,7 @@ class FrameworkInfo(object): bundleBinaryDirectory = "Contents/MacOS" @classmethod - def fromOtoolLibraryLine(cls, line): + def fromOtoolLibraryLine(cls, line: str) -> Optional['FrameworkInfo']: # Note: line must be trimmed if line == "": return None @@ -146,13 +147,12 @@ class FrameworkInfo(object): info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents") info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents") info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources") - info.destinationContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Contents") info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents") return info class ApplicationBundleInfo(object): - def __init__(self, path): + def __init__(self, path: str): self.path = path appName = "Bitcoin-Qt" self.binaryPath = os.path.join(path, "Contents", "MacOS", appName) @@ -167,7 +167,7 @@ class DeploymentInfo(object): self.pluginPath = None self.deployedFrameworks = [] - def detectQtPath(self, frameworkDirectory): + def detectQtPath(self, frameworkDirectory: str): parentDir = os.path.dirname(frameworkDirectory) if os.path.exists(os.path.join(parentDir, "translations")): # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x" @@ -180,9 +180,9 @@ class DeploymentInfo(object): if os.path.exists(pluginPath): self.pluginPath = pluginPath - def usesFramework(self, name): - nameDot = "%s." % name - libNameDot = "lib%s." % name + def usesFramework(self, name: str) -> bool: + nameDot = "{}.".format(name) + libNameDot = "lib{}.".format(name) for framework in self.deployedFrameworks: if framework.endswith(".framework"): if framework.startswith(nameDot): @@ -192,7 +192,7 @@ class DeploymentInfo(object): return True return False -def getFrameworks(binaryPath, verbose): +def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]: if verbose >= 3: print("Inspecting with otool: " + binaryPath) otoolbin=os.getenv("OTOOL", "otool") @@ -202,7 +202,7 @@ def getFrameworks(binaryPath, verbose): if verbose >= 1: sys.stderr.write(o_stderr) sys.stderr.flush() - raise RuntimeError("otool failed with return code %d" % otool.returncode) + raise RuntimeError("otool failed with return code {}".format(otool.returncode)) otoolLines = o_stdout.split("\n") otoolLines.pop(0) # First line is the inspected binary @@ -221,11 +221,11 @@ def getFrameworks(binaryPath, verbose): return libraries -def runInstallNameTool(action, *args): +def runInstallNameTool(action: str, *args): installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool") subprocess.check_call([installnametoolbin, "-"+action] + list(args)) -def changeInstallName(oldName, newName, binaryPath, verbose): +def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int): if verbose >= 3: print("Using install_name_tool:") print(" in", binaryPath) @@ -233,21 +233,21 @@ def changeInstallName(oldName, newName, binaryPath, verbose): print(" to", newName) runInstallNameTool("change", oldName, newName, binaryPath) -def changeIdentification(id, binaryPath, verbose): +def changeIdentification(id: str, binaryPath: str, verbose: int): if verbose >= 3: print("Using install_name_tool:") print(" change identification in", binaryPath) print(" to", id) runInstallNameTool("id", id, binaryPath) -def runStrip(binaryPath, verbose): +def runStrip(binaryPath: str, verbose: int): stripbin=os.getenv("STRIP", "strip") if verbose >= 3: print("Using strip:") print(" stripped", binaryPath) subprocess.check_call([stripbin, "-x", binaryPath]) -def copyFramework(framework, path, verbose): +def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional[str]: if framework.sourceFilePath.startswith("Qt"): #standard place for Nokia Qt installer's frameworks fromPath = "/Library/Frameworks/" + framework.sourceFilePath @@ -309,7 +309,7 @@ def copyFramework(framework, path, verbose): return toPath -def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploymentInfo=None): +def deployFrameworks(frameworks: List[FrameworkInfo], bundlePath: str, binaryPath: str, strip: bool, verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo: if deploymentInfo is None: deploymentInfo = DeploymentInfo() @@ -355,15 +355,15 @@ def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploym return deploymentInfo -def deployFrameworksForAppBundle(applicationBundle, strip, verbose): +def deployFrameworksForAppBundle(applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo: frameworks = getFrameworks(applicationBundle.binaryPath, verbose) if len(frameworks) == 0 and verbose >= 1: - print("Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle.path)) + print("Warning: Could not find any external frameworks to deploy in {}.".format(applicationBundle.path)) return DeploymentInfo() else: return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose) -def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose): +def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: DeploymentInfo, strip: bool, verbose: int): # Lookup available plugins, exclude unneeded plugins = [] if deploymentInfo.pluginPath is None: @@ -373,10 +373,12 @@ def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose): if pluginDirectory == "designer": # Skip designer plugins continue - elif pluginDirectory == "phonon" or pluginDirectory == "phonon_backend": - # Deploy the phonon plugins only if phonon is in use - if not deploymentInfo.usesFramework("phonon"): - continue + elif pluginDirectory == "printsupport": + # Skip printsupport plugins + continue + elif pluginDirectory == "imageformats": + # Skip imageformats plugins + continue elif pluginDirectory == "sqldrivers": # Deploy the sql plugins only if QtSql is in use if not deploymentInfo.usesFramework("QtSql"): @@ -409,6 +411,42 @@ def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose): # Deploy the mediaservice plugins only if QtMultimediaWidgets is in use if not deploymentInfo.usesFramework("QtMultimediaWidgets"): continue + elif pluginDirectory == "canbus": + # Deploy the canbus plugins only if QtSerialBus is in use + if not deploymentInfo.usesFramework("QtSerialBus"): + continue + elif pluginDirectory == "webview": + # Deploy the webview plugins only if QtWebView is in use + if not deploymentInfo.usesFramework("QtWebView"): + continue + elif pluginDirectory == "gamepads": + # Deploy the webview plugins only if QtGamepad is in use + if not deploymentInfo.usesFramework("QtGamepad"): + continue + elif pluginDirectory == "geoservices": + # Deploy the webview plugins only if QtLocation is in use + if not deploymentInfo.usesFramework("QtLocation"): + continue + elif pluginDirectory == "texttospeech": + # Deploy the texttospeech plugins only if QtTextToSpeech is in use + if not deploymentInfo.usesFramework("QtTextToSpeech"): + continue + elif pluginDirectory == "virtualkeyboard": + # Deploy the virtualkeyboard plugins only if QtVirtualKeyboard is in use + if not deploymentInfo.usesFramework("QtVirtualKeyboard"): + continue + elif pluginDirectory == "sceneparsers": + # Deploy the virtualkeyboard plugins only if Qt3DCore is in use + if not deploymentInfo.usesFramework("Qt3DCore"): + continue + elif pluginDirectory == "renderplugins": + # Deploy the renderplugins plugins only if Qt3DCore is in use + if not deploymentInfo.usesFramework("Qt3DCore"): + continue + elif pluginDirectory == "geometryloaders": + # Deploy the geometryloaders plugins only if Qt3DCore is in use + if not deploymentInfo.usesFramework("Qt3DCore"): + continue for pluginName in filenames: pluginPath = os.path.join(pluginDirectory, pluginName) @@ -431,6 +469,10 @@ def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose): # Deploy the accessible qtquick plugin only if QtQuick is in use if not deploymentInfo.usesFramework("QtQuick"): continue + elif pluginPath == "platforminputcontexts/libqtvirtualkeyboardplugin.dylib": + # Deploy the virtualkeyboardplugin plugin only if QtVirtualKeyboard is in use + if not deploymentInfo.usesFramework("QtVirtualKeyboard"): + continue plugins.append((pluginDirectory, pluginName)) @@ -499,7 +541,7 @@ app_bundle = config.app_bundle[0] if not os.path.exists(app_bundle): if verbose >= 1: - sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle)) + sys.stderr.write("Error: Could not find app bundle \"{}\"\n".format(app_bundle)) sys.exit(1) app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0] @@ -511,7 +553,7 @@ if config.translations_dir and config.translations_dir[0]: translations_dir = config.translations_dir[0] else: if verbose >= 1: - sys.stderr.write("Error: Could not find translation dir \"%s\"\n" % (translations_dir)) + sys.stderr.write("Error: Could not find translation dir \"{}\"\n".format(translations_dir)) sys.exit(1) # ------------------------------------------------ @@ -520,7 +562,7 @@ for p in config.add_resources: print("Checking for \"%s\"..." % p) if not os.path.exists(p): if verbose >= 1: - sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p)) + sys.stderr.write("Error: Could not find additional resource file \"{}\"\n".format(p)) sys.exit(1) # ------------------------------------------------ @@ -537,17 +579,17 @@ if len(config.fancy) == 1: p = config.fancy[0] if verbose >= 3: - print("Fancy: Loading \"%s\"..." % p) + print("Fancy: Loading \"{}\"...".format(p)) if not os.path.exists(p): if verbose >= 1: - sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p)) + sys.stderr.write("Error: Could not find fancy disk image plist at \"{}\"\n".format(p)) sys.exit(1) try: fancy = plistlib.readPlist(p) except: if verbose >= 1: - sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p)) + sys.stderr.write("Error: Could not parse fancy disk image plist at \"{}\"\n".format(p)) sys.exit(1) try: @@ -561,18 +603,18 @@ if len(config.fancy) == 1: assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int) except: if verbose >= 1: - sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p)) + sys.stderr.write("Error: Bad format of fancy disk image plist at \"{}\"\n".format(p)) sys.exit(1) if "background_picture" in fancy: bp = fancy["background_picture"] if verbose >= 3: - print("Fancy: Resolving background picture \"%s\"..." % bp) + print("Fancy: Resolving background picture \"{}\"...".format(bp)) if not os.path.exists(bp): bp = os.path.join(os.path.dirname(p), bp) if not os.path.exists(bp): if verbose >= 1: - sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp)) + sys.stderr.write("Error: Could not find background picture at \"{}\" or \"{}\"\n".format(fancy["background_picture"], bp)) sys.exit(1) else: fancy["background_picture"] = bp @@ -623,7 +665,7 @@ try: config.plugins = False except RuntimeError as e: if verbose >= 1: - sys.stderr.write("Error: %s\n" % str(e)) + sys.stderr.write("Error: {}\n".format(str(e))) sys.exit(1) # ------------------------------------------------ @@ -636,7 +678,7 @@ if config.plugins: deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose) except RuntimeError as e: if verbose >= 1: - sys.stderr.write("Error: %s\n" % str(e)) + sys.stderr.write("Error: {}\n".format(str(e))) sys.exit(1) # ------------------------------------------------ @@ -652,14 +694,14 @@ else: else: sys.stderr.write("Error: Could not find Qt translation path\n") sys.exit(1) - add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")] + add_qt_tr = ["qt_{}.qm".format(lng) for lng in config.add_qt_tr[0].split(",")] for lng_file in add_qt_tr: p = os.path.join(qt_tr_dir, lng_file) if verbose >= 3: - print("Checking for \"%s\"..." % p) + print("Checking for \"{}\"...".format(p)) if not os.path.exists(p): if verbose >= 1: - sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file)) + sys.stderr.write("Error: Could not find Qt translation file \"{}\"\n".format(lng_file)) sys.exit(1) # ------------------------------------------------ @@ -700,14 +742,14 @@ if config.sign and 'CODESIGNARGS' not in os.environ: print("You must set the CODESIGNARGS environment variable. Skipping signing.") elif config.sign: if verbose >= 1: - print("Code-signing app bundle %s"%(target,)) - subprocess.check_call("codesign --force %s %s"%(os.environ['CODESIGNARGS'], target), shell=True) + print("Code-signing app bundle {}".format(target)) + subprocess.check_call("codesign --force {} {}".format(os.environ['CODESIGNARGS'], target), shell=True) # ------------------------------------------------ if config.dmg is not None: - def runHDIUtil(verb, image_basename, **kwargs): + def runHDIUtil(verb: str, image_basename: str, **kwargs) -> int: hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"] if "capture_stdout" in kwargs: del kwargs["capture_stdout"] @@ -721,7 +763,7 @@ if config.dmg is not None: for key, value in kwargs.items(): hdiutil_args.append("-" + key) - if not value is True: + if value is not True: hdiutil_args.append(str(value)) return run(hdiutil_args, universal_newlines=True) @@ -765,8 +807,8 @@ if config.dmg is not None: output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True) except subprocess.CalledProcessError as e: sys.exit(e.returncode) - - m = re.search("/Volumes/(.+$)", output) + + m = re.search(r"/Volumes/(.+$)", output) disk_root = m.group(0) disk_name = m.group(1) diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index fe7cd1d597..7630a7a4fa 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -74,7 +74,7 @@ def name_to_ipv6(addr): raise ValueError('Could not parse address %s' % addr) def parse_spec(s, defaultport): - match = re.match('\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s) + match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s) if match: # ipv6 host = match.group(1) port = match.group(2) @@ -136,4 +136,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/depends/Makefile b/depends/Makefile index 70af875189..b7e9a9213e 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -5,6 +5,7 @@ WORK_PATH = $(BASEDIR)/work BASE_CACHE ?= $(BASEDIR)/built SDK_PATH ?= $(BASEDIR)/SDKs NO_QT ?= +PROTOBUF ?= RAPIDCHECK ?= NO_WALLET ?= NO_ZMQ ?= @@ -96,13 +97,15 @@ wallet_packages_$(NO_WALLET) = $(wallet_packages) upnp_packages_$(NO_UPNP) = $(upnp_packages) zmq_packages_$(NO_ZMQ) = $(zmq_packages) +protobuf_packages_$(PROTOBUF) = $(protobuf_packages) rapidcheck_packages_$(RAPIDCHECK) = $(rapidcheck_packages) packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(qt_packages_) $(wallet_packages_) $(upnp_packages_) native_packages += $($(host_arch)_$(host_os)_native_packages) $($(host_os)_native_packages) -ifneq ($(qt_packages_),) -native_packages += $(qt_native_packages) +ifeq ($(protobuf_packages_),) +native_packages += $(protobuf_native_packages) +packages += $(protobuf_packages) endif ifneq ($(zmq_packages_),) @@ -150,6 +153,7 @@ $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_ -e 's|@allow_host_packages@|$(ALLOW_HOST_PACKAGES)|' \ -e 's|@no_qt@|$(NO_QT)|' \ -e 's|@no_zmq@|$(NO_ZMQ)|' \ + -e 's|@enable_bip70@|$(PROTOBUF)|' \ -e 's|@no_wallet@|$(NO_WALLET)|' \ -e 's|@no_upnp@|$(NO_UPNP)|' \ -e 's|@debug@|$(DEBUG)|' \ diff --git a/depends/README.md b/depends/README.md index ca542be13f..cfb9bbfeb0 100644 --- a/depends/README.md +++ b/depends/README.md @@ -77,6 +77,7 @@ The following can be set when running make: make FOO=bar NO_UPNP: Don't download/build/cache packages needed for enabling upnp DEBUG: disable some optimizations and enable more runtime checking RAPIDCHECK: build rapidcheck (experimental, requires cmake) + PROTOBUF: build protobuf (used for deprecated BIP70 support) HOST_ID_SALT: Optional salt to use when generating host package ids BUILD_ID_SALT: Optional salt to use when generating build package ids diff --git a/depends/config.site.in b/depends/config.site.in index e633752066..d0d36641c4 100644 --- a/depends/config.site.in +++ b/depends/config.site.in @@ -37,6 +37,10 @@ if test -z $enable_zmq && test -n "@no_zmq@"; then enable_zmq=no fi +if test -n $enable_bip70 && test -n "@enable_bip70@"; then + enable_bip70=yes +fi + if test x@host_os@ = xdarwin; then BREW=no PORT=no diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 9edcd1eb38..667fde5271 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,7 +1,9 @@ packages:=boost openssl libevent -qt_native_packages = native_protobuf -qt_packages = qrencode protobuf zlib +protobuf_native_packages = native_protobuf +protobuf_packages = protobuf + +qt_packages = qrencode zlib qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 2610c1e748..57208a678a 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,9 +1,9 @@ PACKAGE=qt -$(package)_version=5.9.7 +$(package)_version=5.9.8 $(package)_download_path=https://download.qt.io/official_releases/qt/5.9/$($(package)_version)/submodules $(package)_suffix=opensource-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=36dd9574f006eaa1e5af780e4b33d11fe39d09fd7c12f3b9d83294174bd28f00 +$(package)_sha256_hash=9b9dec1f67df1f94bce2955c5604de992d529dde72050239154c56352da0907d $(package)_dependencies=openssl zlib $(package)_linux_dependencies=freetype fontconfig libxcb $(package)_build_subdir=qtbase @@ -11,10 +11,10 @@ $(package)_qt_libs=corelib network widgets gui plugins testlib $(package)_patches=fix_qt_pkgconfig.patch mac-qmake.conf fix_configure_mac.patch fix_no_printer.patch fix_rcc_determinism.patch fix_riscv64_arch.patch xkb-default.patch no-xlib.patch $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) -$(package)_qttranslations_sha256_hash=b36da7d93c3ab6fca56b32053bb73bc619c8b192bb89b74e3bcde2705f1c2a14 +$(package)_qttranslations_sha256_hash=fb5a47799754af73d3bf501fe513342cfe2fc37f64e80df5533f6110e804220c $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=d62e0f70d99645d6704dbb8976fb2222443061743689943d40970c52c49367a1 +$(package)_qttools_sha256_hash=a97556eb7b2f30252cdd8a598c396cfce2b2f79d2bae883af6d3b26a2cdcc63c $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md index f8146b5d75..f4a8edec75 100644 --- a/doc/bitcoin-conf.md +++ b/doc/bitcoin-conf.md @@ -50,3 +50,13 @@ rpcport=4000 The configuration file is not automatically created; you can create it using your favorite text editor. By default, the configuration file name is `bitcoin.conf` and it is located in the Bitcoin data directory, but both the Bitcoin data directory and the configuration file path may be changed using the `-datadir` and `-conf` command-line options. The `includeconf=<file>` option in the `bitcoin.conf` file can be used to include additional configuration files. + +### Default configuration file locations + +Operating System | Data Directory | Example Path +-- | -- | -- +Windows | `%APPDATA%\Bitcoin\` | `C:\Users\username\AppData\Roaming\Bitcoin\bitcoin.conf` +Linux | `$HOME/.bitcoin/` | `/home/username/.bitcoin/bitcoin.conf` +macOS | `$HOME/Library/Application Support/Bitcoin/` | `/Users/username/Library/Application Support/Bitcoin/bitcoin.conf` + +You can find an example bitcoin.conf file in [share/examples/bitcoin.conf](../share/examples/bitcoin.conf). diff --git a/doc/build-osx.md b/doc/build-osx.md index 65cfce6b15..9942449bf6 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -19,7 +19,7 @@ Then install [Homebrew](https://brew.sh). ## Dependencies ```shell -brew install automake berkeley-db4 libtool boost miniupnpc openssl pkg-config protobuf python qt libevent qrencode +brew install automake berkeley-db4 libtool boost miniupnpc openssl pkg-config python qt libevent qrencode ``` See [dependencies.md](dependencies.md) for a complete overview. diff --git a/doc/build-unix.md b/doc/build-unix.md index eb88aca050..069c983e6e 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -44,7 +44,7 @@ Optional dependencies: miniupnpc | UPnP Support | Firewall-jumping support libdb4.8 | Berkeley DB | Wallet storage (only needed when wallet enabled) qt | GUI | GUI toolkit (only needed when GUI enabled) - protobuf | Payments in GUI | Data interchange format used for payment protocol (only needed when GUI enabled) + protobuf | Payments in GUI | Data interchange format used for payment protocol (only needed when BIP70 enabled) libqrencode | QR codes in GUI | Optional for generating QR codes (only needed when GUI enabled) univalue | Utility | JSON parsing and encoding (bundled version will be used unless --with-system-univalue passed to configure) libzmq3 | ZMQ notification | Optional, allows generating ZMQ notifications (requires ZMQ version >= 4.0.0) @@ -112,12 +112,16 @@ To build without GUI pass `--without-gui`. To build with Qt 5 you need the following: - sudo apt-get install libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libprotobuf-dev protobuf-compiler + sudo apt-get install libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libqrencode (optional) can be installed with: sudo apt-get install libqrencode-dev +protobuf (optional) can be installed with: + + sudo apt-get install libprotobuf-dev protobuf-compiler + Once these are installed, they will be found by configure and a bitcoin-qt executable will be built by default. @@ -140,12 +144,16 @@ ZMQ dependencies (provides ZMQ API): To build with Qt 5 you need the following: - sudo dnf install qt5-qttools-devel qt5-qtbase-devel protobuf-devel + sudo dnf install qt5-qttools-devel qt5-qtbase-devel libqrencode (optional) can be installed with: sudo dnf install qrencode-devel +protobuf (optional) can be installed with: + + sudo dnf install protobuf-devel + Notes ----- The release is built with GCC and then "strip bitcoind" to strip the debug diff --git a/doc/dependencies.md b/doc/dependencies.md index 0b23ca0a2d..e5b4084d99 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -35,7 +35,7 @@ Some dependencies are not needed in all configurations. The following are some f #### Options passed to `./configure` * MiniUPnPc is not needed with `--with-miniupnpc=no`. * Berkeley DB is not needed with `--disable-wallet`. -* protobuf is not needed with `--disable-bip70`. +* protobuf is only needed with `--enable-bip70`. * Qt is not needed with `--without-gui`. * If the qrencode dependency is absent, QR support won't be added. To force an error when that happens, pass `--with-qrencode`. * ZeroMQ is needed only with the `--with-zmq` option. diff --git a/doc/developer-notes.md b/doc/developer-notes.md index d1114b0c73..561f623cd5 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -27,7 +27,7 @@ Developer Notes - [General C++](#general-c) - [C++ data structures](#c-data-structures) - [Strings and formatting](#strings-and-formatting) - - [Variable names](#variable-names) + - [Shadowing](#shadowing) - [Threads and synchronization](#threads-and-synchronization) - [Scripts](#scripts) - [Shebang](#shebang) @@ -613,27 +613,13 @@ Strings and formatting - *Rationale*: Bitcoin Core uses tinyformat, which is type safe. Leave them out to avoid confusion. -Variable names +Shadowing -------------- Although the shadowing warning (`-Wshadow`) is not enabled by default (it prevents issues arising from using a different variable with the same name), please name variables so that their names do not shadow variables defined in the source code. -E.g. in member initializers, prepend `_` to the argument name shadowing the -member name: - -```c++ -class AddressBookPage -{ - Mode m_mode; -} - -AddressBookPage::AddressBookPage(Mode _mode) - : m_mode(_mode) -... -``` - When using nested cycles, do not name the inner cycle variable the same as in the upper cycle, etc. diff --git a/doc/init.md b/doc/init.md index a6c9bb94d8..87e939c636 100644 --- a/doc/init.md +++ b/doc/init.md @@ -59,11 +59,11 @@ Data directory: `/var/lib/bitcoind` PID file: `/var/run/bitcoind/bitcoind.pid` (OpenRC and Upstart) or `/run/bitcoind/bitcoind.pid` (systemd) Lock file: `/var/lock/subsys/bitcoind` (CentOS) -The configuration file, PID directory (if applicable) and data directory -should all be owned by the bitcoin user and group. It is advised for security -reasons to make the configuration file and data directory only readable by the -bitcoin user and group. Access to bitcoin-cli and other bitcoind rpc clients -can then be controlled by group membership. +The PID directory (if applicable) and data directory should both be owned by the +bitcoin user and group. It is advised for security reasons to make the +configuration file and data directory only readable by the bitcoin user and +group. Access to bitcoin-cli and other bitcoind rpc clients can then be +controlled by group membership. NOTE: When using the systemd .service file, the creation of the aforementioned directories and the setting of their permissions is automatically handled by diff --git a/doc/release-notes-15584.md b/doc/release-notes-15584.md new file mode 100644 index 0000000000..3d9b1cc903 --- /dev/null +++ b/doc/release-notes-15584.md @@ -0,0 +1,4 @@ +GUI Changes +----------- +- In 0.18.0 a `./configure` flag was introduced to allow disabling BIP70 support in the GUI (support was enabled by default). In 0.19.0 this flag is now __disabled__ by default. +- If you want compile Bitcoin Core with BIP70 support in the GUI, you can pass `--enable-bip70` to `./configure`.
\ No newline at end of file diff --git a/doc/release-notes-16185.md b/doc/release-notes-16185.md new file mode 100644 index 0000000000..2567ebea40 --- /dev/null +++ b/doc/release-notes-16185.md @@ -0,0 +1,6 @@ +RPC changes +----------- +The `gettransaction` RPC now accepts a third (boolean) argument `verbose`. If +set to `true`, a new `decoded` field will be added to the response containing +the decoded transaction. This field is equivalent to RPC `decoderawtransaction`, +or RPC `getrawtransaction` when `verbose` is passed. diff --git a/doc/release-notes-16695.md b/doc/release-notes-16695.md new file mode 100644 index 0000000000..7acf1dcf97 --- /dev/null +++ b/doc/release-notes-16695.md @@ -0,0 +1,5 @@ +Updated RPCs +------------ + +- The `getchaintxstats` RPC now returns the additional key of + `window_final_block_height`. diff --git a/doc/release-notes-16787.md b/doc/release-notes-16787.md new file mode 100644 index 0000000000..c42b7a5803 --- /dev/null +++ b/doc/release-notes-16787.md @@ -0,0 +1,3 @@ +RPC changes +----------- +The `getnetworkinfo` and `getpeerinfo` commands now contain a new field with decoded network service flags. diff --git a/doc/tor.md b/doc/tor.md index cfb7f16666..2c54e32f84 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -114,7 +114,10 @@ preconfigured and the creation of a hidden service is automatic. If permission p are seen with `-debug=tor` they can be resolved by adding both the user running Tor and the user running bitcoind to the same group and setting permissions appropriately. On Debian-based systems the user running bitcoind can be added to the debian-tor group, -which has the appropriate permissions. +which has the appropriate permissions. Before starting bitcoind you will need to re-login +to allow debian-tor group to be applied. Otherwise you will see the following notice: "tor: +Authentication cookie /run/tor/control.authcookie could not be opened (check permissions)" +on debug.log. An alternative authentication method is the use of the `-torpassword=password` option. The `password` is the clear text form that diff --git a/share/setup.nsi.in b/share/setup.nsi.in index acf9e86667..e9aa1f2b73 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -101,7 +101,7 @@ Section -post SEC0001 WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayVersion "@PACKAGE_VERSION@" WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" Publisher "${COMPANY}" WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" URLInfoAbout "${URL}" - WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayIcon $INSTDIR\uninstall.exe + WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayIcon $INSTDIR\bitcoin-qt.exe WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" UninstallString $INSTDIR\uninstall.exe WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoModify 1 WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoRepair 1 diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 6d8faf3883..7540122418 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -98,6 +98,7 @@ QT_FORMS_UI = \ qt/forms/addressbookpage.ui \ qt/forms/askpassphrasedialog.ui \ qt/forms/coincontroldialog.ui \ + qt/forms/createwalletdialog.ui \ qt/forms/editaddressdialog.ui \ qt/forms/helpmessagedialog.ui \ qt/forms/intro.ui \ @@ -117,6 +118,7 @@ QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ + qt/moc_createwalletdialog.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ qt/moc_bitcoinamountfield.cpp \ @@ -202,6 +204,7 @@ BITCOIN_QT_H = \ qt/clientmodel.h \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ + qt/createwalletdialog.h \ qt/csvmodelwriter.h \ qt/editaddressdialog.h \ qt/guiconstants.h \ @@ -328,6 +331,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/askpassphrasedialog.cpp \ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ + qt/createwalletdialog.cpp \ qt/editaddressdialog.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ diff --git a/src/bech32.cpp b/src/bech32.cpp index d6b29391a9..4c966350b4 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -4,6 +4,8 @@ #include <bech32.h> +#include <assert.h> + namespace { @@ -58,7 +60,7 @@ uint32_t PolyMod(const data& v) // During the course of the loop below, `c` contains the bitpacked coefficients of the // polynomial constructed from just the values of v that were processed so far, mod g(x). In - // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of + // the above example, `c` initially corresponds to 1 mod g(x), and after processing 2 inputs of // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value // for `c`. uint32_t c = 1; @@ -145,6 +147,10 @@ namespace bech32 /** Encode a Bech32 string. */ std::string Encode(const std::string& hrp, const data& values) { + // First ensure that the HRP is all lowercase. BIP-173 requires an encoder + // to return a lowercase Bech32 string, but if given an uppercase HRP, the + // result will always be invalid. + for (const char& c : hrp) assert(c < 'A' || c > 'Z'); data checksum = CreateChecksum(hrp, values); data combined = Cat(values, checksum); std::string ret = hrp + '1'; diff --git a/src/bech32.h b/src/bech32.h index 2e2823e974..fb39cd352b 100644 --- a/src/bech32.h +++ b/src/bech32.h @@ -19,7 +19,7 @@ namespace bech32 { -/** Encode a Bech32 string. Returns the empty string in case of failure. */ +/** Encode a Bech32 string. If hrp contains uppercase characters, this will cause an assertion error. */ std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values); /** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index cde624ce74..a6756fcce7 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -133,7 +133,7 @@ static int AppInitRPC(int argc, char* argv[]) tfm::format(std::cerr, "Error reading configuration file: %s\n", error.c_str()); return EXIT_FAILURE; } - // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) + // Check for -chain, -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) try { SelectBaseParams(gArgs.GetChainName()); } catch (const std::exception& e) { diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index f4972c3cd4..88219f0d0f 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -88,7 +88,7 @@ static int AppInitRawTx(int argc, char* argv[]) return EXIT_FAILURE; } - // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) + // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 361fedf35a..eb7f0098ec 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -27,7 +27,7 @@ static void SetupWalletToolArgs() gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); gArgs.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index cb3c4f70b4..83de684a2b 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -77,7 +77,7 @@ static bool AppInit(int argc, char* argv[]) // Process help and version before taking care about datadir if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { - std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; + std::string strUsage = PACKAGE_NAME " version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { @@ -85,7 +85,7 @@ static bool AppInit(int argc, char* argv[]) } else { - strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME " Daemon\n"; + strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME "\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } @@ -101,7 +101,7 @@ static bool AppInit(int argc, char* argv[]) if (!gArgs.ReadConfigFiles(error, true)) { return InitError(strprintf("Error reading configuration file: %s\n", error)); } - // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) + // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { @@ -142,7 +142,7 @@ static bool AppInit(int argc, char* argv[]) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif - tfm::format(std::cout, PACKAGE_NAME " daemon starting\n"); + tfm::format(std::cout, PACKAGE_NAME " starting\n"); // Daemonize if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) diff --git a/src/chain.h b/src/chain.h index dd9cc2a598..1b67ebbe41 100644 --- a/src/chain.h +++ b/src/chain.h @@ -95,8 +95,8 @@ enum BlockStatus: uint32_t { //! Unused. BLOCK_VALID_UNKNOWN = 0, - //! Parsed, version ok, hash satisfies claimed PoW, 1 <= vtx count <= max, timestamp not in future - BLOCK_VALID_HEADER = 1, + //! Reserved (was BLOCK_VALID_HEADER). + BLOCK_VALID_RESERVED = 1, //! All parent headers found, difficulty matches, timestamp >= median previous, checkpoint. Implies all parents //! are also at least TREE. @@ -117,7 +117,7 @@ enum BlockStatus: uint32_t { BLOCK_VALID_SCRIPTS = 5, //! All validity bits. - BLOCK_VALID_MASK = BLOCK_VALID_HEADER | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS | + BLOCK_VALID_MASK = BLOCK_VALID_RESERVED | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS | BLOCK_VALID_CHAIN | BLOCK_VALID_SCRIPTS, BLOCK_HAVE_DATA = 8, //!< full block available in blk*.dat diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 9b98dff3ca..4bb66c8d8b 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -17,10 +17,11 @@ const std::string CBaseChainParams::REGTEST = "regtest"; void SetupChainParamsBaseOptions() { + gArgs.AddArg("-chain=<chain>", "Use the chain <chain> (default: main). Allowed values: main, test, regtest", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " - "This is intended for regression testing tools and app development.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + "This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-segwitheight=<n>", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-testnet", "Use the test chain", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); } diff --git a/src/core_write.cpp b/src/core_write.cpp index 4d64446d7b..7ce2a49836 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -144,7 +144,7 @@ void ScriptToUniv(const CScript& script, UniValue& out, bool include_address) out.pushKV("type", GetTxnOutputType(type)); CTxDestination address; - if (include_address && ExtractDestination(script, address)) { + if (include_address && ExtractDestination(script, address) && type != TX_PUBKEY) { out.pushKV("address", EncodeDestination(address)); } } @@ -160,7 +160,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, if (fIncludeHex) out.pushKV("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end())); - if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { + if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired) || type == TX_PUBKEY) { out.pushKV("type", GetTxnOutputType(type)); return; } diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index eeec6dec25..126e3479f3 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -5,8 +5,10 @@ #include <stdio.h> #include <util/system.h> #include <walletinitinterface.h> +#include <support/allocators/secure.h> class CWallet; +enum class WalletCreationStatus; namespace interfaces { class Chain; @@ -74,6 +76,11 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& throw std::logic_error("Wallet function called in non-wallet build."); } +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result) +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + namespace interfaces { class Wallet; diff --git a/src/init.cpp b/src/init.cpp index 042335b8e7..bb82130542 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1060,7 +1060,7 @@ bool AppInitParameterInteraction() { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-incrementalrelayfee", ""), n)) - return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", ""))); + return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", "")).translated); incrementalRelayFee = CFeeRate(n); } @@ -1104,9 +1104,9 @@ bool AppInitParameterInteraction() if (gArgs.IsArgSet("-minrelaytxfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n)) { - return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", ""))); + return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", "")).translated); } - // High fee check is done afterward in WalletParameterInteraction() + // High fee check is done afterward in CWallet::CreateWalletFromFile() ::minRelayTxFee = CFeeRate(n); } else if (incrementalRelayFee > ::minRelayTxFee) { // Allow only setting incrementalRelayFee to control both @@ -1120,7 +1120,7 @@ bool AppInitParameterInteraction() { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) - return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", ""))); + return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", "")).translated); } // Feerate used to define dust. Shouldn't be changed lightly as old @@ -1129,7 +1129,7 @@ bool AppInitParameterInteraction() { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n)) - return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", ""))); + return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")).translated); dustRelayFee = CFeeRate(n); } @@ -1760,7 +1760,8 @@ bool AppInitMain(InitInterfaces& interfaces) CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; connOptions.nMaxConnections = nMaxConnections; - connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); + connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections); + connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCKS_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay); connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index fc49817502..c80a8789fc 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -24,6 +24,7 @@ #include <primitives/block.h> #include <rpc/server.h> #include <shutdown.h> +#include <support/allocators/secure.h> #include <sync.h> #include <txmempool.h> #include <ui_interface.h> @@ -43,6 +44,7 @@ fs::path GetWalletDir(); std::vector<fs::path> ListWalletDir(); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning); +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result); namespace interfaces { @@ -60,6 +62,7 @@ public: return gArgs.ParseParameters(argc, argv, error); } bool readConfigFiles(std::string& error) override { return gArgs.ReadConfigFiles(error, true); } + void forceSetArg(const std::string& arg, const std::string& value) override { gArgs.ForceSetArg(arg, value); } bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); } bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); } void selectParams(const std::string& network) override { SelectParams(network); } @@ -258,6 +261,13 @@ public: { return MakeWallet(LoadWallet(*m_interfaces.chain, name, error, warning)); } + WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr<Wallet>& result) override + { + std::shared_ptr<CWallet> wallet; + WalletCreationStatus status = CreateWallet(*m_interfaces.chain, passphrase, wallet_creation_flags, name, error, warning, wallet); + result = MakeWallet(wallet); + return status; + } std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override { return MakeHandler(::uiInterface.InitMessage_connect(fn)); diff --git a/src/interfaces/node.h b/src/interfaces/node.h index b93b52c5cc..2f4f396e72 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -9,6 +9,7 @@ #include <amount.h> // For CAmount #include <net.h> // For CConnman::NumConnections #include <netaddress.h> // For Network +#include <support/allocators/secure.h> // For SecureString #include <functional> #include <memory> @@ -27,6 +28,7 @@ class RPCTimerInterface; class UniValue; class proxyType; struct CNodeStateStats; +enum class WalletCreationStatus; namespace interfaces { class Handler; @@ -44,6 +46,9 @@ public: //! Set command line arguments. virtual bool parseParameters(int argc, const char* const argv[], std::string& error) = 0; + //! Set a command line argument + virtual void forceSetArg(const std::string& arg, const std::string& value) = 0; + //! Set a command line argument if it doesn't already have a value virtual bool softSetArg(const std::string& arg, const std::string& value) = 0; @@ -200,6 +205,9 @@ public: //! with handleLoadWallet. virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, std::string& error, std::string& warning) = 0; + //! Create a wallet from file + virtual WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr<Wallet>& result) = 0; + //! Register handler for init messages. using InitMessageFn = std::function<void(const std::string& message)>; virtual std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 077dc1ab4d..0c8d92eba5 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -65,7 +65,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx) { WalletTxStatus result; - result.block_height = locked_chain.getBlockHeight(wtx.hashBlock).get_value_or(std::numeric_limits<int>::max()); + result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max()); result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.time_received = wtx.nTimeReceived; diff --git a/src/net.cpp b/src/net.cpp index 337d1f6a46..89f82aa3d2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -352,7 +352,7 @@ static CAddress GetBindAddress(SOCKET sock) return addr_bind; } -CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection) +CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only) { if (pszDest == nullptr) { if (IsLocal(addrConnect)) @@ -442,7 +442,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); CAddress addr_bind = GetBindAddress(hSocket); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false); + CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false, block_relay_only); pnode->AddRef(); return pnode; @@ -499,9 +499,11 @@ void CNode::copyStats(CNodeStats &stats) X(nServices); X(addr); X(addrBind); - { - LOCK(cs_filter); - X(fRelayTxes); + if (m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_filter); + stats.fRelayTxes = m_tx_relay->fRelayTxes; + } else { + stats.fRelayTxes = false; } X(nLastSend); X(nLastRecv); @@ -528,9 +530,11 @@ void CNode::copyStats(CNodeStats &stats) } X(m_legacyWhitelisted); X(m_permissionFlags); - { - LOCK(cs_feeFilter); - X(minFeeFilter); + if (m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_feeFilter); + stats.minFeeFilter = m_tx_relay->minFeeFilter; + } else { + stats.minFeeFilter = 0; } // It is common for nodes with good ping times to suddenly become lagged, @@ -818,11 +822,17 @@ bool CConnman::AttemptToEvictConnection() continue; if (node->fDisconnect) continue; - LOCK(node->cs_filter); + bool peer_relay_txes = false; + bool peer_filter_not_null = false; + if (node->m_tx_relay != nullptr) { + LOCK(node->m_tx_relay->cs_filter); + peer_relay_txes = node->m_tx_relay->fRelayTxes; + peer_filter_not_null = node->m_tx_relay->pfilter != nullptr; + } NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, node->nLastBlockTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), - node->fRelayTxes, node->pfilter != nullptr, node->addr, node->nKeyedNetGroup, + peer_relay_txes, peer_filter_not_null, node->addr, node->nKeyedNetGroup, node->m_prefer_evict}; vEvictionCandidates.push_back(candidate); } @@ -895,7 +905,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); CAddress addr; int nInbound = 0; - int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler); + int nMaxInbound = nMaxConnections - m_max_outbound; if (hSocket != INVALID_SOCKET) { if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { @@ -1655,7 +1665,7 @@ int CConnman::GetExtraOutboundCount() } } } - return std::max(nOutbound - nMaxOutbound, 0); + return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0); } void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) @@ -1715,7 +1725,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) CAddress addrConnect; // Only connect out to one peer per network group (/16 for IPv4). - int nOutbound = 0; + int nOutboundFullRelay = 0; + int nOutboundBlockRelay = 0; std::set<std::vector<unsigned char> > setConnected; { LOCK(cs_vNodes); @@ -1727,7 +1738,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // also have the added issue that they're attacker controlled and could be used // to prevent us from connecting to particular hosts if we used them here. setConnected.insert(pnode->addr.GetGroup()); - nOutbound++; + if (pnode->m_tx_relay == nullptr) { + nOutboundBlockRelay++; + } else if (!pnode->fFeeler) { + nOutboundFullRelay++; + } } } } @@ -1746,7 +1761,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // bool fFeeler = false; - if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) { + if (nOutboundFullRelay >= m_max_outbound_full_relay && nOutboundBlockRelay >= m_max_outbound_block_relay && !GetTryNewOutboundPeer()) { int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds). if (nTime > nNextFeeler) { nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); @@ -1820,7 +1835,14 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); } - OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler); + // Open this connection as block-relay-only if we're already at our + // full-relay capacity, but not yet at our block-relay peer limit. + // (It should not be possible for fFeeler to be set if we're not + // also at our block-relay peer limit, but check against that as + // well for sanity.) + bool block_relay_only = nOutboundBlockRelay < m_max_outbound_block_relay && !fFeeler && nOutboundFullRelay >= m_max_outbound_full_relay; + + OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler, false, block_relay_only); } } } @@ -1907,7 +1929,7 @@ void CConnman::ThreadOpenAddedConnections() } // if successful, this moves the passed grant to the constructed node -void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection) +void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection, bool block_relay_only) { // // Initiate outbound network connection @@ -1926,7 +1948,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai } else if (FindNode(std::string(pszDest))) return; - CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection); + CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection, block_relay_only); if (!pnode) return; @@ -2229,7 +2251,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) if (semOutbound == nullptr) { // initialize semaphore - semOutbound = MakeUnique<CSemaphore>(std::min((nMaxOutbound + nMaxFeeler), nMaxConnections)); + semOutbound = MakeUnique<CSemaphore>(std::min(m_max_outbound, nMaxConnections)); } if (semAddnode == nullptr) { // initialize semaphore @@ -2307,7 +2329,7 @@ void CConnman::Interrupt() InterruptSocks5(true); if (semOutbound) { - for (int i=0; i<(nMaxOutbound + nMaxFeeler); i++) { + for (int i=0; i<m_max_outbound; i++) { semOutbound->post(); } } @@ -2617,14 +2639,17 @@ int CConnman::GetBestHeight() const unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } -CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn) +CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn, bool block_relay_only) : nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn), addrBind(addrBindIn), fInbound(fInboundIn), nKeyedNetGroup(nKeyedNetGroupIn), addrKnown(5000, 0.001), - filterInventoryKnown(50000, 0.000001), + // Don't relay addr messages to peers that we connect to as block-relay-only + // peers (to prevent adversaries from inferring these links from addr + // traffic). + m_addr_relay_peer(!block_relay_only), id(idIn), nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn), @@ -2633,8 +2658,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; hashContinue = uint256(); - filterInventoryKnown.reset(); - pfilter = MakeUnique<CBloomFilter>(); + if (!block_relay_only) { + m_tx_relay = MakeUnique<TxRelay>(); + } for (const std::string &msg : getAllNetMessageTypes()) mapRecvBytesPerMsgCmd[msg] = 0; @@ -61,10 +61,12 @@ static const unsigned int MAX_ADDR_TO_SEND = 1000; static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 4 * 1000 * 1000; /** Maximum length of the user agent string in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; -/** Maximum number of automatic outgoing nodes */ -static const int MAX_OUTBOUND_CONNECTIONS = 8; +/** Maximum number of automatic outgoing nodes over which we'll relay everything (blocks, tx, addrs, etc) */ +static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8; /** Maximum number of addnode outgoing nodes */ static const int MAX_ADDNODE_CONNECTIONS = 8; +/** Maximum number of block-relay-only outgoing connections */ +static const int MAX_BLOCKS_ONLY_CONNECTIONS = 2; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ @@ -131,7 +133,8 @@ public: { ServiceFlags nLocalServices = NODE_NONE; int nMaxConnections = 0; - int nMaxOutbound = 0; + int m_max_outbound_full_relay = 0; + int m_max_outbound_block_relay = 0; int nMaxAddnode = 0; int nMaxFeeler = 0; int nBestHeight = 0; @@ -155,10 +158,12 @@ public: void Init(const Options& connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; - nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections); + m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); + m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; + m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; m_banman = connOptions.m_banman; @@ -197,7 +202,7 @@ public: bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); - void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false); + void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false, bool block_relay_only = false); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func); @@ -253,7 +258,7 @@ public: void AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); std::vector<CAddress> GetAddresses(); - // This allows temporarily exceeding nMaxOutbound, with the goal of finding + // This allows temporarily exceeding m_max_outbound_full_relay, with the goal of finding // a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer(); @@ -355,7 +360,7 @@ private: CNode* FindNode(const CService& addr); bool AttemptToEvictConnection(); - CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection); + CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only); void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; void DeleteNode(CNode* pnode); @@ -414,9 +419,17 @@ private: std::unique_ptr<CSemaphore> semOutbound; std::unique_ptr<CSemaphore> semAddnode; int nMaxConnections; - int nMaxOutbound; + + // How many full-relay (tx, block, addr) outbound peers we want + int m_max_outbound_full_relay; + + // How many block-relay only outbound peers we want + // We do not relay tx or addr messages with these peers + int m_max_outbound_block_relay; + int nMaxAddnode; int nMaxFeeler; + int m_max_outbound; bool m_use_addrman_outgoing; std::atomic<int> nBestHeight; CClientUIInterface* clientInterface; @@ -442,7 +455,7 @@ private: std::thread threadMessageHandler; /** flag for deciding to connect to an extra outbound peer, - * in excess of nMaxOutbound + * in excess of m_max_outbound_full_relay * This takes the place of a feeler connection */ std::atomic_bool m_try_another_outbound_peer; @@ -681,15 +694,8 @@ public: // Setting fDisconnect to true will cause the node to be disconnected the // next time DisconnectNodes() runs std::atomic_bool fDisconnect{false}; - // We use fRelayTxes for two purposes - - // a) it allows us to not relay tx invs before receiving the peer's version message - // b) the peer may tell us in its version message that we should not relay tx invs - // unless it loads a bloom filter. - bool fRelayTxes GUARDED_BY(cs_filter){false}; bool fSentAddr{false}; CSemaphoreGrant grantOutbound; - mutable CCriticalSection cs_filter; - std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter); std::atomic<int> nRefCount{0}; const uint64_t nKeyedNetGroup; @@ -708,28 +714,51 @@ public: std::vector<CAddress> vAddrToSend; CRollingBloomFilter addrKnown; bool fGetAddr{false}; - std::set<uint256> setKnown; int64_t nNextAddrSend GUARDED_BY(cs_sendProcessing){0}; int64_t nNextLocalAddrSend GUARDED_BY(cs_sendProcessing){0}; - // inventory based relay - CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_inventory); - // Set of transaction ids we still have to announce. - // They are sorted by the mempool before relay, so the order is not important. - std::set<uint256> setInventoryTxToSend; + const bool m_addr_relay_peer; + bool IsAddrRelayPeer() const { return m_addr_relay_peer; } + // List of block ids we still have announce. // There is no final sorting before sending, as they are always sent immediately // and in the order requested. std::vector<uint256> vInventoryBlockToSend GUARDED_BY(cs_inventory); CCriticalSection cs_inventory; - int64_t nNextInvSend{0}; + + struct TxRelay { + TxRelay() { pfilter = MakeUnique<CBloomFilter>(); } + mutable CCriticalSection cs_filter; + // We use fRelayTxes for two purposes - + // a) it allows us to not relay tx invs before receiving the peer's version message + // b) the peer may tell us in its version message that we should not relay tx invs + // unless it loads a bloom filter. + bool fRelayTxes GUARDED_BY(cs_filter){false}; + std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter) GUARDED_BY(cs_filter); + + mutable CCriticalSection cs_tx_inventory; + CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_tx_inventory){50000, 0.000001}; + // Set of transaction ids we still have to announce. + // They are sorted by the mempool before relay, so the order is not important. + std::set<uint256> setInventoryTxToSend; + // Used for BIP35 mempool sending + bool fSendMempool GUARDED_BY(cs_tx_inventory){false}; + // Last time a "MEMPOOL" request was serviced. + std::atomic<int64_t> timeLastMempoolReq{0}; + int64_t nNextInvSend{0}; + + CCriticalSection cs_feeFilter; + // Minimum fee rate with which to filter inv's to this node + CAmount minFeeFilter GUARDED_BY(cs_feeFilter){0}; + CAmount lastSentFeeFilter{0}; + int64_t nextSendTimeFeeFilter{0}; + }; + + // m_tx_relay == nullptr if we're not relaying transactions with this peer + std::unique_ptr<TxRelay> m_tx_relay; + // Used for headers announcements - unfiltered blocks to relay std::vector<uint256> vBlockHashesToAnnounce GUARDED_BY(cs_inventory); - // Used for BIP35 mempool sending - bool fSendMempool GUARDED_BY(cs_inventory){false}; - - // Last time a "MEMPOOL" request was serviced. - std::atomic<int64_t> timeLastMempoolReq{0}; // Block and TXN accept times std::atomic<int64_t> nLastBlockTime{0}; @@ -746,15 +775,10 @@ public: std::atomic<int64_t> nMinPingUsecTime{std::numeric_limits<int64_t>::max()}; // Whether a ping is requested. std::atomic<bool> fPingQueued{false}; - // Minimum fee rate with which to filter inv's to this node - CAmount minFeeFilter GUARDED_BY(cs_feeFilter){0}; - CCriticalSection cs_feeFilter; - CAmount lastSentFeeFilter{0}; - int64_t nextSendTimeFeeFilter{0}; std::set<uint256> orphan_work_set; - CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false); + CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool block_relay_only = false); ~CNode(); CNode(const CNode&) = delete; CNode& operator=(const CNode&) = delete; @@ -847,20 +871,21 @@ public: void AddInventoryKnown(const CInv& inv) { - { - LOCK(cs_inventory); - filterInventoryKnown.insert(inv.hash); + if (m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_tx_inventory); + m_tx_relay->filterInventoryKnown.insert(inv.hash); } } void PushInventory(const CInv& inv) { - LOCK(cs_inventory); - if (inv.type == MSG_TX) { - if (!filterInventoryKnown.contains(inv.hash)) { - setInventoryTxToSend.insert(inv.hash); + if (inv.type == MSG_TX && m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_tx_inventory); + if (!m_tx_relay->filterInventoryKnown.contains(inv.hash)) { + m_tx_relay->setInventoryTxToSend.insert(inv.hash); } } else if (inv.type == MSG_BLOCK) { + LOCK(cs_inventory); vInventoryBlockToSend.push_back(inv.hash); } } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 520dfcbb66..7f2fea5584 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -262,7 +262,7 @@ struct CNodeState { bool fSupportsDesiredCmpctVersion; /** State used to enforce CHAIN_SYNC_TIMEOUT - * Only in effect for outbound, non-manual connections, with + * Only in effect for outbound, non-manual, full-relay connections, with * m_protect == false * Algorithm: if a peer's best known block has less work than our tip, * set a timeout CHAIN_SYNC_TIMEOUT seconds in the future: @@ -425,7 +425,7 @@ static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) CAddress addrMe = CAddress(CService(), nLocalNodeServices); connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, - nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes)); + nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes && pnode->m_tx_relay != nullptr)); if (fLogIPs) { LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid); @@ -757,7 +757,7 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) } // Returns true for outbound peers, excluding manual connections, feelers, and -// one-shots +// one-shots. static bool IsOutboundDisconnectionCandidate(const CNode *node) { return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot); @@ -1330,7 +1330,7 @@ static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connma assert(nRelayNodes <= best.size()); auto sortfunc = [&best, &hasher, nRelayNodes](CNode* pnode) { - if (pnode->nVersion >= CADDR_TIME_VERSION) { + if (pnode->nVersion >= CADDR_TIME_VERSION && pnode->IsAddrRelayPeer()) { uint64_t hashKey = CSipHasher(hasher).Write(pnode->GetId()).Finalize(); for (unsigned int i = 0; i < nRelayNodes; i++) { if (hashKey > best[i].first) { @@ -1449,11 +1449,11 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c { bool sendMerkleBlock = false; CMerkleBlock merkleBlock; - { - LOCK(pfrom->cs_filter); - if (pfrom->pfilter) { + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_filter); + if (pfrom->m_tx_relay->pfilter) { sendMerkleBlock = true; - merkleBlock = CMerkleBlock(*pblock, *pfrom->pfilter); + merkleBlock = CMerkleBlock(*pblock, *pfrom->m_tx_relay->pfilter); } } if (sendMerkleBlock) { @@ -1513,7 +1513,12 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin(); std::vector<CInv> vNotFound; const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); - { + + // Note that if we receive a getdata for a MSG_TX or MSG_WITNESS_TX from a + // block-relay-only outbound peer, we will stop processing further getdata + // messages from this peer (likely resulting in our peer eventually + // disconnecting us). + if (pfrom->m_tx_relay != nullptr) { LOCK(cs_main); while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) { @@ -1533,11 +1538,11 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm if (mi != mapRelay.end()) { connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second)); push = true; - } else if (pfrom->timeLastMempoolReq) { + } else if (pfrom->m_tx_relay->timeLastMempoolReq) { auto txinfo = mempool.info(inv.hash); // To protect privacy, do not answer getdata using the mempool when // that TX couldn't have been INVed in reply to a MEMPOOL request. - if (txinfo.tx && txinfo.nTime <= pfrom->timeLastMempoolReq) { + if (txinfo.tx && txinfo.nTime <= pfrom->m_tx_relay->timeLastMempoolReq) { connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx)); push = true; } @@ -1773,9 +1778,11 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve } } - if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr) { - // If this is an outbound peer, check to see if we should protect + if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr && pfrom->m_tx_relay != nullptr) { + // If this is an outbound full-relay peer, check to see if we should protect // it from the bad/lagging chain logic. + // Note that block-relay-only peers are already implicitly protected, so we + // only consider setting m_protect for the full-relay peers. if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId()); nodestate->m_chain_sync.m_protect = true; @@ -1996,9 +2003,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // set nodes not capable of serving the complete blockchain history as "limited nodes" pfrom->m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED)); - { - LOCK(pfrom->cs_filter); - pfrom->fRelayTxes = fRelay; // set to true after we get the first filter* message + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_filter); + pfrom->m_tx_relay->fRelayTxes = fRelay; // set to true after we get the first filter* message } // Change version @@ -2017,7 +2024,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr UpdatePreferredDownload(pfrom, State(pfrom->GetId())); } - if (!pfrom->fInbound) + if (!pfrom->fInbound && pfrom->IsAddrRelayPeer()) { // Advertise our address if (fListen && !::ChainstateActive().IsInitialBlockDownload()) @@ -2089,9 +2096,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Mark this node as currently connected, so we update its timestamp later. LOCK(cs_main); State(pfrom->GetId())->fCurrentlyConnected = true; - LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s\n", - pfrom->nVersion.load(), pfrom->nStartingHeight, pfrom->GetId(), - (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : "")); + LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n", + pfrom->nVersion.load(), pfrom->nStartingHeight, + pfrom->GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : ""), + pfrom->m_tx_relay == nullptr ? "block-relay" : "full-relay"); } if (pfrom->nVersion >= SENDHEADERS_VERSION) { @@ -2132,6 +2140,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Don't want addr from older versions unless seeding if (pfrom->nVersion < CADDR_TIME_VERSION && connman->GetAddressCount() > 1000) return true; + if (!pfrom->IsAddrRelayPeer()) { + return true; + } if (vAddr.size() > 1000) { LOCK(cs_main); @@ -2215,7 +2226,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return false; } - bool fBlocksOnly = !g_relay_txes; + // We won't accept tx inv's if we're in blocks-only mode, or this is a + // block-relay-only peer + bool fBlocksOnly = !g_relay_txes || (pfrom->m_tx_relay == nullptr); // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true if (pfrom->HasPermission(PF_RELAY)) @@ -2254,7 +2267,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr { pfrom->AddInventoryKnown(inv); if (fBlocksOnly) { - LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId()); + LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol, disconnecting peer=%d\n", inv.hash.ToString(), pfrom->GetId()); + pfrom->fDisconnect = true; + return true; } else if (!fAlreadyHave && !fImporting && !fReindex && !::ChainstateActive().IsInitialBlockDownload()) { RequestTx(State(pfrom->GetId()), inv.hash, current_time); } @@ -2471,9 +2486,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (strCommand == NetMsgType::TX) { // Stop processing the transaction early if // We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off - if (!g_relay_txes && !pfrom->HasPermission(PF_RELAY)) + // or if this peer is supposed to be a block-relay-only peer + if ((!g_relay_txes && !pfrom->HasPermission(PF_RELAY)) || (pfrom->m_tx_relay == nullptr)) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; return true; } @@ -2990,6 +3007,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrint(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom->GetId()); return true; } + if (!pfrom->IsAddrRelayPeer()) { + LogPrint(BCLog::NET, "Ignoring \"getaddr\" from block-relay-only connection. peer=%d\n", pfrom->GetId()); + return true; + } // Only send one GetAddr response per connection to reduce resource waste // and discourage addr stamping of INV announcements. @@ -3031,8 +3052,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return true; } - LOCK(pfrom->cs_inventory); - pfrom->fSendMempool = true; + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_tx_inventory); + pfrom->m_tx_relay->fSendMempool = true; + } return true; } @@ -3123,12 +3146,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LOCK(cs_main); Misbehaving(pfrom->GetId(), 100); } - else + else if (pfrom->m_tx_relay != nullptr) { - LOCK(pfrom->cs_filter); - pfrom->pfilter.reset(new CBloomFilter(filter)); - pfrom->pfilter->UpdateEmptyFull(); - pfrom->fRelayTxes = true; + LOCK(pfrom->m_tx_relay->cs_filter); + pfrom->m_tx_relay->pfilter.reset(new CBloomFilter(filter)); + pfrom->m_tx_relay->pfilter->UpdateEmptyFull(); + pfrom->m_tx_relay->fRelayTxes = true; } return true; } @@ -3142,10 +3165,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr bool bad = false; if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) { bad = true; - } else { - LOCK(pfrom->cs_filter); - if (pfrom->pfilter) { - pfrom->pfilter->insert(vData); + } else if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_filter); + if (pfrom->m_tx_relay->pfilter) { + pfrom->m_tx_relay->pfilter->insert(vData); } else { bad = true; } @@ -3158,11 +3181,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } if (strCommand == NetMsgType::FILTERCLEAR) { - LOCK(pfrom->cs_filter); + if (pfrom->m_tx_relay == nullptr) { + return true; + } + LOCK(pfrom->m_tx_relay->cs_filter); if (pfrom->GetLocalServices() & NODE_BLOOM) { - pfrom->pfilter.reset(new CBloomFilter()); + pfrom->m_tx_relay->pfilter.reset(new CBloomFilter()); } - pfrom->fRelayTxes = true; + pfrom->m_tx_relay->fRelayTxes = true; return true; } @@ -3170,9 +3196,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr CAmount newFeeFilter = 0; vRecv >> newFeeFilter; if (MoneyRange(newFeeFilter)) { - { - LOCK(pfrom->cs_feeFilter); - pfrom->minFeeFilter = newFeeFilter; + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_feeFilter); + pfrom->m_tx_relay->minFeeFilter = newFeeFilter; } LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->GetId()); } @@ -3449,6 +3475,8 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds) if (state == nullptr) return; // shouldn't be possible, but just in case // Don't evict our protected peers if (state->m_chain_sync.m_protect) return; + // Don't evict our block-relay-only peers. + if (pnode->m_tx_relay == nullptr) return; if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) { worst_peer = pnode->GetId(); oldest_block_announcement = state->m_last_block_announcement; @@ -3576,7 +3604,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Address refresh broadcast int64_t nNow = GetTimeMicros(); - if (!::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) { + if (pto->IsAddrRelayPeer() && !::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) { AdvertiseLocal(pto); pto->nNextLocalAddrSend = PoissonNextSend(nNow, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); } @@ -3584,7 +3612,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // // Message: addr // - if (pto->nNextAddrSend < nNow) { + if (pto->IsAddrRelayPeer() && pto->nNextAddrSend < nNow) { pto->nNextAddrSend = PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL); std::vector<CAddress> vAddr; vAddr.reserve(pto->vAddrToSend.size()); @@ -3792,120 +3820,123 @@ bool PeerLogicValidation::SendMessages(CNode* pto) } pto->vInventoryBlockToSend.clear(); - // Check whether periodic sends should happen - bool fSendTrickle = pto->HasPermission(PF_NOBAN); - if (pto->nNextInvSend < nNow) { - fSendTrickle = true; - if (pto->fInbound) { - pto->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL); - } else { - // Use half the delay for outbound peers, as there is less privacy concern for them. - pto->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1); + if (pto->m_tx_relay != nullptr) { + LOCK(pto->m_tx_relay->cs_tx_inventory); + // Check whether periodic sends should happen + bool fSendTrickle = pto->HasPermission(PF_NOBAN); + if (pto->m_tx_relay->nNextInvSend < nNow) { + fSendTrickle = true; + if (pto->fInbound) { + pto->m_tx_relay->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL); + } else { + // Use half the delay for outbound peers, as there is less privacy concern for them. + pto->m_tx_relay->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1); + } } - } - - // Time to send but the peer has requested we not relay transactions. - if (fSendTrickle) { - LOCK(pto->cs_filter); - if (!pto->fRelayTxes) pto->setInventoryTxToSend.clear(); - } - // Respond to BIP35 mempool requests - if (fSendTrickle && pto->fSendMempool) { - auto vtxinfo = mempool.infoAll(); - pto->fSendMempool = false; - CAmount filterrate = 0; - { - LOCK(pto->cs_feeFilter); - filterrate = pto->minFeeFilter; + // Time to send but the peer has requested we not relay transactions. + if (fSendTrickle) { + LOCK(pto->m_tx_relay->cs_filter); + if (!pto->m_tx_relay->fRelayTxes) pto->m_tx_relay->setInventoryTxToSend.clear(); } - LOCK(pto->cs_filter); - - for (const auto& txinfo : vtxinfo) { - const uint256& hash = txinfo.tx->GetHash(); - CInv inv(MSG_TX, hash); - pto->setInventoryTxToSend.erase(hash); - if (filterrate) { - if (txinfo.feeRate.GetFeePerK() < filterrate) - continue; - } - if (pto->pfilter) { - if (!pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + // Respond to BIP35 mempool requests + if (fSendTrickle && pto->m_tx_relay->fSendMempool) { + auto vtxinfo = mempool.infoAll(); + pto->m_tx_relay->fSendMempool = false; + CAmount filterrate = 0; + { + LOCK(pto->m_tx_relay->cs_feeFilter); + filterrate = pto->m_tx_relay->minFeeFilter; } - pto->filterInventoryKnown.insert(hash); - vInv.push_back(inv); - if (vInv.size() == MAX_INV_SZ) { - connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); - vInv.clear(); + + LOCK(pto->m_tx_relay->cs_filter); + + for (const auto& txinfo : vtxinfo) { + const uint256& hash = txinfo.tx->GetHash(); + CInv inv(MSG_TX, hash); + pto->m_tx_relay->setInventoryTxToSend.erase(hash); + if (filterrate) { + if (txinfo.feeRate.GetFeePerK() < filterrate) + continue; + } + if (pto->m_tx_relay->pfilter) { + if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + } + pto->m_tx_relay->filterInventoryKnown.insert(hash); + vInv.push_back(inv); + if (vInv.size() == MAX_INV_SZ) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + vInv.clear(); + } } + pto->m_tx_relay->timeLastMempoolReq = GetTime(); } - pto->timeLastMempoolReq = GetTime(); - } - // Determine transactions to relay - if (fSendTrickle) { - // Produce a vector with all candidates for sending - std::vector<std::set<uint256>::iterator> vInvTx; - vInvTx.reserve(pto->setInventoryTxToSend.size()); - for (std::set<uint256>::iterator it = pto->setInventoryTxToSend.begin(); it != pto->setInventoryTxToSend.end(); it++) { - vInvTx.push_back(it); - } - CAmount filterrate = 0; - { - LOCK(pto->cs_feeFilter); - filterrate = pto->minFeeFilter; - } - // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. - // A heap is used so that not all items need sorting if only a few are being sent. - CompareInvMempoolOrder compareInvMempoolOrder(&mempool); - std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); - // No reason to drain out at many times the network's capacity, - // especially since we have many peers and some will draw much shorter delays. - unsigned int nRelayedTransactions = 0; - LOCK(pto->cs_filter); - while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { - // Fetch the top element from the heap - std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); - std::set<uint256>::iterator it = vInvTx.back(); - vInvTx.pop_back(); - uint256 hash = *it; - // Remove it from the to-be-sent set - pto->setInventoryTxToSend.erase(it); - // Check if not in the filter already - if (pto->filterInventoryKnown.contains(hash)) { - continue; + // Determine transactions to relay + if (fSendTrickle) { + // Produce a vector with all candidates for sending + std::vector<std::set<uint256>::iterator> vInvTx; + vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size()); + for (std::set<uint256>::iterator it = pto->m_tx_relay->setInventoryTxToSend.begin(); it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) { + vInvTx.push_back(it); } - // Not in the mempool anymore? don't bother sending it. - auto txinfo = mempool.info(hash); - if (!txinfo.tx) { - continue; - } - if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) { - continue; - } - if (pto->pfilter && !pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; - // Send - vInv.push_back(CInv(MSG_TX, hash)); - nRelayedTransactions++; + CAmount filterrate = 0; { - // Expire old relay messages - while (!vRelayExpiration.empty() && vRelayExpiration.front().first < nNow) - { - mapRelay.erase(vRelayExpiration.front().second); - vRelayExpiration.pop_front(); + LOCK(pto->m_tx_relay->cs_feeFilter); + filterrate = pto->m_tx_relay->minFeeFilter; + } + // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. + // A heap is used so that not all items need sorting if only a few are being sent. + CompareInvMempoolOrder compareInvMempoolOrder(&mempool); + std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); + // No reason to drain out at many times the network's capacity, + // especially since we have many peers and some will draw much shorter delays. + unsigned int nRelayedTransactions = 0; + LOCK(pto->m_tx_relay->cs_filter); + while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { + // Fetch the top element from the heap + std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); + std::set<uint256>::iterator it = vInvTx.back(); + vInvTx.pop_back(); + uint256 hash = *it; + // Remove it from the to-be-sent set + pto->m_tx_relay->setInventoryTxToSend.erase(it); + // Check if not in the filter already + if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) { + continue; } + // Not in the mempool anymore? don't bother sending it. + auto txinfo = mempool.info(hash); + if (!txinfo.tx) { + continue; + } + if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) { + continue; + } + if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + // Send + vInv.push_back(CInv(MSG_TX, hash)); + nRelayedTransactions++; + { + // Expire old relay messages + while (!vRelayExpiration.empty() && vRelayExpiration.front().first < nNow) + { + mapRelay.erase(vRelayExpiration.front().second); + vRelayExpiration.pop_front(); + } - auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx))); - if (ret.second) { - vRelayExpiration.push_back(std::make_pair(nNow + 15 * 60 * 1000000, ret.first)); + auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx))); + if (ret.second) { + vRelayExpiration.push_back(std::make_pair(nNow + 15 * 60 * 1000000, ret.first)); + } } + if (vInv.size() == MAX_INV_SZ) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + vInv.clear(); + } + pto->m_tx_relay->filterInventoryKnown.insert(hash); } - if (vInv.size() == MAX_INV_SZ) { - connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); - vInv.clear(); - } - pto->filterInventoryKnown.insert(hash); } } } @@ -4066,27 +4097,27 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Message: feefilter // // We don't want white listed peers to filter txs to us if we have -whitelistforcerelay - if (pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && + if (pto->m_tx_relay != nullptr && pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && !pto->HasPermission(PF_FORCERELAY)) { CAmount currentFilter = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); int64_t timeNow = GetTimeMicros(); - if (timeNow > pto->nextSendTimeFeeFilter) { + if (timeNow > pto->m_tx_relay->nextSendTimeFeeFilter) { static CFeeRate default_feerate(DEFAULT_MIN_RELAY_TX_FEE); static FeeFilterRounder filterRounder(default_feerate); CAmount filterToSend = filterRounder.round(currentFilter); // We always have a fee filter of at least minRelayTxFee filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK()); - if (filterToSend != pto->lastSentFeeFilter) { + if (filterToSend != pto->m_tx_relay->lastSentFeeFilter) { connman->PushMessage(pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend)); - pto->lastSentFeeFilter = filterToSend; + pto->m_tx_relay->lastSentFeeFilter = filterToSend; } - pto->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL); + pto->m_tx_relay->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. - else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->nextSendTimeFeeFilter && - (currentFilter < 3 * pto->lastSentFeeFilter / 4 || currentFilter > 4 * pto->lastSentFeeFilter / 3)) { - pto->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; + else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->m_tx_relay->nextSendTimeFeeFilter && + (currentFilter < 3 * pto->m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto->m_tx_relay->lastSentFeeFilter / 3)) { + pto->m_tx_relay->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; } } } diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index a89a15bc9d..2ababb5e1e 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -18,12 +18,13 @@ #include <QMessageBox> #include <QPushButton> -AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) : +AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out) : QDialog(parent), ui(new Ui::AskPassphraseDialog), mode(_mode), model(nullptr), - fCapsLock(false) + fCapsLock(false), + m_passphrase_out(passphrase_out) { ui->setupUi(this); @@ -43,7 +44,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) : switch(mode) { case Encrypt: // Ask passphrase x2 - ui->warningLabel->setText(tr("Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>.")); + ui->warningLabel->setText(tr("Enter the new passphrase for the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>.")); ui->passLabel1->hide(); ui->passEdit1->hide(); setWindowTitle(tr("Encrypt wallet")); @@ -66,7 +67,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) : break; case ChangePass: // Ask old passphrase + new passphrase x2 setWindowTitle(tr("Change passphrase")); - ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase to the wallet.")); + ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase for the wallet.")); break; } textChanged(); @@ -90,7 +91,7 @@ void AskPassphraseDialog::setModel(WalletModel *_model) void AskPassphraseDialog::accept() { SecureString oldpass, newpass1, newpass2; - if(!model) + if (!model && mode != Encrypt) return; oldpass.reserve(MAX_PASSPHRASE_SIZE); newpass1.reserve(MAX_PASSPHRASE_SIZE); @@ -119,24 +120,33 @@ void AskPassphraseDialog::accept() { if(newpass1 == newpass2) { - if(model->setWalletEncrypted(true, newpass1)) - { - QMessageBox::warning(this, tr("Wallet encrypted"), + QString encryption_reminder = tr("Remember that encrypting your wallet cannot fully protect " + "your bitcoins from being stolen by malware infecting your computer."); + if (m_passphrase_out) { + m_passphrase_out->assign(newpass1); + QMessageBox::warning(this, tr("Wallet to be encrypted"), "<qt>" + - tr("Your wallet is now encrypted. " - "Remember that encrypting your wallet cannot fully protect " - "your bitcoins from being stolen by malware infecting your computer.") + - "<br><br><b>" + - tr("IMPORTANT: Any previous backups you have made of your wallet file " - "should be replaced with the newly generated, encrypted wallet file. " - "For security reasons, previous backups of the unencrypted wallet file " - "will become useless as soon as you start using the new, encrypted wallet.") + + tr("Your wallet is about to be encrypted. ") + encryption_reminder + "</b></qt>"); - } - else - { - QMessageBox::critical(this, tr("Wallet encryption failed"), - tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted.")); + } else { + assert(model != nullptr); + if(model->setWalletEncrypted(true, newpass1)) + { + QMessageBox::warning(this, tr("Wallet encrypted"), + "<qt>" + + tr("Your wallet is now encrypted. ") + encryption_reminder + + "<br><br><b>" + + tr("IMPORTANT: Any previous backups you have made of your wallet file " + "should be replaced with the newly generated, encrypted wallet file. " + "For security reasons, previous backups of the unencrypted wallet file " + "will become useless as soon as you start using the new, encrypted wallet.") + + "</b></qt>"); + } + else + { + QMessageBox::critical(this, tr("Wallet encryption failed"), + tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted.")); + } } QDialog::accept(); // Success } diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h index ac31569f63..bdfd3fb9a0 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -7,6 +7,8 @@ #include <QDialog> +#include <support/allocators/secure.h> + class WalletModel; namespace Ui { @@ -27,7 +29,7 @@ public: Decrypt /**< Ask passphrase and decrypt wallet */ }; - explicit AskPassphraseDialog(Mode mode, QWidget *parent); + explicit AskPassphraseDialog(Mode mode, QWidget *parent, SecureString* passphrase_out = nullptr); ~AskPassphraseDialog(); void accept(); @@ -39,6 +41,7 @@ private: Mode mode; WalletModel *model; bool fCapsLock; + SecureString* m_passphrase_out; private Q_SLOTS: void textChanged(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index adc19df935..46f8deee57 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -282,6 +282,10 @@ void BitcoinApplication::parameterSetup() m_node.initParameterInteraction(); } +void BitcoinApplication::SetPrune(bool prune, bool force) { + optionsModel->SetPrune(prune, force); +} + void BitcoinApplication::requestInitialize() { qDebug() << __func__ << ": Requesting initialize"; @@ -487,8 +491,10 @@ int GuiMain(int argc, char* argv[]) /// 5. Now that settings and translations are available, ask user for data directory // User language is set up: pick a data directory - if (!Intro::pickDataDirectory(*node)) - return EXIT_SUCCESS; + bool did_show_intro = false; + bool prune = false; // Intro dialog prune check box + // Gracefully exit if the user cancels + if (!Intro::showIfNeeded(*node, did_show_intro, prune)) return EXIT_SUCCESS; /// 6. Determine availability of data directory and parse bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes @@ -511,7 +517,7 @@ int GuiMain(int argc, char* argv[]) // - QSettings() will use the new application name after this, resulting in network-specific settings // - Needs to be done before createOptionsModel - // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) + // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause) try { node->selectParams(gArgs.GetChainName()); } catch(std::exception &e) { @@ -524,7 +530,7 @@ int GuiMain(int argc, char* argv[]) PaymentServer::ipcParseCommandLine(*node, argc, argv); #endif - QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString()))); + QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(Params().NetworkIDString())); assert(!networkStyle.isNull()); // Allow for separate UI settings for testnets QApplication::setApplicationName(networkStyle->getAppName()); @@ -562,6 +568,11 @@ int GuiMain(int argc, char* argv[]) // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); + if (did_show_intro) { + // Store intro dialog settings other than datadir (network specific) + app.SetPrune(prune, true); + } + if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 3869193a3a..8c77fd8a7d 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -67,6 +67,8 @@ public: void parameterSetup(); /// Create options model void createOptionsModel(bool resetSettings); + /// Update prune value + void SetPrune(bool prune, bool force = false); /// Create main window void createWindow(const NetworkStyle *networkStyle); /// Create splash screen diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index 5854ade655..9fa49b87fa 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -6,6 +6,7 @@ #include <qt/bitcoinunits.h> #include <qt/guiconstants.h> +#include <qt/guiutil.h> #include <qt/qvaluecombobox.h> #include <QApplication> @@ -121,7 +122,7 @@ public: const QFontMetrics fm(fontMetrics()); int h = lineEdit()->minimumSizeHint().height(); - int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways)); + int w = GUIUtil::TextWidth(fm, BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways)); w += 2; // cursor blinking space QStyleOptionSpinBox opt; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index bc9af9793e..7671fde705 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -6,6 +6,7 @@ #include <qt/bitcoinunits.h> #include <qt/clientmodel.h> +#include <qt/createwalletdialog.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/modaloverlay.h> @@ -40,7 +41,6 @@ #include <QApplication> #include <QComboBox> #include <QDateTime> -#include <QDesktopWidget> #include <QDragEnterEvent> #include <QListWidget> #include <QMenu> @@ -48,6 +48,7 @@ #include <QMessageBox> #include <QMimeData> #include <QProgressDialog> +#include <QScreen> #include <QSettings> #include <QShortcut> #include <QStackedWidget> @@ -81,7 +82,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window - move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); + move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } #ifdef ENABLE_WALLET @@ -339,6 +340,9 @@ void BitcoinGUI::createActions() m_close_wallet_action = new QAction(tr("Close Wallet..."), this); m_close_wallet_action->setStatusTip(tr("Close wallet")); + m_create_wallet_action = new QAction(tr("Create Wallet..."), this); + m_create_wallet_action->setStatusTip(tr("Create a new wallet")); + showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Bitcoin command-line options").arg(PACKAGE_NAME)); @@ -371,6 +375,10 @@ void BitcoinGUI::createActions() for (const std::pair<const std::string, bool>& i : m_wallet_controller->listWalletDir()) { const std::string& path = i.first; QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + // Menu items remove single &. Single & are shown when && is in + // the string, but only the first occurrence. So replace only + // the first & with &&. + name.replace(name.indexOf(QChar('&')), 1, QString("&&")); QAction* action = m_open_wallet_menu->addAction(name); if (i.second) { @@ -379,31 +387,11 @@ void BitcoinGUI::createActions() continue; } - connect(action, &QAction::triggered, [this, name, path] { - OpenWalletActivity* activity = m_wallet_controller->openWallet(path); - - QProgressDialog* dialog = new QProgressDialog(this); - dialog->setLabelText(tr("Opening Wallet <b>%1</b>...").arg(name.toHtmlEscaped())); - dialog->setRange(0, 0); - dialog->setCancelButton(nullptr); - dialog->setWindowModality(Qt::ApplicationModal); - dialog->show(); - - connect(activity, &OpenWalletActivity::message, this, [this] (QMessageBox::Icon icon, QString text) { - QMessageBox box; - box.setIcon(icon); - box.setText(tr("Open Wallet Failed")); - box.setInformativeText(text); - box.setStandardButtons(QMessageBox::Ok); - box.setDefaultButton(QMessageBox::Ok); - connect(this, &QObject::destroyed, &box, &QDialog::accept); - box.exec(); - }); + connect(action, &QAction::triggered, [this, path] { + auto activity = new OpenWalletActivity(m_wallet_controller, this); connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet); connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater); - connect(activity, &OpenWalletActivity::finished, dialog, &QObject::deleteLater); - bool invoked = QMetaObject::invokeMethod(activity, "open"); - assert(invoked); + activity->open(path); }); } if (m_open_wallet_menu->isEmpty()) { @@ -414,6 +402,12 @@ void BitcoinGUI::createActions() connect(m_close_wallet_action, &QAction::triggered, [this] { m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); }); + connect(m_create_wallet_action, &QAction::triggered, [this] { + auto activity = new CreateWalletActivity(m_wallet_controller, this); + connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet); + connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); + activity->create(); + }); } #endif // ENABLE_WALLET @@ -435,6 +429,7 @@ void BitcoinGUI::createMenuBar() QMenu *file = appMenuBar->addMenu(tr("&File")); if(walletFrame) { + file->addAction(m_create_wallet_action); file->addAction(m_open_wallet_action); file->addAction(m_close_wallet_action); file->addSeparator(); @@ -480,24 +475,16 @@ void BitcoinGUI::createMenuBar() connect(qApp, &QApplication::focusWindowChanged, [zoom_action] (QWindow* window) { zoom_action->setEnabled(window != nullptr); }); -#else - QAction* restore_action = window_menu->addAction(tr("Restore")); - connect(restore_action, &QAction::triggered, [] { - qApp->focusWindow()->showNormal(); - }); - - connect(qApp, &QApplication::focusWindowChanged, [restore_action] (QWindow* window) { - restore_action->setEnabled(window != nullptr); - }); #endif if (walletFrame) { +#ifdef Q_OS_MAC window_menu->addSeparator(); QAction* main_window_action = window_menu->addAction(tr("Main Window")); connect(main_window_action, &QAction::triggered, [this] { GUIUtil::bringToFront(this); }); - +#endif window_menu->addSeparator(); window_menu->addAction(usedSendingAddressesAction); window_menu->addAction(usedReceivingAddressesAction); @@ -1407,7 +1394,7 @@ UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *pl const QFontMetrics fm(font()); for (const BitcoinUnits::Unit unit : units) { - max_width = qMax(max_width, fm.width(BitcoinUnits::longName(unit))); + max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::longName(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 46ced79007..809cf8b4ed 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -147,6 +147,7 @@ private: QAction* openRPCConsoleAction = nullptr; QAction* openAction = nullptr; QAction* showHelpMessageAction = nullptr; + QAction* m_create_wallet_action{nullptr}; QAction* m_open_wallet_action{nullptr}; QMenu* m_open_wallet_menu{nullptr}; QAction* m_close_wallet_action{nullptr}; diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index 87736cd185..5cde21eec6 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -131,12 +131,12 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Initialization sanity check failed. %s is shu QT_TRANSLATE_NOOP("bitcoin-core", "Insufficient funds"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -onion address or hostname: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -proxy address or hostname: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid P2P permission: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -%s=<amount>: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -discardfee=<amount>: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -fallbackfee=<amount>: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid netmask specified in -whitelist: '%s'"), -QT_TRANSLATE_NOOP("bitcoin-core", "Keypool ran out, please call keypoolrefill first"), QT_TRANSLATE_NOOP("bitcoin-core", "Loading P2P addresses..."), QT_TRANSLATE_NOOP("bitcoin-core", "Loading banlist..."), QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index..."), @@ -170,7 +170,6 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Transaction amounts must not be negative"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction fee and change calculation failed"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction has too long of a mempool chain"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction must have at least one recipient"), -QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large for fee policy"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer (bind returned error %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer. %s is probably already running."), diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp new file mode 100644 index 0000000000..8e6474b0d4 --- /dev/null +++ b/src/qt/createwalletdialog.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <qt/createwalletdialog.h> +#include <qt/forms/ui_createwalletdialog.h> + +#include <QPushButton> + +CreateWalletDialog::CreateWalletDialog(QWidget* parent) : + QDialog(parent), + ui(new Ui::CreateWalletDialog) +{ + ui->setupUi(this); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->wallet_name_line_edit->setFocus(Qt::ActiveWindowFocusReason); + + connect(ui->wallet_name_line_edit, &QLineEdit::textEdited, [this](const QString& text) { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); + }); + + connect(ui->encrypt_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) { + // Disable the disable_privkeys_checkbox when isEncryptWalletChecked is + // set to true, enable it when isEncryptWalletChecked is false. + ui->disable_privkeys_checkbox->setEnabled(!checked); + + // When the disable_privkeys_checkbox is disabled, uncheck it. + if (!ui->disable_privkeys_checkbox->isEnabled()) { + ui->disable_privkeys_checkbox->setChecked(false); + } + }); +} + +CreateWalletDialog::~CreateWalletDialog() +{ + delete ui; +} + +QString CreateWalletDialog::walletName() const +{ + return ui->wallet_name_line_edit->text(); +} + +bool CreateWalletDialog::isEncryptWalletChecked() const +{ + return ui->encrypt_wallet_checkbox->isChecked(); +} + +bool CreateWalletDialog::isDisablePrivateKeysChecked() const +{ + return ui->disable_privkeys_checkbox->isChecked(); +} + +bool CreateWalletDialog::isMakeBlankWalletChecked() const +{ + return ui->blank_wallet_checkbox->isChecked(); +} diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h new file mode 100644 index 0000000000..30766107b9 --- /dev/null +++ b/src/qt/createwalletdialog.h @@ -0,0 +1,35 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_CREATEWALLETDIALOG_H +#define BITCOIN_QT_CREATEWALLETDIALOG_H + +#include <QDialog> + +class WalletModel; + +namespace Ui { + class CreateWalletDialog; +} + +/** Dialog for creating wallets + */ +class CreateWalletDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CreateWalletDialog(QWidget* parent); + virtual ~CreateWalletDialog(); + + QString walletName() const; + bool isEncryptWalletChecked() const; + bool isDisablePrivateKeysChecked() const; + bool isMakeBlankWalletChecked() const; + +private: + Ui::CreateWalletDialog *ui; +}; + +#endif // BITCOIN_QT_CREATEWALLETDIALOG_H diff --git a/src/qt/forms/askpassphrasedialog.ui b/src/qt/forms/askpassphrasedialog.ui index 69803989cd..e74d183818 100644 --- a/src/qt/forms/askpassphrasedialog.ui +++ b/src/qt/forms/askpassphrasedialog.ui @@ -95,7 +95,7 @@ <item row="3" column="1"> <widget class="QCheckBox" name="toggleShowPasswordButton"> <property name="text"> - <string>Show password</string> + <string>Show passphrase</string> </property> </widget> </item> diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui new file mode 100644 index 0000000000..e49bab8f3b --- /dev/null +++ b/src/qt/forms/createwalletdialog.ui @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CreateWalletDialog</class> + <widget class="QDialog" name="CreateWalletDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>364</width> + <height>185</height> + </rect> + </property> + <property name="windowTitle"> + <string>Create Wallet</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>10</x> + <y>140</y> + <width>341</width> + <height>32</height> + </rect> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + <widget class="QLineEdit" name="wallet_name_line_edit"> + <property name="geometry"> + <rect> + <x>120</x> + <y>20</y> + <width>231</width> + <height>24</height> + </rect> + </property> + </widget> + <widget class="QLabel" name="label"> + <property name="geometry"> + <rect> + <x>20</x> + <y>20</y> + <width>101</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Wallet Name</string> + </property> + </widget> + <widget class="QCheckBox" name="encrypt_wallet_checkbox"> + <property name="geometry"> + <rect> + <x>20</x> + <y>50</y> + <width>171</width> + <height>22</height> + </rect> + </property> + <property name="toolTip"> + <string>Encrypt the wallet. The wallet will be encrypted with a passphrase of your choice.</string> + </property> + <property name="text"> + <string>Encrypt Wallet</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox" name="disable_privkeys_checkbox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="geometry"> + <rect> + <x>20</x> + <y>80</y> + <width>171</width> + <height>22</height> + </rect> + </property> + <property name="toolTip"> + <string>Disable private keys for this wallet. Wallets with private keys disabled will have no private keys and cannot have an HD seed or imported private keys. This is ideal for watch-only wallets.</string> + </property> + <property name="text"> + <string>Disable Private Keys</string> + </property> + </widget> + <widget class="QCheckBox" name="blank_wallet_checkbox"> + <property name="geometry"> + <rect> + <x>20</x> + <y>110</y> + <width>171</width> + <height>22</height> + </rect> + </property> + <property name="toolTip"> + <string>Make a blank wallet. Blank wallets do not initially have private keys or scripts. Private keys and addresses can be imported, or an HD seed can be set, at a later time.</string> + </property> + <property name="text"> + <string>Make Blank Wallet</string> + </property> + </widget> + </widget> + <tabstops> + <tabstop>wallet_name_line_edit</tabstop> + <tabstop>encrypt_wallet_checkbox</tabstop> + <tabstop>disable_privkeys_checkbox</tabstop> + <tabstop>blank_wallet_checkbox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CreateWalletDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CreateWalletDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 6e52c5e477..be807b20c0 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -15,6 +15,25 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> + <widget class="QLabel" name="label_alerts"> + <property name="visible"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true">QLabel { background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; }</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="margin"> + <number>3</number> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> <number>0</number> diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui index cfdd8482e3..f27a4ebe44 100644 --- a/src/qt/forms/intro.ui +++ b/src/qt/forms/intro.ui @@ -211,6 +211,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="prune"> + <property name="toolTip"> + <string>Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</string> + </property> + <property name="text"> + <string></string> + </property> + </widget> + </item> + <item> <widget class="QLabel" name="lblExplanation2"> <property name="text"> <string>This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</string> diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index d8f5594983..dcdb247977 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_QT_GUICONSTANTS_H #define BITCOIN_QT_GUICONSTANTS_H +#include <cstdint> + /* Milliseconds between model updates */ static const int MODEL_UPDATE_DELAY = 250; diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index dc1da7f8a9..c4e0321f28 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -39,7 +39,6 @@ #include <QClipboard> #include <QDateTime> #include <QDesktopServices> -#include <QDesktopWidget> #include <QDoubleValidator> #include <QFileDialog> #include <QFont> @@ -58,9 +57,10 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include <objc/objc-runtime.h> #include <CoreServices/CoreServices.h> #include <QProcess> + +void ForceActivation(); #endif namespace GUIUtil { @@ -360,10 +360,7 @@ bool isObscured(QWidget *w) void bringToFront(QWidget* w) { #ifdef Q_OS_MAC - // Force application activation on macOS. With Qt 5.4 this is required when - // an action in the dock menu is triggered. - id app = objc_msgSend((id) objc_getClass("NSApplication"), sel_registerName("sharedApplication")); - objc_msgSend(app, sel_registerName("activateIgnoringOtherApps:"), YES); + ForceActivation(); #endif if (w) { @@ -591,7 +588,7 @@ bool SetStartOnSystemStartup(bool fAutoStart) // Start client minimized QString strArgs = "-min"; // Set -testnet /-regtest options - strArgs += QString::fromStdString(strprintf(" -testnet=%d -regtest=%d", gArgs.GetBoolArg("-testnet", false), gArgs.GetBoolArg("-regtest", false))); + strArgs += QString::fromStdString(strprintf(" -chain=%s", gArgs.GetChainName())); // Set the path to the shortcut target psl->SetPath(pszExePath); @@ -686,7 +683,7 @@ bool SetStartOnSystemStartup(bool fAutoStart) optionFile << "Name=Bitcoin\n"; else optionFile << strprintf("Name=Bitcoin (%s)\n", chain); - optionFile << "Exec=" << pszExePath << strprintf(" -min -testnet=%d -regtest=%d\n", gArgs.GetBoolArg("-testnet", false), gArgs.GetBoolArg("-regtest", false)); + optionFile << "Exec=" << pszExePath << strprintf(" -min -chain=%s\n", chain); optionFile << "Terminal=false\n"; optionFile << "Hidden=false\n"; optionFile.close(); @@ -916,7 +913,7 @@ qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal m while(font_size >= minPointSize) { font.setPointSizeF(font_size); QFontMetrics fm(font); - if (fm.width(text) < width) { + if (TextWidth(fm, text) < width) { break; } font_size -= 0.5; @@ -948,7 +945,7 @@ void PolishProgressDialog(QProgressDialog* dialog) { #ifdef Q_OS_MAC // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. - const int margin = dialog->fontMetrics().width("X"); + const int margin = TextWidth(dialog->fontMetrics(), ("X")); dialog->resize(dialog->width() + 2 * margin, dialog->height()); dialog->show(); #else @@ -956,4 +953,13 @@ void PolishProgressDialog(QProgressDialog* dialog) #endif } +int TextWidth(const QFontMetrics& fm, const QString& text) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + return fm.horizontalAdvance(text); +#else + return fm.width(text); +#endif +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index bea4a83494..9db92f94d7 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -257,6 +257,14 @@ namespace GUIUtil // Fix known bugs in QProgressDialog class. void PolishProgressDialog(QProgressDialog* dialog); + + /** + * Returns the distance in pixels appropriate for drawing a subsequent character after text. + * + * In Qt 5.12 and before the QFontMetrics::width() is used and it is deprecated since Qt 13.0. + * In Qt 5.11 the QFontMetrics::horizontalAdvance() was introduced. + */ + int TextWidth(const QFontMetrics& fm, const QString& text); } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 102e37e471..9e05c63aa0 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -131,6 +131,11 @@ Intro::Intro(QWidget *parent, uint64_t blockchain_size, uint64_t chain_state_siz ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(PACKAGE_NAME)); uint64_t pruneTarget = std::max<int64_t>(0, gArgs.GetArg("-prune", 0)); + if (pruneTarget > 1) { // -prune=1 means enabled, above that it's a size in MB + ui->prune->setChecked(true); + ui->prune->setEnabled(false); + } + ui->prune->setText(tr("Discard blocks after verification, except most recent %1 GB (prune)").arg(pruneTarget ? pruneTarget / 1000 : 2)); requiredSpace = m_blockchain_size; QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time."); if (pruneTarget) { @@ -180,8 +185,10 @@ void Intro::setDataDirectory(const QString &dataDir) } } -bool Intro::pickDataDirectory(interfaces::Node& node) +bool Intro::showIfNeeded(interfaces::Node& node, bool& did_show_intro, bool& prune) { + did_show_intro = false; + QSettings settings; /* If data directory provided on command line, no need to look at settings or show a picking dialog */ @@ -205,6 +212,7 @@ bool Intro::pickDataDirectory(interfaces::Node& node) Intro intro(0, node.getAssumedBlockchainSize(), node.getAssumedChainStateSize()); intro.setDataDirectory(dataDir); intro.setWindowIcon(QIcon(":icons/bitcoin")); + did_show_intro = true; while(true) { @@ -227,6 +235,9 @@ bool Intro::pickDataDirectory(interfaces::Node& node) } } + // Additional preferences: + prune = intro.ui->prune->isChecked(); + settings.setValue("strDataDir", dataDir); settings.setValue("fReset", false); } @@ -263,6 +274,11 @@ void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable { freeString += " " + tr("(of %n GB needed)", "", requiredSpace); ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); + ui->prune->setChecked(true); + } else if (bytesAvailable / GB_BYTES - requiredSpace < 10) { + freeString += " " + tr("(%n GB needed for full chain)", "", requiredSpace); + ui->freeSpace->setStyleSheet("QLabel { color: #999900 }"); + ui->prune->setChecked(true); } else { ui->freeSpace->setStyleSheet(""); } diff --git a/src/qt/intro.h b/src/qt/intro.h index c3b26808d4..aca7e71642 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -39,6 +39,7 @@ public: /** * Determine data directory. Let the user choose if the current one doesn't exist. + * Let the user configure additional preferences such as pruning. * * @returns true if a data directory was selected, false if the user cancelled the selection * dialog. @@ -46,7 +47,7 @@ public: * @note do NOT call global GetDataDir() before calling this function, this * will cause the wrong path to be cached. */ - static bool pickDataDirectory(interfaces::Node& node); + static bool showIfNeeded(interfaces::Node& node, bool& did_show_intro, bool& prune); Q_SIGNALS: void requestCheck(); diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index bff7469071..7864f97f31 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -132,7 +132,7 @@ <context> <name>AddressTableModel</name> <message> - <location filename="../addresstablemodel.cpp" line="+163"/> + <location filename="../addresstablemodel.cpp" line="+165"/> <source>Label</source> <translation type="unfinished"></translation> </message> @@ -297,7 +297,7 @@ <context> <name>BanTableModel</name> <message> - <location filename="../bantablemodel.cpp" line="+86"/> + <location filename="../bantablemodel.cpp" line="+88"/> <source>IP/Netmask</source> <translation type="unfinished"></translation> </message> @@ -310,17 +310,17 @@ <context> <name>BitcoinGUI</name> <message> - <location filename="../bitcoingui.cpp" line="+318"/> + <location filename="../bitcoingui.cpp" line="+315"/> <source>Sign &message...</source> <translation>Sign &message...</translation> </message> <message> - <location line="+638"/> + <location line="+637"/> <source>Synchronizing with network...</source> <translation>Synchronizing with network...</translation> </message> <message> - <location line="-716"/> + <location line="-715"/> <source>&Overview</source> <translation>&Overview</translation> </message> @@ -400,7 +400,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+217"/> + <location line="+216"/> <source>Wallet:</source> <translation type="unfinished"></translation> </message> @@ -435,7 +435,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="-1036"/> + <location line="-1035"/> <source>Send coins to a Bitcoin address</source> <translation>Send coins to a Bitcoin address</translation> </message> @@ -500,7 +500,7 @@ <translation>Verify messages to ensure they were signed with specified Bitcoin addresses</translation> </message> <message> - <location line="+118"/> + <location line="+117"/> <source>&File</source> <translation>&File</translation> </message> @@ -520,7 +520,7 @@ <translation>Tabs toolbar</translation> </message> <message> - <location line="-271"/> + <location line="-270"/> <source>Request payments (generates QR codes and bitcoin: URIs)</source> <translation type="unfinished"></translation> </message> @@ -545,7 +545,7 @@ <translation type="unfinished"></translation> </message> <message numerus="yes"> - <location line="+540"/> + <location line="+539"/> <source>%n active connection(s) to Bitcoin network</source> <translation> <numerusform>%n active connection to Bitcoin network</numerusform> @@ -606,7 +606,7 @@ <translation>Up to date</translation> </message> <message> - <location line="-657"/> + <location line="-656"/> <source>&Sending addresses</source> <translation type="unfinished"></translation> </message> @@ -641,7 +641,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+30"/> + <location line="+29"/> <source>default wallet</source> <translation type="unfinished"></translation> </message> @@ -782,7 +782,7 @@ <translation>Wallet is <b>encrypted</b> and currently <b>locked</b></translation> </message> <message> - <location filename="../bitcoin.cpp" line="+390"/> + <location filename="../bitcoin.cpp" line="+382"/> <source>A fatal error occurred. Bitcoin can no longer continue safely and will quit.</source> <translation type="unfinished"></translation> </message> @@ -941,7 +941,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+155"/> + <location line="+157"/> <source>yes</source> <translation type="unfinished"></translation> </message> @@ -1736,14 +1736,14 @@ <location filename="../paymentserver.cpp" line="+226"/> <location line="+346"/> <location line="+42"/> - <location line="+110"/> + <location line="+108"/> <location line="+14"/> <location line="+18"/> <source>Payment request error</source> <translation type="unfinished"></translation> </message> <message> - <location line="-529"/> + <location line="-527"/> <source>Cannot start bitcoin: click-to-pay handler</source> <translation type="unfinished"></translation> </message> @@ -1805,12 +1805,12 @@ <location line="+31"/> <location line="+10"/> <location line="+17"/> - <location line="+85"/> + <location line="+83"/> <source>Payment request rejected</source> <translation type="unfinished"></translation> </message> <message> - <location line="-152"/> + <location line="-150"/> <source>Payment request network doesn't match client network.</source> <translation type="unfinished"></translation> </message> @@ -1841,7 +1841,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+65"/> + <location line="+63"/> <source>Refund from %1</source> <translation type="unfinished"></translation> </message> @@ -1879,7 +1879,7 @@ <context> <name>PeerTableModel</name> <message> - <location filename="../peertablemodel.cpp" line="+108"/> + <location filename="../peertablemodel.cpp" line="+110"/> <source>User Agent</source> <translation type="unfinished"></translation> </message> @@ -1922,7 +1922,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+702"/> + <location line="+699"/> <source>%1 d</source> <translation type="unfinished"></translation> </message> @@ -1938,7 +1938,7 @@ </message> <message> <location line="+2"/> - <location line="+50"/> + <location line="+47"/> <source>%1 s</source> <translation type="unfinished"></translation> </message> @@ -2032,27 +2032,22 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../bitcoin.cpp" line="+74"/> - <source>Error parsing command line arguments: %1.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+37"/> + <location filename="../bitcoin.cpp" line="+116"/> <source>Error: Specified data directory "%1" does not exist.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+5"/> + <location line="+6"/> <source>Error: Cannot parse configuration file: %1.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+14"/> + <location line="+15"/> <source>Error: %1</source> <translation type="unfinished"></translation> </message> <message> - <location line="+57"/> + <location line="+59"/> <source>%1 didn't yet exit safely...</source> <translation type="unfinished"></translation> </message> @@ -2103,7 +2098,7 @@ <context> <name>RPCConsole</name> <message> - <location filename="../forms/debugwindow.ui" line="+56"/> + <location filename="../forms/debugwindow.ui" line="+75"/> <location line="+26"/> <location line="+26"/> <location line="+26"/> @@ -2147,12 +2142,12 @@ <translation>&Information</translation> </message> <message> - <location line="-10"/> + <location line="-29"/> <source>Debug window</source> <translation type="unfinished"></translation> </message> <message> - <location line="+25"/> + <location line="+44"/> <source>General</source> <translation type="unfinished"></translation> </message> @@ -2265,8 +2260,8 @@ </message> <message> <location line="+65"/> - <location filename="../rpcconsole.cpp" line="+498"/> - <location line="+757"/> + <location filename="../rpcconsole.cpp" line="+497"/> + <location line="+759"/> <source>Select a peer to view detailed information.</source> <translation type="unfinished"></translation> </message> @@ -2417,7 +2412,7 @@ <translation>Clear console</translation> </message> <message> - <location filename="../rpcconsole.cpp" line="-252"/> + <location filename="../rpcconsole.cpp" line="-243"/> <source>1 &hour</source> <translation type="unfinished"></translation> </message> @@ -2450,7 +2445,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+47"/> + <location line="+38"/> <source>&Unban</source> <translation type="unfinished"></translation> </message> @@ -2628,7 +2623,7 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../receivecoinsdialog.cpp" line="+45"/> + <location filename="../receivecoinsdialog.cpp" line="+46"/> <source>Copy URI</source> <translation type="unfinished"></translation> </message> @@ -2714,7 +2709,7 @@ <context> <name>RecentRequestsTableModel</name> <message> - <location filename="../recentrequeststablemodel.cpp" line="+25"/> + <location filename="../recentrequeststablemodel.cpp" line="+27"/> <source>Date</source> <translation type="unfinished">Date</translation> </message> @@ -2753,7 +2748,7 @@ <name>SendCoinsDialog</name> <message> <location filename="../forms/sendcoinsdialog.ui" line="+14"/> - <location filename="../sendcoinsdialog.cpp" line="+600"/> + <location filename="../sendcoinsdialog.cpp" line="+601"/> <source>Send Coins</source> <translation>Send Coins</translation> </message> @@ -2940,7 +2935,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation>S&end</translation> </message> <message> - <location filename="../sendcoinsdialog.cpp" line="-512"/> + <location filename="../sendcoinsdialog.cpp" line="-513"/> <source>Copy quantity</source> <translation type="unfinished"></translation> </message> @@ -2980,7 +2975,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+117"/> + <location line="+118"/> <source> from wallet '%1'</source> <translation type="unfinished"></translation> </message> @@ -3698,7 +3693,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>TransactionTableModel</name> <message> - <location filename="../transactiontablemodel.cpp" line="+223"/> + <location filename="../transactiontablemodel.cpp" line="+225"/> <source>Date</source> <translation type="unfinished">Date</translation> </message> @@ -3834,7 +3829,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>TransactionView</name> <message> - <location filename="../transactionview.cpp" line="+70"/> + <location filename="../transactionview.cpp" line="+69"/> <location line="+16"/> <source>All</source> <translation type="unfinished"></translation> @@ -3955,7 +3950,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+199"/> + <location line="+194"/> <source>Export Transaction History</source> <translation type="unfinished"></translation> </message> @@ -4033,7 +4028,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>UnitDisplayStatusBarControl</name> <message> - <location filename="../bitcoingui.cpp" line="+155"/> + <location filename="../bitcoingui.cpp" line="+156"/> <source>Unit to show amounts in. Click to select another unit.</source> <translation type="unfinished"></translation> </message> @@ -4041,7 +4036,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>WalletController</name> <message> - <location filename="../walletcontroller.cpp" line="+70"/> + <location filename="../walletcontroller.cpp" line="+73"/> <source>Close wallet</source> <translation type="unfinished"></translation> </message> @@ -4072,7 +4067,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished">Send Coins</translation> </message> <message> - <location line="+301"/> + <location line="+309"/> <location line="+39"/> <location line="+5"/> <source>Fee bump error</source> @@ -4205,12 +4200,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+31"/> + <location line="+30"/> <source>Unable to start HTTP server. See debug log for details.</source> <translation type="unfinished"></translation> </message> <message> - <location line="-168"/> + <location line="-167"/> <source>The %s developers</source> <translation type="unfinished"></translation> </message> @@ -4391,6 +4386,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos </message> <message> <location line="+4"/> + <source>Invalid P2P permission: '%s'</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Invalid amount for -%s=<amount>: '%s'</source> <translation type="unfinished"></translation> </message> @@ -4405,17 +4405,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+23"/> + <location line="+22"/> <source>Specified blocks directory "%s" does not exist.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+26"/> + <location line="+25"/> <source>Upgrading txindex database</source> <translation type="unfinished"></translation> </message> <message> - <location line="-45"/> + <location line="-44"/> <source>Loading P2P addresses...</source> <translation type="unfinished"></translation> </message> @@ -4465,7 +4465,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> + <location line="+5"/> <source>Unable to bind to %s on this computer. %s is probably already running.</source> <translation type="unfinished"></translation> </message> @@ -4500,7 +4500,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="-155"/> + <location line="-154"/> <source>Error: Listening for incoming connections failed (listen returned error %s)</source> <translation type="unfinished"></translation> </message> @@ -4545,7 +4545,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+4"/> + <location line="+5"/> <source>Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)</source> <translation type="unfinished"></translation> </message> @@ -4555,7 +4555,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> + <location line="+5"/> <source>Need to specify a port with -whitebind: '%s'</source> <translation type="unfinished"></translation> </message> @@ -4617,11 +4617,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos </message> <message> <location line="+5"/> - <source>Transaction too large for fee policy</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+1"/> <source>Transaction too large</source> <translation>Transaction too large</translation> </message> @@ -4661,7 +4656,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="-178"/> + <location line="-177"/> <source>-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source> <translation type="unfinished"></translation> </message> @@ -4696,12 +4691,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+20"/> - <source>Keypool ran out, please call keypoolrefill first</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+21"/> + <location line="+41"/> <source>Starting network threads...</source> <translation type="unfinished"></translation> </message> @@ -4736,12 +4726,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> + <location line="+9"/> <source>Unknown network specified in -onlynet: '%s'</source> <translation>Unknown network specified in -onlynet: '%s'</translation> </message> <message> - <location line="-51"/> + <location line="-50"/> <source>Insufficient funds</source> <translation>Insufficient funds</translation> </message> diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index 102adce6c5..5eb23c76e6 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -1,12 +1,11 @@ -// Copyright (c) 2011-2018 The Bitcoin Core developers +// Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "macdockiconhandler.h" -#undef slots -#include <objc/objc.h> -#include <objc/message.h> +#include <AppKit/AppKit.h> +#include <objc/runtime.h> static MacDockIconHandler *s_instance = nullptr; @@ -21,9 +20,7 @@ bool dockClickHandler(id self, SEL _cmd, ...) { } void setupDockClickHandler() { - id app = objc_msgSend((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); - id delegate = objc_msgSend(app, sel_registerName("delegate")); - Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); + Class delClass = (Class)[[[NSApplication sharedApplication] delegate] class]; SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); } @@ -44,3 +41,13 @@ void MacDockIconHandler::cleanup() { delete s_instance; } + +/** + * Force application activation on macOS. With Qt 5.5.1 this is required when + * an action in the Dock menu is triggered. + * TODO: Define a Qt version where it's no-longer necessary. + */ +void ForceActivation() +{ + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; +} diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index f0c860e669..5c039a939e 100644 --- a/src/qt/networkstyle.cpp +++ b/src/qt/networkstyle.cpp @@ -6,6 +6,9 @@ #include <qt/guiconstants.h> +#include <chainparamsbase.h> +#include <tinyformat.h> + #include <QApplication> static const struct { @@ -13,11 +16,10 @@ static const struct { const char *appName; const int iconColorHueShift; const int iconColorSaturationReduction; - const char *titleAddText; } network_styles[] = { - {"main", QAPP_APP_NAME_DEFAULT, 0, 0, ""}, - {"test", QAPP_APP_NAME_TESTNET, 70, 30, QT_TRANSLATE_NOOP("SplashScreen", "[testnet]")}, - {"regtest", QAPP_APP_NAME_REGTEST, 160, 30, "[regtest]"} + {"main", QAPP_APP_NAME_DEFAULT, 0, 0}, + {"test", QAPP_APP_NAME_TESTNET, 70, 30}, + {"regtest", QAPP_APP_NAME_REGTEST, 160, 30} }; static const unsigned network_styles_count = sizeof(network_styles)/sizeof(*network_styles); @@ -75,8 +77,9 @@ NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift, trayAndWindowIcon = QIcon(pixmap.scaled(QSize(256,256))); } -const NetworkStyle *NetworkStyle::instantiate(const QString &networkId) +const NetworkStyle* NetworkStyle::instantiate(const std::string& networkId) { + std::string titleAddText = networkId == CBaseChainParams::MAIN ? "" : strprintf("[%s]", networkId); for (unsigned x=0; x<network_styles_count; ++x) { if (networkId == network_styles[x].networkId) @@ -85,7 +88,7 @@ const NetworkStyle *NetworkStyle::instantiate(const QString &networkId) network_styles[x].appName, network_styles[x].iconColorHueShift, network_styles[x].iconColorSaturationReduction, - network_styles[x].titleAddText); + titleAddText.c_str()); } } return nullptr; diff --git a/src/qt/networkstyle.h b/src/qt/networkstyle.h index b78a9f5948..bb12dd1b6e 100644 --- a/src/qt/networkstyle.h +++ b/src/qt/networkstyle.h @@ -14,7 +14,7 @@ class NetworkStyle { public: /** Get style associated with provided BIP70 network id, or 0 if not known */ - static const NetworkStyle *instantiate(const QString &networkId); + static const NetworkStyle* instantiate(const std::string& networkId); const QString &getAppName() const { return appName; } const QIcon &getAppIcon() const { return appIcon; } diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index f3974b1c85..d047a82475 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -92,11 +92,7 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("bPrune", false); if (!settings.contains("nPruneSize")) settings.setValue("nPruneSize", 2); - // Convert prune size from GB to MiB: - const uint64_t nPruneSizeMiB = (settings.value("nPruneSize").toInt() * GB_BYTES) >> 20; - if (!m_node.softSetArg("-prune", settings.value("bPrune").toBool() ? std::to_string(nPruneSizeMiB) : "0")) { - addOverriddenOption("-prune"); - } + SetPrune(settings.value("bPrune").toBool()); if (!settings.contains("nDatabaseCache")) settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); @@ -240,6 +236,22 @@ static const QString GetDefaultProxyAddress() return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); } +void OptionsModel::SetPrune(bool prune, bool force) +{ + QSettings settings; + settings.setValue("bPrune", prune); + // Convert prune size from GB to MiB: + const uint64_t nPruneSizeMiB = (settings.value("nPruneSize").toInt() * GB_BYTES) >> 20; + std::string prune_val = prune ? std::to_string(nPruneSizeMiB) : "0"; + if (force) { + m_node.forceSetArg("-prune", prune_val); + return; + } + if (!m_node.softSetArg("-prune", prune_val)) { + addOverriddenOption("-prune"); + } +} + // read QSettings values and return them QVariant OptionsModel::data(const QModelIndex & index, int role) const { diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 1af3a72b92..b1231b7c7d 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -77,6 +77,9 @@ public: bool getCoinControlFeatures() const { return fCoinControlFeatures; } const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; } + /* Explicit setters */ + void SetPrune(bool prune, bool force = false); + /* Restart flag helper */ void setRestartRequired(bool fRequired); bool isRestartRequired() const; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index d8e48f350a..07ffff0126 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -204,9 +204,8 @@ void OverviewPage::updateWatchOnlyLabels(bool showWatchOnly) void OverviewPage::setClientModel(ClientModel *model) { this->clientModel = model; - if(model) - { - // Show warning if this is a prerelease version + if (model) { + // Show warning, for example if this is a prerelease version connect(model, &ClientModel::alertsChanged, this, &OverviewPage::updateAlerts); updateAlerts(model->getStatusBarWarnings()); } diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index f3f5d28af9..00d83d23dd 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -41,8 +41,8 @@ #include <QNetworkReply> #include <QNetworkRequest> #include <QSslCertificate> +#include <QSslConfiguration> #include <QSslError> -#include <QSslSocket> #include <QStringList> #include <QTextDocument> #include <QUrlQuery> @@ -328,7 +328,9 @@ void PaymentServer::handleURIOrFile(const QString& s) #ifndef ENABLE_BIP70 if (uri.hasQueryItem("r")) { // payment request Q_EMIT message(tr("URI handling"), - tr("Cannot process payment request because BIP70 support was not compiled in."), + tr("Cannot process payment request because BIP70 support was not compiled in.")+ + tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+ + tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), CClientUIInterface::ICON_WARNING); } #endif @@ -364,7 +366,9 @@ void PaymentServer::handleURIOrFile(const QString& s) return; #else Q_EMIT message(tr("Payment request file handling"), - tr("Cannot process payment request because BIP70 support was not compiled in."), + tr("Cannot process payment request because BIP70 support was not compiled in.")+ + tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+ + tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), CClientUIInterface::ICON_WARNING); #endif } @@ -448,9 +452,9 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) certList = QSslCertificate::fromPath(certFile); // Use those certificates when fetching payment requests, too: - QSslSocket::setDefaultCaCertificates(certList); + QSslConfiguration::defaultConfiguration().setCaCertificates(certList); } else - certList = QSslSocket::systemCaCertificates(); + certList = QSslConfiguration::systemCaCertificates(); int nRootCerts = 0; const QDateTime currentTime = QDateTime::currentDateTime(); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 05157c2a4a..e8cf432131 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -261,7 +261,7 @@ void ReceiveCoinsDialog::copyColumnToClipboard(int column) if (!firstIndex.isValid()) { return; } - GUIUtil::setClipboard(model->getRecentRequestsTableModel()->data(firstIndex.child(firstIndex.row(), column), Qt::EditRole).toString()); + GUIUtil::setClipboard(model->getRecentRequestsTableModel()->index(firstIndex.row(), column).data(Qt::EditRole).toString()); } // context menu diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index 8a1140e952..130b709d46 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -76,7 +76,7 @@ public: QVariant data(const QModelIndex &index, int role) const; bool setData(const QModelIndex &index, const QVariant &value, int role); QVariant headerData(int section, Qt::Orientation orientation, int role) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); Qt::ItemFlags flags(const QModelIndex &index) const; /*@}*/ diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 19b11ba1cd..eccc34e12f 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -28,13 +28,12 @@ #include <wallet/wallet.h> #endif -#include <QDesktopWidget> #include <QKeyEvent> #include <QMenu> #include <QMessageBox> #include <QScrollBar> +#include <QScreen> #include <QSettings> -#include <QSignalMapper> #include <QTime> #include <QTimer> #include <QStringList> @@ -451,7 +450,7 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty QSettings settings; if (!restoreGeometry(settings.value("RPCConsoleWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window - move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); + move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } QChar nonbreaking_hyphen(8209); @@ -558,6 +557,17 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) void RPCConsole::setClientModel(ClientModel *model) { clientModel = model; + + bool wallet_enabled{false}; +#ifdef ENABLE_WALLET + wallet_enabled = WalletModel::isWalletEnabled(); +#endif // ENABLE_WALLET + if (model && !wallet_enabled) { + // Show warning, for example if this is a prerelease version + connect(model, &ClientModel::alertsChanged, this, &RPCConsole::updateAlerts); + updateAlerts(model->getStatusBarWarnings()); + } + ui->trafficGraph->setClientModel(model); if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) { // Keep up to date with client @@ -603,19 +613,10 @@ void RPCConsole::setClientModel(ClientModel *model) peersTableContextMenu->addAction(banAction7d); peersTableContextMenu->addAction(banAction365d); - // Add a signal mapping to allow dynamic context menu arguments. - // We need to use int (instead of int64_t), because signal mapper only supports - // int or objects, which is okay because max bantime (1 year) is < int_max. - QSignalMapper* signalMapper = new QSignalMapper(this); - signalMapper->setMapping(banAction1h, 60*60); - signalMapper->setMapping(banAction24h, 60*60*24); - signalMapper->setMapping(banAction7d, 60*60*24*7); - signalMapper->setMapping(banAction365d, 60*60*24*365); - connect(banAction1h, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(banAction24h, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(banAction7d, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(banAction365d, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(signalMapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), this, &RPCConsole::banSelectedNode); + connect(banAction1h, &QAction::triggered, [this] { banSelectedNode(60 * 60); }); + connect(banAction24h, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24); }); + connect(banAction7d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 7); }); + connect(banAction365d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 365); }); // peer table context menu signals connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu); @@ -1274,3 +1275,9 @@ QString RPCConsole::tabTitle(TabTypes tab_type) const { return ui->tabWidget->tabText(tab_type); } + +void RPCConsole::updateAlerts(const QString& warnings) +{ + this->ui->label_alerts->setVisible(!warnings.isEmpty()); + this->ui->label_alerts->setText(warnings); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 38015e38fd..3f7a74ba03 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -167,6 +167,9 @@ private: /** Update UI with latest network info from model. */ void updateNetworkState(); + +private Q_SLOTS: + void updateAlerts(const QString& warnings); }; #endif // BITCOIN_QT_RPCCONSOLE_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 0ed057bc8b..a88119d8c5 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -283,7 +283,7 @@ void SendCoinsDialog::on_sendButton_clicked() // generate amount string with wallet name in case of multiwallet QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); if (model->isMultiwallet()) { - amount.append(tr(" from wallet '%1'").arg(model->getWalletName())); + amount.append(tr(" from wallet '%1'").arg(GUIUtil::HtmlEscape(model->getWalletName()))); } // generate address string @@ -297,7 +297,7 @@ void SendCoinsDialog::on_sendButton_clicked() { if(rcp.label.length() > 0) // label with address { - recipientElement.append(tr("%1 to '%2'").arg(amount, rcp.label)); + recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label))); recipientElement.append(QString(" (%1)").arg(address)); } else // just address @@ -704,7 +704,7 @@ void SendCoinsDialog::updateSmartFeeLabel() int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness(); QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14)); ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }"); - ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x")); + ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x")); } else { diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 5bceb1f945..0e5abb89f3 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -8,12 +8,12 @@ #include <qt/splashscreen.h> -#include <qt/networkstyle.h> - #include <clientversion.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <interfaces/wallet.h> +#include <qt/guiutil.h> +#include <qt/networkstyle.h> #include <ui_interface.h> #include <util/system.h> #include <util/translation.h> @@ -21,9 +21,9 @@ #include <QApplication> #include <QCloseEvent> -#include <QDesktopWidget> #include <QPainter> #include <QRadialGradient> +#include <QScreen> SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const NetworkStyle *networkStyle) : @@ -75,21 +75,21 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw // check font size and drawing with pixPaint.setFont(QFont(font, 33*fontFactor)); QFontMetrics fm = pixPaint.fontMetrics(); - int titleTextWidth = fm.width(titleText); + int titleTextWidth = GUIUtil::TextWidth(fm, titleText); if (titleTextWidth > 176) { fontFactor = fontFactor * 176 / titleTextWidth; } pixPaint.setFont(QFont(font, 33*fontFactor)); fm = pixPaint.fontMetrics(); - titleTextWidth = fm.width(titleText); + titleTextWidth = GUIUtil::TextWidth(fm, titleText); pixPaint.drawText(pixmap.width()/devicePixelRatio-titleTextWidth-paddingRight,paddingTop,titleText); pixPaint.setFont(QFont(font, 15*fontFactor)); // if the version string is too long, reduce size fm = pixPaint.fontMetrics(); - int versionTextWidth = fm.width(versionText); + int versionTextWidth = GUIUtil::TextWidth(fm, versionText); if(versionTextWidth > titleTextWidth+paddingRight-10) { pixPaint.setFont(QFont(font, 10*fontFactor)); titleVersionVSpace -= 5; @@ -111,7 +111,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw boldFont.setWeight(QFont::Bold); pixPaint.setFont(boldFont); fm = pixPaint.fontMetrics(); - int titleAddTextWidth = fm.width(titleAddText); + int titleAddTextWidth = GUIUtil::TextWidth(fm, titleAddText); pixPaint.drawText(pixmap.width()/devicePixelRatio-titleAddTextWidth-10,15,titleAddText); } @@ -124,7 +124,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw QRect r(QPoint(), QSize(pixmap.size().width()/devicePixelRatio,pixmap.size().height()/devicePixelRatio)); resize(r.size()); setFixedSize(r.size()); - move(QApplication::desktop()->screenGeometry().center() - r.center()); + move(QGuiApplication::primaryScreen()->geometry().center() - r.center()); subscribeToCoreSignals(); installEventFilter(this); diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 49e9e072a8..8ae01ac093 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -68,8 +68,7 @@ void AppTests::appTests() m_app.parameterSetup(); m_app.createOptionsModel(true /* reset settings */); - QScopedPointer<const NetworkStyle> style( - NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString()))); + QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().NetworkIDString())); m_app.setupPlatformStyle(); m_app.createWindow(style.data()); connect(&m_app, &BitcoinApplication::windowShown, this, &AppTests::guiTests); diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 17e174e57a..cbc4ab49f5 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -30,7 +30,6 @@ #include <QMenu> #include <QPoint> #include <QScrollBar> -#include <QSignalMapper> #include <QTableView> #include <QTimer> #include <QUrl> @@ -176,11 +175,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa contextMenu->addAction(abandonAction); contextMenu->addAction(editLabelAction); - mapperThirdPartyTxUrls = new QSignalMapper(this); - - // Connect actions - connect(mapperThirdPartyTxUrls, static_cast<void (QSignalMapper::*)(const QString&)>(&QSignalMapper::mapped), this, &TransactionView::openThirdPartyTxUrl); - connect(dateWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseDate); connect(typeWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseType); connect(watchOnlyWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseWatchonly); @@ -246,15 +240,15 @@ void TransactionView::setModel(WalletModel *_model) QStringList listUrls = _model->getOptionsModel()->getThirdPartyTxUrls().split("|", QString::SkipEmptyParts); for (int i = 0; i < listUrls.size(); ++i) { - QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host(); + QString url = listUrls[i].trimmed(); + QString host = QUrl(url, QUrl::StrictMode).host(); if (!host.isEmpty()) { QAction *thirdPartyTxUrlAction = new QAction(host, this); // use host as menu item label if (i == 0) contextMenu->addSeparator(); contextMenu->addAction(thirdPartyTxUrlAction); - connect(thirdPartyTxUrlAction, &QAction::triggered, mapperThirdPartyTxUrls, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed()); + connect(thirdPartyTxUrlAction, &QAction::triggered, [this, url] { openThirdPartyTxUrl(url); }); } } } diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index e07181d1c8..79347c371f 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -23,7 +23,6 @@ class QFrame; class QLineEdit; class QMenu; class QModelIndex; -class QSignalMapper; class QTableView; QT_END_NAMESPACE @@ -72,7 +71,6 @@ private: QLineEdit *amountWidget; QMenu *contextMenu; - QSignalMapper *mapperThirdPartyTxUrls; QFrame *dateRangeWidget; QDateTimeEdit *dateFrom; diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index a8e7bce6b5..fa6f9f3f16 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -2,8 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <qt/askpassphrasedialog.h> +#include <qt/createwalletdialog.h> +#include <qt/guiconstants.h> +#include <qt/guiutil.h> #include <qt/walletcontroller.h> +#include <wallet/wallet.h> + #include <interfaces/handler.h> #include <interfaces/node.h> @@ -13,10 +19,13 @@ #include <QMessageBox> #include <QMutexLocker> #include <QThread> +#include <QTimer> #include <QWindow> WalletController::WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent) : QObject(parent) + , m_activity_thread(new QThread(this)) + , m_activity_worker(new QObject) , m_node(node) , m_platform_style(platform_style) , m_options_model(options_model) @@ -29,15 +38,17 @@ WalletController::WalletController(interfaces::Node& node, const PlatformStyle* getOrCreateWallet(std::move(wallet)); } - m_activity_thread.start(); + m_activity_worker->moveToThread(m_activity_thread); + m_activity_thread->start(); } // Not using the default destructor because not all member types definitions are // available in the header, just forward declared. WalletController::~WalletController() { - m_activity_thread.quit(); - m_activity_thread.wait(); + m_activity_thread->quit(); + m_activity_thread->wait(); + delete m_activity_worker; } std::vector<WalletModel*> WalletController::getOpenWallets() const @@ -60,18 +71,11 @@ std::map<std::string, bool> WalletController::listWalletDir() const return wallets; } -OpenWalletActivity* WalletController::openWallet(const std::string& name, QWidget* parent) -{ - OpenWalletActivity* activity = new OpenWalletActivity(this, name); - activity->moveToThread(&m_activity_thread); - return activity; -} - void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent) { QMessageBox box(parent); box.setWindowTitle(tr("Close wallet")); - box.setText(tr("Are you sure you wish to close wallet <i>%1</i>?").arg(wallet_model->getDisplayName())); + box.setText(tr("Are you sure you wish to close the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName()))); box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.")); box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); box.setDefaultButton(QMessageBox::Yes); @@ -140,23 +144,148 @@ void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) delete wallet_model; } +WalletControllerActivity::WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget) + : QObject(wallet_controller) + , m_wallet_controller(wallet_controller) + , m_parent_widget(parent_widget) +{ +} -OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, const std::string& name) - : m_wallet_controller(wallet_controller) - , m_name(name) -{} +WalletControllerActivity::~WalletControllerActivity() +{ + delete m_progress_dialog; +} -void OpenWalletActivity::open() +void WalletControllerActivity::showProgressDialog(const QString& label_text) { - std::string error, warning; - std::unique_ptr<interfaces::Wallet> wallet = m_wallet_controller->m_node.loadWallet(m_name, error, warning); - if (!warning.empty()) { - Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning)); + m_progress_dialog = new QProgressDialog(m_parent_widget); + + m_progress_dialog->setLabelText(label_text); + m_progress_dialog->setRange(0, 0); + m_progress_dialog->setCancelButton(nullptr); + m_progress_dialog->setWindowModality(Qt::ApplicationModal); + GUIUtil::PolishProgressDialog(m_progress_dialog); +} + +CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget) + : WalletControllerActivity(wallet_controller, parent_widget) +{ + m_passphrase.reserve(MAX_PASSPHRASE_SIZE); +} + +CreateWalletActivity::~CreateWalletActivity() +{ + delete m_create_wallet_dialog; + delete m_passphrase_dialog; +} + +void CreateWalletActivity::askPassphrase() +{ + m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase); + m_passphrase_dialog->setWindowModality(Qt::ApplicationModal); + m_passphrase_dialog->show(); + + connect(m_passphrase_dialog, &QObject::destroyed, [this] { + m_passphrase_dialog = nullptr; + }); + connect(m_passphrase_dialog, &QDialog::accepted, [this] { + createWallet(); + }); + connect(m_passphrase_dialog, &QDialog::rejected, [this] { + Q_EMIT finished(); + }); +} + +void CreateWalletActivity::createWallet() +{ + showProgressDialog(tr("Creating Wallet <b>%1</b>...").arg(m_create_wallet_dialog->walletName().toHtmlEscaped())); + + std::string name = m_create_wallet_dialog->walletName().toStdString(); + uint64_t flags = 0; + if (m_create_wallet_dialog->isDisablePrivateKeysChecked()) { + flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } - if (wallet) { - Q_EMIT opened(m_wallet_controller->getOrCreateWallet(std::move(wallet))); - } else { - Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error)); + if (m_create_wallet_dialog->isMakeBlankWalletChecked()) { + flags |= WALLET_FLAG_BLANK_WALLET; } + + QTimer::singleShot(500, worker(), [this, name, flags] { + std::unique_ptr<interfaces::Wallet> wallet; + WalletCreationStatus status = node().createWallet(m_passphrase, flags, name, m_error_message, m_warning_message, wallet); + + if (status == WalletCreationStatus::SUCCESS) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + + QTimer::singleShot(500, this, &CreateWalletActivity::finish); + }); +} + +void CreateWalletActivity::finish() +{ + m_progress_dialog->hide(); + + if (!m_error_message.empty()) { + QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message)); + } else if (!m_warning_message.empty()) { + QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(m_warning_message)); + } + + if (m_wallet_model) Q_EMIT created(m_wallet_model); + + Q_EMIT finished(); +} + +void CreateWalletActivity::create() +{ + m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget); + m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal); + m_create_wallet_dialog->show(); + + connect(m_create_wallet_dialog, &QObject::destroyed, [this] { + m_create_wallet_dialog = nullptr; + }); + connect(m_create_wallet_dialog, &QDialog::rejected, [this] { + Q_EMIT finished(); + }); + connect(m_create_wallet_dialog, &QDialog::accepted, [this] { + if (m_create_wallet_dialog->isEncryptWalletChecked()) { + askPassphrase(); + } else { + createWallet(); + } + }); +} + +OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget) + : WalletControllerActivity(wallet_controller, parent_widget) +{ +} + +void OpenWalletActivity::finish() +{ + m_progress_dialog->hide(); + + if (!m_error_message.empty()) { + QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message)); + } else if (!m_warning_message.empty()) { + QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(m_warning_message)); + } + + if (m_wallet_model) Q_EMIT opened(m_wallet_model); + Q_EMIT finished(); } + +void OpenWalletActivity::open(const std::string& path) +{ + QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + + showProgressDialog(tr("Opening Wallet <b>%1</b>...").arg(name.toHtmlEscaped())); + + QTimer::singleShot(0, worker(), [this, path] { + std::unique_ptr<interfaces::Wallet> wallet = node().loadWallet(path, m_error_message, m_warning_message); + + if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + + QTimer::singleShot(0, this, &OpenWalletActivity::finish); + }); +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index be1c282919..fb37b7292c 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -6,15 +6,20 @@ #define BITCOIN_QT_WALLETCONTROLLER_H #include <qt/walletmodel.h> +#include <support/allocators/secure.h> #include <sync.h> #include <map> #include <memory> +#include <string> #include <vector> #include <QMessageBox> #include <QMutex> +#include <QProgressDialog> #include <QThread> +#include <QTimer> +#include <QString> class OptionsModel; class PlatformStyle; @@ -24,7 +29,11 @@ class Handler; class Node; } // namespace interfaces +class AskPassphraseDialog; +class CreateWalletActivity; +class CreateWalletDialog; class OpenWalletActivity; +class WalletControllerActivity; /** * Controller between interfaces::Node, WalletModel instances and the GUI. @@ -33,7 +42,6 @@ class WalletController : public QObject { Q_OBJECT - WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet); void removeAndDeleteWallet(WalletModel* wallet_model); public: @@ -43,11 +51,12 @@ public: //! Returns wallet models currently open. std::vector<WalletModel*> getOpenWallets() const; + WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet); + //! Returns all wallet names in the wallet dir mapped to whether the wallet //! is loaded. std::map<std::string, bool> listWalletDir() const; - OpenWalletActivity* openWallet(const std::string& name, QWidget* parent = nullptr); void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); Q_SIGNALS: @@ -57,7 +66,8 @@ Q_SIGNALS: void coinsSent(WalletModel* wallet_model, SendCoinsRecipient recipient, QByteArray transaction); private: - QThread m_activity_thread; + QThread* const m_activity_thread; + QObject* const m_activity_worker; interfaces::Node& m_node; const PlatformStyle* const m_platform_style; OptionsModel* const m_options_model; @@ -65,27 +75,72 @@ private: std::vector<WalletModel*> m_wallets; std::unique_ptr<interfaces::Handler> m_handler_load_wallet; - friend class OpenWalletActivity; + friend class WalletControllerActivity; }; -class OpenWalletActivity : public QObject +class WalletControllerActivity : public QObject { Q_OBJECT public: - OpenWalletActivity(WalletController* wallet_controller, const std::string& name); - -public Q_SLOTS: - void open(); + WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget); + virtual ~WalletControllerActivity(); Q_SIGNALS: - void message(QMessageBox::Icon icon, const QString text); void finished(); + +protected: + interfaces::Node& node() const { return m_wallet_controller->m_node; } + QObject* worker() const { return m_wallet_controller->m_activity_worker; } + + void showProgressDialog(const QString& label_text); + + WalletController* const m_wallet_controller; + QWidget* const m_parent_widget; + QProgressDialog* m_progress_dialog{nullptr}; + WalletModel* m_wallet_model{nullptr}; + std::string m_error_message; + std::string m_warning_message; +}; + + +class CreateWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget); + virtual ~CreateWalletActivity(); + + void create(); + +Q_SIGNALS: + void created(WalletModel* wallet_model); + +private: + void askPassphrase(); + void createWallet(); + void finish(); + + SecureString m_passphrase; + CreateWalletDialog* m_create_wallet_dialog{nullptr}; + AskPassphraseDialog* m_passphrase_dialog{nullptr}; +}; + +class OpenWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget); + + void open(const std::string& path); + +Q_SIGNALS: void opened(WalletModel* wallet_model); private: - WalletController* const m_wallet_controller; - std::string const m_name; + void finish(); }; #endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index be47f67f95..8652827b59 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -170,9 +170,9 @@ void WalletView::processNewTransaction(const QModelIndex& parent, int start, int QString type = ttm->index(start, TransactionTableModel::Type, parent).data().toString(); QModelIndex index = ttm->index(start, 0, parent); QString address = ttm->data(index, TransactionTableModel::AddressRole).toString(); - QString label = ttm->data(index, TransactionTableModel::LabelRole).toString(); + QString label = GUIUtil::HtmlEscape(ttm->data(index, TransactionTableModel::LabelRole).toString()); - Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, walletModel->getWalletName()); + Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, GUIUtil::HtmlEscape(walletModel->getWalletName())); } void WalletView::gotoOverviewPage() diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 7161c39ffd..9513c2b9ac 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1569,6 +1569,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) " \"time\": xxxxx, (numeric) The timestamp for the final block in the window in UNIX format.\n" " \"txcount\": xxxxx, (numeric) The total number of transactions in the chain up to that point.\n" " \"window_final_block_hash\": \"...\", (string) The hash of the final block in the window.\n" + " \"window_final_block_height\": xxxxx, (numeric) The height of the final block in the window.\n" " \"window_block_count\": xxxxx, (numeric) Size of the window in number of blocks.\n" " \"window_tx_count\": xxxxx, (numeric) The number of transactions in the window. Only returned if \"window_block_count\" is > 0.\n" " \"window_interval\": xxxxx, (numeric) The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0.\n" @@ -1619,6 +1620,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) ret.pushKV("time", (int64_t)pindex->nTime); ret.pushKV("txcount", (int64_t)pindex->nChainTx); ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex()); + ret.pushKV("window_final_block_height", pindex->nHeight); ret.pushKV("window_block_count", blockcount); if (blockcount > 0) { ret.pushKV("window_tx_count", nTxDiff); @@ -2062,17 +2064,21 @@ UniValue scantxoutset(const JSONRPCRequest& request) }, RPCResult{ "{\n" + " \"success\": true|false, (boolean) Whether the scan was completed\n" + " \"txouts\": n, (numeric) The number of unspent transaction outputs scanned\n" + " \"height\": n, (numeric) The current block height (index)\n" + " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n" " \"unspents\": [\n" - " {\n" - " \"txid\" : \"transactionid\", (string) The transaction id\n" - " \"vout\": n, (numeric) the vout value\n" - " \"scriptPubKey\" : \"script\", (string) the script key\n" - " \"desc\" : \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n" - " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n" - " \"height\" : n, (numeric) Height of the unspent transaction output\n" + " {\n" + " \"txid\": \"hash\", (string) The transaction id\n" + " \"vout\": n, (numeric) The vout value\n" + " \"scriptPubKey\": \"script\", (string) The script key\n" + " \"desc\": \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n" + " \"amount\": x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n" + " \"height\": n, (numeric) Height of the unspent transaction output\n" " }\n" - " ,...], \n" - " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n" + " ,...],\n" + " \"total_amount\": x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n" "]\n" }, RPCExamples{""}, @@ -2126,15 +2132,20 @@ UniValue scantxoutset(const JSONRPCRequest& request) g_scan_progress = 0; int64_t count = 0; std::unique_ptr<CCoinsViewCursor> pcursor; + CBlockIndex* tip; { LOCK(cs_main); ::ChainstateActive().ForceFlushStateToDisk(); pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor()); assert(pcursor); + tip = ::ChainActive().Tip(); + assert(tip); } bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins); result.pushKV("success", res); - result.pushKV("searched_items", count); + result.pushKV("txouts", count); + result.pushKV("height", tip->nHeight); + result.pushKV("bestblock", tip->GetBlockHash().GetHex()); for (const auto& it : coins) { const COutPoint& outpoint = it.first; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 3cd661e067..c2714f9c83 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -85,6 +85,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getblockheader", 1, "verbose" }, { "getchaintxstats", 0, "nblocks" }, { "gettransaction", 1, "include_watchonly" }, + { "gettransaction", 2, "verbose" }, { "getrawtransaction", 1, "verbose" }, { "createrawtransaction", 0, "inputs" }, { "createrawtransaction", 1, "outputs" }, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 25dda924a4..7c4b3d0cc6 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -82,6 +82,10 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) " \"addrbind\":\"ip:port\", (string) Bind address of the connection to the peer\n" " \"addrlocal\":\"ip:port\", (string) Local address as reported by the peer\n" " \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n" + " \"servicesnames\":[ (array) the services offered, in human-readable form\n" + " \"SERVICE_NAME\", (string) the service name if it is recognised\n" + " ...\n" + " ],\n" " \"relaytxes\":true|false, (boolean) Whether peer has asked us to relay transactions to it\n" " \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n" " \"lastrecv\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last receive\n" @@ -147,6 +151,7 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) if (stats.addrBind.IsValid()) obj.pushKV("addrbind", stats.addrBind.ToString()); obj.pushKV("services", strprintf("%016x", stats.nServices)); + obj.pushKV("servicesnames", GetServicesNames(stats.nServices)); obj.pushKV("relaytxes", stats.fRelayTxes); obj.pushKV("lastsend", stats.nLastSend); obj.pushKV("lastrecv", stats.nLastRecv); @@ -446,6 +451,10 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) " \"subversion\": \"/Satoshi:x.x.x/\", (string) the server subversion string\n" " \"protocolversion\": xxxxx, (numeric) the protocol version\n" " \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services we offer to the network\n" + " \"localservicesnames\": [ (array) the services we offer to the network, in human-readable form\n" + " \"SERVICE_NAME\", (string) the service name\n" + " ...\n" + " ],\n" " \"localrelay\": true|false, (bool) true if transaction relay is requested from peers\n" " \"timeoffset\": xxxxx, (numeric) the time offset\n" " \"connections\": xxxxx, (numeric) the number of connections\n" @@ -484,8 +493,11 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) obj.pushKV("version", CLIENT_VERSION); obj.pushKV("subversion", strSubVersion); obj.pushKV("protocolversion",PROTOCOL_VERSION); - if(g_connman) - obj.pushKV("localservices", strprintf("%016x", g_connman->GetLocalServices())); + if (g_connman) { + ServiceFlags services = g_connman->GetLocalServices(); + obj.pushKV("localservices", strprintf("%016x", services)); + obj.pushKV("localservicesnames", GetServicesNames(services)); + } obj.pushKV("localrelay", g_relay_txes); obj.pushKV("timeoffset", GetTimeOffset()); if (g_connman) { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index ffbad45714..fb8ea8c227 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -758,7 +758,10 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) } FindCoins(coins); - return SignTransaction(mtx, request.params[2], &keystore, coins, true, request.params[3]); + // Parse the prevtxs array + ParsePrevouts(request.params[2], &keystore, coins); + + return SignTransaction(mtx, &keystore, coins, request.params[3]); } static UniValue sendrawtransaction(const JSONRPCRequest& request) diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 55425cca35..f1d176ba4d 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -147,9 +147,8 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: vErrorsRet.push_back(entry); } -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins, bool is_temp_keystore, const UniValue& hashType) +void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins) { - // Add previous txouts given in the RPC call: if (!prevTxsUnival.isNull()) { UniValue prevTxs = prevTxsUnival.get_array(); for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) { @@ -197,36 +196,80 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } // if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed - if (is_temp_keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) { + const bool is_p2sh = scriptPubKey.IsPayToScriptHash(); + const bool is_p2wsh = scriptPubKey.IsPayToWitnessScriptHash(); + if (keystore && (is_p2sh || is_p2wsh)) { RPCTypeCheckObj(prevOut, { {"redeemScript", UniValueType(UniValue::VSTR)}, {"witnessScript", UniValueType(UniValue::VSTR)}, }, true); UniValue rs = find_value(prevOut, "redeemScript"); - if (!rs.isNull()) { - std::vector<unsigned char> rsData(ParseHexV(rs, "redeemScript")); - CScript redeemScript(rsData.begin(), rsData.end()); - keystore->AddCScript(redeemScript); - // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). - // This is only for compatibility, it is encouraged to use the explicit witnessScript field instead. - keystore->AddCScript(GetScriptForWitness(redeemScript)); - } UniValue ws = find_value(prevOut, "witnessScript"); - if (!ws.isNull()) { - std::vector<unsigned char> wsData(ParseHexV(ws, "witnessScript")); - CScript witnessScript(wsData.begin(), wsData.end()); - keystore->AddCScript(witnessScript); - // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). - keystore->AddCScript(GetScriptForWitness(witnessScript)); - } if (rs.isNull() && ws.isNull()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing redeemScript/witnessScript"); } + + // work from witnessScript when possible + std::vector<unsigned char> scriptData(!ws.isNull() ? ParseHexV(ws, "witnessScript") : ParseHexV(rs, "redeemScript")); + CScript script(scriptData.begin(), scriptData.end()); + keystore->AddCScript(script); + // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). + // This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead. + CScript witness_output_script{GetScriptForWitness(script)}; + keystore->AddCScript(witness_output_script); + + if (!ws.isNull() && !rs.isNull()) { + // if both witnessScript and redeemScript are provided, + // they should either be the same (for backwards compat), + // or the redeemScript should be the encoded form of + // the witnessScript (ie, for p2sh-p2wsh) + if (ws.get_str() != rs.get_str()) { + std::vector<unsigned char> redeemScriptData(ParseHexV(rs, "redeemScript")); + CScript redeemScript(redeemScriptData.begin(), redeemScriptData.end()); + if (redeemScript != witness_output_script) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript does not correspond to witnessScript"); + } + } + } + + if (is_p2sh) { + const CTxDestination p2sh{ScriptHash(script)}; + const CTxDestination p2sh_p2wsh{ScriptHash(witness_output_script)}; + if (scriptPubKey == GetScriptForDestination(p2sh)) { + // traditional p2sh; arguably an error if + // we got here with rs.IsNull(), because + // that means the p2sh script was specified + // via witnessScript param, but for now + // we'll just quietly accept it + } else if (scriptPubKey == GetScriptForDestination(p2sh_p2wsh)) { + // p2wsh encoded as p2sh; ideally the witness + // script was specified in the witnessScript + // param, but also support specifying it via + // redeemScript param for backwards compat + // (in which case ws.IsNull() == true) + } else { + // otherwise, can't generate scriptPubKey from + // either script, so we got unusable parameters + throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey"); + } + } else if (is_p2wsh) { + // plain p2wsh; could throw an error if script + // was specified by redeemScript rather than + // witnessScript (ie, ws.IsNull() == true), but + // accept it for backwards compat + const CTxDestination p2wsh{WitnessV0ScriptHash(script)}; + if (scriptPubKey != GetScriptForDestination(p2wsh)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey"); + } + } } } } +} +UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, std::map<COutPoint, Coin>& coins, const UniValue& hashType) +{ int nHashType = ParseSighashString(hashType); bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); @@ -266,6 +309,9 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)"); + } else if (serror == SCRIPT_ERR_SIG_NULLFAIL) { + // Verification failed (possibly due to insufficient signatures). + TxInErrorToJSON(txin, vErrors, "CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)"); } else { TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); } diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index c85593e71e..b35e6da4ca 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -12,19 +12,27 @@ class UniValue; struct CMutableTransaction; class Coin; class COutPoint; +class SigningProvider; /** * Sign a transaction with the given keystore and previous transactions * * @param mtx The transaction to-be-signed - * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain * @param keystore Temporary keystore containing signing keys * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call - * @param tempKeystore Whether to use temporary keystore * @param hashType The signature hash type * @returns JSON object with details of signed transaction */ -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins, bool tempKeystore, const UniValue& hashType); +UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, std::map<COutPoint, Coin>& coins, const UniValue& hashType); + +/** + * Parse a prevtxs UniValue array and get the map of coins from it + * + * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain + * @param keystore A pointer to the temprorary keystore if there is one + * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call + */ +void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins); /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 18f7426bcf..3e5bb85c1c 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -162,7 +162,7 @@ UniValue stop(const JSONRPCRequest& jsonRequest) if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error( RPCHelpMan{"stop", - "\nStop Bitcoin server.", + "\nRequest a graceful shutdown of " PACKAGE_NAME ".", {}, RPCResults{}, RPCExamples{""}, @@ -173,7 +173,7 @@ UniValue stop(const JSONRPCRequest& jsonRequest) if (jsonRequest.params[0].isNum()) { MilliSleep(jsonRequest.params[0].get_int()); } - return "Bitcoin server stopping"; + return PACKAGE_NAME " stopping"; } static UniValue uptime(const JSONRPCRequest& jsonRequest) diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 22d67c34da..adda90c104 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -733,3 +733,21 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl } return ret; } + +UniValue GetServicesNames(ServiceFlags services) +{ + UniValue servicesNames(UniValue::VARR); + + if (services & NODE_NETWORK) + servicesNames.push_back("NETWORK"); + if (services & NODE_GETUTXO) + servicesNames.push_back("GETUTXO"); + if (services & NODE_BLOOM) + servicesNames.push_back("BLOOM"); + if (services & NODE_WITNESS) + servicesNames.push_back("WITNESS"); + if (services & NODE_NETWORK_LIMITED) + servicesNames.push_back("NETWORK_LIMITED"); + + return servicesNames; +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 4c3322b879..72fc7b6286 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -8,6 +8,7 @@ #include <node/transaction.h> #include <outputtype.h> #include <pubkey.h> +#include <protocol.h> #include <rpc/protocol.h> #include <rpc/request.h> #include <script/script.h> @@ -90,6 +91,9 @@ std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value); /** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider); +/** Returns, given services flags, a list of humanly readable (known) network services */ +UniValue GetServicesNames(ServiceFlags services); + struct RPCArg { enum class Type { OBJ, diff --git a/src/test/README.md b/src/test/README.md index 0017e3de26..8901fae7bd 100644 --- a/src/test/README.md +++ b/src/test/README.md @@ -49,7 +49,3 @@ unit tests. The file naming convention is `<source_filename>_tests.cpp` and such files should wrap their tests in a test suite called `<source_filename>_tests`. For an example of this pattern, examine `uint256_tests.cpp`. - -For further reading, I found the following website to be helpful in -explaining how the boost unit test framework works: -[http://www.alittlemadness.com/2009/03/31/c-unit-testing-with-boosttest/](http://archive.is/dRBGf). diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index a50d6854f8..b0a613372f 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -151,17 +151,17 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false); const Consensus::Params& consensusParams = Params().GetConsensus(); - constexpr int nMaxOutbound = 8; + constexpr int max_outbound_full_relay = 8; CConnman::Options options; options.nMaxConnections = 125; - options.nMaxOutbound = nMaxOutbound; + options.m_max_outbound_full_relay = max_outbound_full_relay; options.nMaxFeeler = 1; connman->Init(options); std::vector<CNode *> vNodes; // Mock some outbound peers - for (int i=0; i<nMaxOutbound; ++i) { + for (int i=0; i<max_outbound_full_relay; ++i) { AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); } @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); - for (int i=0; i<nMaxOutbound; ++i) { + for (int i=0; i<max_outbound_full_relay; ++i) { BOOST_CHECK(vNodes[i]->fDisconnect == false); } // Last added node should get marked for eviction @@ -203,10 +203,10 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); - for (int i=0; i<nMaxOutbound-1; ++i) { + for (int i=0; i<max_outbound_full_relay-1; ++i) { BOOST_CHECK(vNodes[i]->fDisconnect == false); } - BOOST_CHECK(vNodes[nMaxOutbound-1]->fDisconnect == true); + BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true); BOOST_CHECK(vNodes.back()->fDisconnect == false); bool dummy; diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index e69ebcc2c3..193858cca9 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -13,7 +13,7 @@ #include <boost/test/unit_test.hpp> -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks); +bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks); BOOST_AUTO_TEST_SUITE(tx_validationcache_tests) @@ -125,7 +125,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail // WITNESS requires P2SH test_flags |= SCRIPT_VERIFY_P2SH; } - bool ret = CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), true, test_flags, true, add_to_cache, txdata, nullptr); + bool ret = CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, nullptr); // CheckInputs should succeed iff test_flags doesn't intersect with // failing_flags bool expected_return_value = !(test_flags & failing_flags); @@ -135,13 +135,13 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail if (ret && add_to_cache) { // Check that we get a cache hit if the tx was valid std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), true, test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK(scriptchecks.empty()); } else { // Check that we get script executions to check, if the transaction // was invalid, or we didn't add to cache. std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), true, test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size()); } } @@ -204,13 +204,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CValidationState state; PrecomputedTransactionData ptd_spend_tx(spend_tx); - BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); + BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); // If we call again asking for scriptchecks (as happens in // ConnectBlock), we should add a script check object for this -- we're // not caching invalidity (if that changes, delete this test case). std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); + BOOST_CHECK(CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), 1U); // Test that CheckInputs returns true iff DERSIG-enforcing flags are @@ -272,7 +272,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100; CValidationState state; PrecomputedTransactionData txdata(invalid_with_cltv_tx); - BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); } // TEST CHECKSEQUENCEVERIFY @@ -300,7 +300,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100; CValidationState state; PrecomputedTransactionData txdata(invalid_with_csv_tx); - BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); } // TODO: add tests for remaining script flags @@ -362,12 +362,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CValidationState state; PrecomputedTransactionData txdata(tx); // This transaction is now invalid under segwit, because of the second input. - BOOST_CHECK(!CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); + BOOST_CHECK(!CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); std::vector<CScriptCheck> scriptchecks; // Make sure this transaction was not cached (ie because the first // input was valid) - BOOST_CHECK(CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); // Should get 2 script checks back -- caching is on a whole-transaction basis. BOOST_CHECK_EQUAL(scriptchecks.size(), 2U); } diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 65cb956fbe..d0cd4b0a03 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -913,7 +913,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) // Results file is formatted like: // // <input> || <output> - BOOST_CHECK_EQUAL(out_sha_hex, "b284f4b4a15dd6bf8c06213a69a004b1960388e1d9917173927db52ac220927f"); + BOOST_CHECK_EQUAL(out_sha_hex, "94b4ad55c8ac639a56b93e36f7e32e4c611fd7d7dd7b2be6a71707b1eadcaec7"); } BOOST_AUTO_TEST_CASE(util_FormatMoney) diff --git a/src/util/error.cpp b/src/util/error.cpp index 287476c0d3..aa44ed3e3a 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -41,12 +41,12 @@ std::string ResolveErrMsg(const std::string& optname, const std::string& strBind return strprintf(_("Cannot resolve -%s address: '%s'").translated, optname, strBind); } -std::string AmountHighWarn(const std::string& optname) +bilingual_str AmountHighWarn(const std::string& optname) { - return strprintf(_("%s is set very high!").translated, optname); + return strprintf(_("%s is set very high!"), optname); } -std::string AmountErrMsg(const std::string& optname, const std::string& strValue) +bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue) { - return strprintf(_("Invalid amount for -%s=<amount>: '%s'").translated, optname, strValue); + return strprintf(_("Invalid amount for -%s=<amount>: '%s'"), optname, strValue); } diff --git a/src/util/error.h b/src/util/error.h index 7777cc0c5d..f540b0020d 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -17,6 +17,8 @@ #include <string> +struct bilingual_str; + enum class TransactionError { OK, //!< No error MISSING_INPUTS, @@ -34,8 +36,8 @@ std::string TransactionErrorString(const TransactionError error); std::string ResolveErrMsg(const std::string& optname, const std::string& strBind); -std::string AmountHighWarn(const std::string& optname); +bilingual_str AmountHighWarn(const std::string& optname); -std::string AmountErrMsg(const std::string& optname, const std::string& strValue); +bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue); #endif // BITCOIN_UTIL_ERROR_H diff --git a/src/util/system.cpp b/src/util/system.cpp index c925dec253..8098cde093 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -954,16 +954,18 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) std::string ArgsManager::GetChainName() const { LOCK(cs_args); - bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest"); - bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet"); + const bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest"); + const bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet"); + const bool is_chain_arg_set = IsArgSet("-chain"); - if (fTestNet && fRegTest) - throw std::runtime_error("Invalid combination of -regtest and -testnet."); + if ((int)is_chain_arg_set + (int)fRegTest + (int)fTestNet > 1) { + throw std::runtime_error("Invalid combination of -regtest, -testnet and -chain. Can use at most one."); + } if (fRegTest) return CBaseChainParams::REGTEST; if (fTestNet) return CBaseChainParams::TESTNET; - return CBaseChainParams::MAIN; + return GetArg("-chain", CBaseChainParams::MAIN); } bool RenameOver(fs::path src, fs::path dest) diff --git a/src/validation.cpp b/src/validation.cpp index 8b842553c6..6a9b0c95fb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -184,7 +184,7 @@ std::unique_ptr<CBlockTreeDB> pblocktree; // See definition for documentation static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight); static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight); -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr); +bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr); static FILE* OpenUndoFile(const FlatFilePos &pos, bool fReadOnly = false); static FlatFileSeq BlockFileSeq(); static FlatFileSeq UndoFileSeq(); @@ -425,7 +425,7 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt } } - return CheckInputs(tx, state, view, true, flags, cacheSigStore, true, txdata); + return CheckInputs(tx, state, view, flags, cacheSigStore, true, txdata); } /** @@ -615,17 +615,55 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool REJECT_HIGHFEE, "absurdly-high-fee", strprintf("%d > %d", nFees, nAbsurdFee)); + const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts); // Calculate in-mempool ancestors, up to a limit. CTxMemPool::setEntries setAncestors; size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000; size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; + + if (setConflicts.size() == 1) { + // In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we + // would meet the chain limits after the conflicts have been removed. However, there isn't a practical + // way to do this short of calculating the ancestor and descendant sets with an overlay cache of + // changed mempool entries. Due to both implementation and runtime complexity concerns, this isn't + // very realistic, thus we only ensure a limited set of transactions are RBF'able despite mempool + // conflicts here. Importantly, we need to ensure that some transactions which were accepted using + // the below carve-out are able to be RBF'ed, without impacting the security the carve-out provides + // for off-chain contract systems (see link in the comment below). + // + // Specifically, the subset of RBF transactions which we allow despite chain limits are those which + // conflict directly with exactly one other transaction (but may evict children of said transaction), + // and which are not adding any new mempool dependencies. Note that the "no new mempool dependencies" + // check is accomplished later, so we don't bother doing anything about it here, but if BIP 125 is + // amended, we may need to move that check to here instead of removing it wholesale. + // + // Such transactions are clearly not merging any existing packages, so we are only concerned with + // ensuring that (a) no package is growing past the package size (not count) limits and (b) we are + // not allowing something to effectively use the (below) carve-out spot when it shouldn't be allowed + // to. + // + // To check these we first check if we meet the RBF criteria, above, and increment the descendant + // limits by the direct conflict and its descendants (as these are recalculated in + // CalculateMempoolAncestors by assuming the new transaction being added is a new descendant, with no + // removals, of each parent's existing dependant set). The ancestor count limits are unmodified (as + // the ancestor limits should be the same for both our new transaction and any conflicts). + // We don't bother incrementing nLimitDescendants by the full removal count as that limit never comes + // into force here (as we're only adding a single transaction). + assert(setIterConflicting.size() == 1); + CTxMemPool::txiter conflict = *setIterConflicting.begin(); + + nLimitDescendants += 1; + nLimitDescendantSize += conflict->GetSizeWithDescendants(); + } + std::string errString; if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { setAncestors.clear(); // If CalculateMemPoolAncestors fails second time, we want the original error string. std::string dummy_err_string; + // Contracting/payment channels CPFP carve-out: // If the new transaction is relatively small (up to 40k weight) // and has at most one ancestor (ie ancestor limit of 2, including // the new transaction), allow it if its parent has exactly the @@ -674,7 +712,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CFeeRate newFeeRate(nModifiedFees, nSize); std::set<uint256> setConflictsParents; const int maxDescendantsToVisit = 100; - const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts); for (const auto& mi : setIterConflicting) { // Don't allow the replacement to reduce the feerate of the // mempool. @@ -734,6 +771,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // feerate junk to be mined first. Ideally we'd keep track of // the ancestor feerates and make the decision based on that, // but for now requiring all new inputs to be confirmed works. + // + // Note that if you relax this to make RBF a little more useful, + // this may break the CalculateMempoolAncestors RBF relaxation, + // above. See the comment above the first CalculateMempoolAncestors + // call for more info. if (!setConflictsParents.count(tx.vin[j].prevout.hash)) { // Rather than check the UTXO set - potentially expensive - @@ -773,15 +815,17 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; // Check against previous transactions - // This is done last to help prevent CPU exhaustion denial-of-service attacks. + // The first loop above does all the inexpensive checks. + // Only if ALL inputs pass do we perform expensive ECDSA signature checks. + // Helps prevent CPU exhaustion denial-of-service attacks. PrecomputedTransactionData txdata(tx); - if (!CheckInputs(tx, state, view, true, scriptVerifyFlags, true, false, txdata)) { + if (!CheckInputs(tx, state, view, scriptVerifyFlags, true, false, txdata)) { // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we // need to turn both off, and compare against just turning off CLEANSTACK // to see if the failure is specifically due to witness validation. CValidationState stateDummy; // Want reported failures to be from first CheckInputs - if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && - !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { + if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && + !CheckInputs(tx, stateDummy, view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { // Only the witness is missing, so the transaction itself may be fine. state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); @@ -1298,90 +1342,79 @@ void InitScriptExecutionCache() { * * Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp */ -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - if (!tx.IsCoinBase()) - { - if (pvChecks) - pvChecks->reserve(tx.vin.size()); - - // The first loop above does all the inexpensive checks. - // Only if ALL inputs pass do we perform expensive ECDSA signature checks. - // Helps prevent CPU exhaustion attacks. - - // Skip script verification when connecting blocks under the - // assumevalid block. Assuming the assumevalid block is valid this - // is safe because block merkle hashes are still computed and checked, - // Of course, if an assumed valid block is invalid due to false scriptSigs - // this optimization would allow an invalid chain to be accepted. - if (fScriptChecks) { - // First check if script executions have been cached with the same - // flags. Note that this assumes that the inputs provided are - // correct (ie that the transaction hash which is in tx's prevouts - // properly commits to the scriptPubKey in the inputs view of that - // transaction). - uint256 hashCacheEntry; - // We only use the first 19 bytes of nonce to avoid a second SHA - // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64) - static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache"); - CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin()); - AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks - if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) { - return true; - } - - for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - const Coin& coin = inputs.AccessCoin(prevout); - assert(!coin.IsSpent()); - - // We very carefully only pass in things to CScriptCheck which - // are clearly committed to by tx' witness hash. This provides - // a sanity check that our caching is not introducing consensus - // failures through additional data in, eg, the coins being - // spent being checked as a part of CScriptCheck. - - // Verify signature - CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata); - if (pvChecks) { - pvChecks->push_back(CScriptCheck()); - check.swap(pvChecks->back()); - } else if (!check()) { - if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) { - // Check whether the failure was caused by a - // non-mandatory script verification check, such as - // non-standard DER encodings or non-null dummy - // arguments; if so, ensure we return NOT_STANDARD - // instead of CONSENSUS to avoid downstream users - // splitting the network between upgraded and - // non-upgraded nodes by banning CONSENSUS-failing - // data providers. - CScriptCheck check2(coin.out, tx, i, - flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); - if (check2()) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); - } - // MANDATORY flag failures correspond to - // ValidationInvalidReason::CONSENSUS. Because CONSENSUS - // failures are the most serious case of validation - // failures, we may need to consider using - // RECENT_CONSENSUS_CHANGE for any script failure that - // could be due to non-upgraded nodes which we may want to - // support, to avoid splitting the network (but this - // depends on the details of how net_processing handles - // such errors). - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); - } - } + if (tx.IsCoinBase()) return true; + + if (pvChecks) { + pvChecks->reserve(tx.vin.size()); + } + + // First check if script executions have been cached with the same + // flags. Note that this assumes that the inputs provided are + // correct (ie that the transaction hash which is in tx's prevouts + // properly commits to the scriptPubKey in the inputs view of that + // transaction). + uint256 hashCacheEntry; + // We only use the first 19 bytes of nonce to avoid a second SHA + // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64) + static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache"); + CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin()); + AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks + if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) { + return true; + } - if (cacheFullScriptStore && !pvChecks) { - // We executed all of the provided scripts, and were told to - // cache the result. Do so now. - scriptExecutionCache.insert(hashCacheEntry); + for (unsigned int i = 0; i < tx.vin.size(); i++) { + const COutPoint &prevout = tx.vin[i].prevout; + const Coin& coin = inputs.AccessCoin(prevout); + assert(!coin.IsSpent()); + + // We very carefully only pass in things to CScriptCheck which + // are clearly committed to by tx' witness hash. This provides + // a sanity check that our caching is not introducing consensus + // failures through additional data in, eg, the coins being + // spent being checked as a part of CScriptCheck. + + // Verify signature + CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata); + if (pvChecks) { + pvChecks->push_back(CScriptCheck()); + check.swap(pvChecks->back()); + } else if (!check()) { + if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) { + // Check whether the failure was caused by a + // non-mandatory script verification check, such as + // non-standard DER encodings or non-null dummy + // arguments; if so, ensure we return NOT_STANDARD + // instead of CONSENSUS to avoid downstream users + // splitting the network between upgraded and + // non-upgraded nodes by banning CONSENSUS-failing + // data providers. + CScriptCheck check2(coin.out, tx, i, + flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); + if (check2()) + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); } + // MANDATORY flag failures correspond to + // ValidationInvalidReason::CONSENSUS. Because CONSENSUS + // failures are the most serious case of validation + // failures, we may need to consider using + // RECENT_CONSENSUS_CHANGE for any script failure that + // could be due to non-upgraded nodes which we may want to + // support, to avoid splitting the network (but this + // depends on the details of how net_processing handles + // such errors). + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); } } + if (cacheFullScriptStore && !pvChecks) { + // We executed all of the provided scripts, and were told to + // cache the result. Do so now. + scriptExecutionCache.insert(hashCacheEntry); + } + return true; } @@ -1769,6 +1802,11 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl pindexBestHeader->GetAncestor(pindex->nHeight) == pindex && pindexBestHeader->nChainWork >= nMinimumChainWork) { // This block is a member of the assumed verified chain and an ancestor of the best header. + // Script verification is skipped when connecting blocks under the + // assumevalid block. Assuming the assumevalid block is valid this + // is safe because block merkle hashes are still computed and checked, + // Of course, if an assumed valid block is invalid due to false scriptSigs + // this optimization would allow an invalid chain to be accepted. // The equivalent time check discourages hash power from extorting the network via DOS attack // into accepting an invalid block through telling users they must manually set assumevalid. // Requiring a software change or burying the invalid block, regardless of the setting, makes @@ -1952,7 +1990,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ - if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { + if (fScriptChecks && !CheckInputs(tx, state, view, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { if (state.GetReason() == ValidationInvalidReason::TX_NOT_STANDARD) { // CheckInputs may return NOT_STANDARD for extra flags we passed, // but we can't return that, as it's not defined for a block, so @@ -2202,14 +2240,12 @@ void static UpdateTip(const CBlockIndex* pindexNew, const CChainParams& chainPar if (nUpgraded > 0) AppendWarning(warningMessages, strprintf(_("%d of last 100 blocks have unexpected version").translated, nUpgraded)); } - LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__, /* Continued */ + LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, FormatISO8601DateTime(pindexNew->GetBlockTime()), - GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize()); - if (!warningMessages.empty()) - LogPrintf(" warning='%s'", warningMessages); /* Continued */ - LogPrintf("\n"); + GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize(), + !warningMessages.empty() ? strprintf(" warning='%s'", warningMessages) : ""); } @@ -2563,7 +2599,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar return true; } -static void NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) { +static bool NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) { bool fNotify = false; bool fInitialBlockDownload = false; static CBlockIndex* pindexHeaderOld = nullptr; @@ -2582,6 +2618,7 @@ static void NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) { if (fNotify) { uiInterface.NotifyHeaderTip(fInitialBlockDownload, pindexHeader); } + return fNotify; } static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) { @@ -3395,7 +3432,11 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio } } } - NotifyHeaderTip(); + if (NotifyHeaderTip()) { + if (::ChainstateActive().IsInitialBlockDownload() && ppindex && *ppindex) { + LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n", (*ppindex)->nHeight, 100.0/((*ppindex)->nHeight+(GetAdjustedTime() - (*ppindex)->GetBlockTime()) / Params().GetConsensus().nPowTargetSpacing) * (*ppindex)->nHeight); + } + } return true; } diff --git a/src/wallet/load.h b/src/wallet/load.h index 81f078fd10..5a62e29303 100644 --- a/src/wallet/load.h +++ b/src/wallet/load.h @@ -17,7 +17,7 @@ class Chain; //! Responsible for reading and validating the -wallet arguments and verifying the wallet database. //! This function will perform salvage on the wallet if requested, as long as only one wallet is -//! being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +//! being loaded (WalletInit::ParameterInteraction() forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); //! Load wallet databases. diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 7707d6233b..f52e4318c8 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -384,8 +384,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); } - wtx.nIndex = txnIndex; - wtx.hashBlock = merkleBlock.header.GetHash(); + wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 30e767e489..216205ed61 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -134,10 +134,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo entry.pushKV("generated", true); if (confirms > 0) { - entry.pushKV("blockhash", wtx.hashBlock.GetHex()); - entry.pushKV("blockindex", wtx.nIndex); + entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex()); + entry.pushKV("blockindex", wtx.m_confirm.nIndex); int64_t block_time; - bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time); + bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time); assert(found_block); entry.pushKV("blocktime", block_time); } else { @@ -1648,7 +1648,10 @@ static UniValue gettransaction(const JSONRPCRequest& request) "\nGet detailed information about in-wallet transaction <txid>\n", { {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses in balance calculation and details[]"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", + "Whether to include watch-only addresses in balance calculation and details[]"}, + {"verbose", RPCArg::Type::BOOL, /* default */ "false", + "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"}, }, RPCResult{ "{\n" @@ -1684,11 +1687,14 @@ static UniValue gettransaction(const JSONRPCRequest& request) " ,...\n" " ],\n" " \"hex\" : \"data\" (string) Raw data for transaction\n" + " \"decoded\" : transaction (json object) Optional, the decoded transaction (only present when `verbose` is passed), equivalent to the\n" + " RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed.\n" "}\n" }, RPCExamples{ HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") + + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") }, }.Check(request); @@ -1708,6 +1714,8 @@ static UniValue gettransaction(const JSONRPCRequest& request) filter |= ISMINE_WATCH_ONLY; } + bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool(); + UniValue entry(UniValue::VOBJ); auto it = pwallet->mapWallet.find(hash); if (it == pwallet->mapWallet.end()) { @@ -1733,6 +1741,12 @@ static UniValue gettransaction(const JSONRPCRequest& request) std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); entry.pushKV("hex", strHex); + if (verbose) { + UniValue decoded(UniValue::VOBJ); + TxToUniv(*wtx.tx, uint256(), decoded, false); + entry.pushKV("decoded", decoded); + } + return entry; } @@ -3269,7 +3283,10 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) } pwallet->chain().findCoins(coins); - return SignTransaction(mtx, request.params[1], pwallet, coins, false, request.params[2]); + // Parse the prevtxs array + ParsePrevouts(request.params[1], nullptr, coins); + + return SignTransaction(mtx, pwallet, coins, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -4175,7 +4192,7 @@ static const CRPCCommand commands[] = { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, - { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, + { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","verbose"} }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, { "wallet", "getbalances", &getbalances, {} }, { "wallet", "getwalletinfo", &getwalletinfo, {} }, diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 8af05dea45..fc3be2b6ab 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -249,8 +249,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) LockAssertion lock(::cs_main); LOCK(wallet.cs_wallet); - wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash(); - wtx.nIndex = 0; + wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0); // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. @@ -281,14 +280,19 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 } CWalletTx wtx(&wallet, MakeTransactionRef(tx)); - if (block) { - wtx.SetMerkleBranch(block->GetBlockHash(), 0); - } - { - LOCK(cs_main); + LOCK(cs_main); + LOCK(wallet.cs_wallet); + // If transaction is already in map, to avoid inconsistencies, unconfirmation + // is needed before confirm again with different block. + std::map<uint256, CWalletTx>::iterator it = wallet.mapWallet.find(wtx.GetHash()); + if (it != wallet.mapWallet.end()) { + wtx.setUnconfirmed(); wallet.AddToWallet(wtx); } - LOCK(wallet.cs_wallet); + if (block) { + wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0); + } + wallet.AddToWallet(wtx); return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart; } @@ -382,7 +386,7 @@ public: LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); + it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 03acf23508..7cf09d554b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -93,13 +93,14 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) static Mutex g_wallet_release_mutex; static std::condition_variable g_wallet_release_cv; -static std::set<CWallet*> g_unloading_wallet_set; +static std::set<std::string> g_unloading_wallet_set; // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet* wallet) { // Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain // so that it's in sync with the current chainstate. + const std::string name = wallet->GetName(); wallet->WalletLogPrintf("Releasing wallet\n"); wallet->BlockUntilSyncedToCurrentChain(); wallet->Flush(); @@ -108,7 +109,7 @@ static void ReleaseWallet(CWallet* wallet) // Wallet is now released, notify UnloadWallet, if any. { LOCK(g_wallet_release_mutex); - if (g_unloading_wallet_set.erase(wallet) == 0) { + if (g_unloading_wallet_set.erase(name) == 0) { // UnloadWallet was not called for this wallet, all done. return; } @@ -119,21 +120,21 @@ static void ReleaseWallet(CWallet* wallet) void UnloadWallet(std::shared_ptr<CWallet>&& wallet) { // Mark wallet for unloading. - CWallet* pwallet = wallet.get(); + const std::string name = wallet->GetName(); { LOCK(g_wallet_release_mutex); - auto it = g_unloading_wallet_set.insert(pwallet); + auto it = g_unloading_wallet_set.insert(name); assert(it.second); } // The wallet can be in use so it's not possible to explicitly unload here. // Notify the unload intent so that all remaining shared pointers are // released. - pwallet->NotifyUnload(); + wallet->NotifyUnload(); // Time to ditch our shared_ptr and wait for ReleaseWallet call. wallet.reset(); { WAIT_LOCK(g_wallet_release_mutex, lock); - while (g_unloading_wallet_set.count(pwallet) == 1) { + while (g_unloading_wallet_set.count(name) == 1) { g_wallet_release_cv.wait(lock); } } @@ -523,18 +524,9 @@ bool CWallet::LoadCScript(const CScript& redeemScript) static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) { - //TODO: Use Solver to extract this? - CScript::const_iterator pc = dest.begin(); - opcodetype opcode; - std::vector<unsigned char> vch; - if (!dest.GetOp(pc, opcode, vch) || !CPubKey::ValidSize(vch)) - return false; - pubKeyOut = CPubKey(vch); - if (!pubKeyOut.IsFullyValid()) - return false; - if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch)) - return false; - return true; + std::vector<std::vector<unsigned char>> solutions; + return Solver(dest, solutions) == TX_PUBKEY && + (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); } bool CWallet::AddWatchOnlyInMem(const CScript &dest) @@ -1118,22 +1110,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) bool fUpdated = false; if (!fInsertedNew) { - // Merge - if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) - { - wtx.hashBlock = wtxIn.hashBlock; - fUpdated = true; - } - // If no longer abandoned, update - if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) - { - wtx.hashBlock = wtxIn.hashBlock; - fUpdated = true; - } - if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex)) - { - wtx.nIndex = wtxIn.nIndex; + if (wtxIn.m_confirm.status != wtx.m_confirm.status) { + wtx.m_confirm.status = wtxIn.m_confirm.status; + wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex; + wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock; fUpdated = true; + } else { + assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex); + assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock); } if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) { @@ -1180,8 +1164,19 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) return true; } -void CWallet::LoadToWallet(const CWalletTx& wtxIn) +void CWallet::LoadToWallet(CWalletTx& wtxIn) { + // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken. + auto locked_chain = LockChain(); + // If tx hasn't been reorged out of chain while wallet being shutdown + // change tx status to UNCONFIRMED and reset hashBlock/nIndex. + if (!wtxIn.m_confirm.hashBlock.IsNull()) { + if (locked_chain && !locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) { + wtxIn.setUnconfirmed(); + wtxIn.m_confirm.hashBlock = uint256(); + wtxIn.m_confirm.nIndex = 0; + } + } uint256 hash = wtxIn.GetHash(); const auto& ins = mapWallet.emplace(hash, wtxIn); CWalletTx& wtx = ins.first->second; @@ -1194,14 +1189,14 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn) auto it = mapWallet.find(txin.prevout.hash); if (it != mapWallet.end()) { CWalletTx& prevtx = it->second; - if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { - MarkConflicted(prevtx.hashBlock, wtx.GetHash()); + if (prevtx.isConflicted()) { + MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash()); } } } } -bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate) +bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) { const CTransaction& tx = *ptx; { @@ -1248,9 +1243,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256 CWalletTx wtx(this, ptx); - // Get merkle branch if transaction was found in a block - if (!block_hash.IsNull()) - wtx.SetMerkleBranch(block_hash, posInBlock); + // Block disconnection override an abandoned tx as unconfirmed + // which means user may have to call abandontransaction again + wtx.SetConf(status, block_hash, posInBlock); return AddToWallet(wtx, false); } @@ -1310,7 +1305,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui if (currentconfirm == 0 && !wtx.isAbandoned()) { // If the orig tx was not in block/mempool, none of its spends can be in mempool assert(!wtx.InMempool()); - wtx.nIndex = -1; + wtx.m_confirm.nIndex = 0; wtx.setAbandoned(); wtx.MarkDirty(); batch.WriteTx(wtx); @@ -1364,8 +1359,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. - wtx.nIndex = -1; - wtx.hashBlock = hashBlock; + wtx.m_confirm.nIndex = 0; + wtx.m_confirm.hashBlock = hashBlock; + wtx.setConflicted(); wtx.MarkDirty(); batch.WriteTx(wtx); // Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too @@ -1383,8 +1379,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) { - if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) +void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx) +{ + if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance @@ -1396,7 +1393,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_h void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); + SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); auto it = mapWallet.find(ptx->GetHash()); if (it != mapWallet.end()) { @@ -1416,22 +1413,14 @@ void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransaction const uint256& block_hash = block.GetHash(); auto locked_chain = chain().lock(); LOCK(cs_wallet); - // TODO: Temporarily ensure that mempool removals are notified before - // connected transactions. This shouldn't matter, but the abandoned - // state of transactions in our wallet is currently cleared when we - // receive another notification and there is a race condition where - // notification of a connected conflict might cause an outside process - // to abandon a transaction and then have it inadvertently cleared by - // the notification that the conflicted transaction was evicted. - for (const CTransactionRef& ptx : vtxConflicted) { - SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); - TransactionRemovedFromMempool(ptx); - } for (size_t i = 0; i < block.vtx.size(); i++) { - SyncTransaction(block.vtx[i], block_hash, i); + SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i); TransactionRemovedFromMempool(block.vtx[i]); } + for (const CTransactionRef& ptx : vtxConflicted) { + TransactionRemovedFromMempool(ptx); + } m_last_block_processed = block_hash; } @@ -1440,8 +1429,12 @@ void CWallet::BlockDisconnected(const CBlock& block) { auto locked_chain = chain().lock(); LOCK(cs_wallet); + // At block disconnection, this will change an abandoned transaction to + // be unconfirmed, whether or not the transaction is added back to the mempool. + // User may have to call abandontransaction again. It may be addressed in the + // future with a stickier abandoned state or even removing abandontransaction call. for (const CTransactionRef& ptx : block.vtx) { - SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); + SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); } } @@ -2078,7 +2071,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate); + SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate); } // scan succeeded, record block as most recent successfully scanned result.last_scanned_block = block_hash; @@ -3340,6 +3333,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { + // Even if we don't use this lock in this function, we want to preserve + // lock order in LoadToWallet if query of chain state is needed to know + // tx status. If lock can't be taken (e.g wallet-tool), tx confirmation + // status may be not reliable. + auto locked_chain = LockChain(); LOCK(cs_wallet); fFirstRunRet = false; @@ -4050,7 +4048,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C for (const auto& entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; - if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) { + if (Optional<int> height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) { // ... which are already in a block for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs @@ -4093,9 +4091,9 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const { unsigned int nTimeSmart = wtx.nTimeReceived; - if (!wtx.hashUnset()) { + if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) { int64_t blocktime; - if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) { + if (chain().findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &blocktime)) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; @@ -4123,7 +4121,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { - WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); + WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString()); } } return nTimeSmart; @@ -4241,6 +4239,11 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b // Recover readable keypairs: CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; + // Even if we don't use this lock in this function, we want to preserve + // lock order in LoadToWallet if query of chain state is needed to know + // tx status. If lock can't be taken, tx confirmation status may be not + // reliable. + auto locked_chain = dummyWallet.LockChain(); if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; } @@ -4251,7 +4254,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags) { - const std::string& walletFile = WalletDataFilePath(location.GetPath()).string(); + const std::string walletFile = WalletDataFilePath(location.GetPath()).string(); // needed to restore wallet transaction meta data after -zapwallettxes std::vector<CWalletTx> vWtx; @@ -4395,23 +4398,23 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) { - chain.initError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", ""))); + chain.initError(strprintf(_("Unknown address type '%s'").translated, gArgs.GetArg("-addresstype", ""))); return nullptr; } if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) { - chain.initError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", ""))); + chain.initError(strprintf(_("Unknown change type '%s'").translated, gArgs.GetArg("-changetype", ""))); return nullptr; } if (gArgs.IsArgSet("-mintxfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) { - chain.initError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); + chain.initError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")).translated); return nullptr; } if (n > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-mintxfee") + " " + + chain.initWarning(AmountHighWarn("-mintxfee").translated + " " + _("This is the minimum transaction fee you pay on every transaction.").translated); } walletInstance->m_min_fee = CFeeRate(n); @@ -4425,7 +4428,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-fallbackfee") + " " + + chain.initWarning(AmountHighWarn("-fallbackfee").translated + " " + _("This is the transaction fee you may pay when fee estimates are not available.").translated); } walletInstance->m_fallback_fee = CFeeRate(nFeePerK); @@ -4438,7 +4441,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-discardfee") + " " + + chain.initWarning(AmountHighWarn("-discardfee").translated + " " + _("This is the transaction fee you may discard if change is smaller than dust at this level").translated); } walletInstance->m_discard_rate = CFeeRate(nFeePerK); @@ -4446,11 +4449,11 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.IsArgSet("-paytxfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) { - chain.initError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); + chain.initError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")).translated); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-paytxfee") + " " + + chain.initWarning(AmountHighWarn("-paytxfee").translated + " " + _("This is the transaction fee you will pay if you send a transaction.").translated); } walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000); @@ -4465,7 +4468,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, { CAmount nMaxFee = 0; if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) { - chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); + chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")).translated); return nullptr; } if (nMaxFee > HIGH_MAX_TX_FEE) { @@ -4479,9 +4482,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, walletInstance->m_default_max_tx_fee = nMaxFee; } - if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) - chain.initWarning(AmountHighWarn("-minrelaytxfee") + " " + + if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) { + chain.initWarning(AmountHighWarn("-minrelaytxfee").translated + " " + _("The wallet will avoid paying less than the minimum relay fee.").translated); + } walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); @@ -4634,21 +4638,23 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) m_pre_split = false; } -void CWalletTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) +void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock) { + // Update tx status + m_confirm.status = status; + // Update the tx's hashBlock - hashBlock = block_hash; + m_confirm.hashBlock = block_hash; // set the position of the transaction in the block - nIndex = posInBlock; + m_confirm.nIndex = posInBlock; } int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const { - if (hashUnset()) - return 0; + if (isUnconfirmed() || isAbandoned()) return 0; - return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1); + return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1); } int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 984be3e301..3428e8e001 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -396,7 +396,9 @@ class CWalletTx private: const CWallet* pwallet; - /** Constant used in hashBlock to indicate tx has been abandoned */ + /** Constant used in hashBlock to indicate tx has been abandoned, only used at + * serialization/deserialization to avoid ambiguity with conflicted. + */ static const uint256 ABANDON_HASH; public: @@ -457,9 +459,7 @@ public: mutable CAmount nChangeCached; CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) - : tx(std::move(arg)), - hashBlock(uint256()), - nIndex(-1) + : tx(std::move(arg)) { Init(pwalletIn); } @@ -477,16 +477,37 @@ public: fInMempool = false; nChangeCached = 0; nOrderPos = -1; + m_confirm = Confirmation{}; } CTransactionRef tx; - uint256 hashBlock; - /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest - * block in the chain we know this or any in-wallet dependency conflicts - * with. Older clients interpret nIndex == -1 as unconfirmed for backward - * compatibility. + + /* New transactions start as UNCONFIRMED. At BlockConnected, + * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected, + * they roll back to UNCONFIRMED. If we detect a conflicting transaction at + * block connection, we update conflicted tx and its dependencies as CONFLICTED. + * If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED + * by using the abandontransaction call. This last status may be override by a CONFLICTED + * or CONFIRMED transition. + */ + enum Status { + UNCONFIRMED, + CONFIRMED, + CONFLICTED, + ABANDONED + }; + + /* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed. + * This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state + * where they instead point to block hash and index of the deepest conflicting tx. */ - int nIndex; + struct Confirmation { + Status status = UNCONFIRMED; + uint256 hashBlock = uint256(); + int nIndex = 0; + }; + + Confirmation m_confirm; template<typename Stream> void Serialize(Stream& s) const @@ -502,7 +523,9 @@ public: std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch std::vector<char> dummy_vector2; //!< Used to be vtxPrev bool dummy_bool = false; //!< Used to be fSpent - s << tx << hashBlock << dummy_vector1 << nIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; + uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock; + int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex; + s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; } template<typename Stream> @@ -513,7 +536,25 @@ public: std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev bool dummy_bool; //! Used to be fSpent - s >> tx >> hashBlock >> dummy_vector1 >> nIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; + int serializedIndex; + s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; + + /* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to + * the earliest block in the chain we know this or any in-wallet ancestor conflicts + * with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned. + * In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or + * unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward + * compatibility (pre-commit 9ac63d6). + */ + if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { + m_confirm.hashBlock = uint256(); + setAbandoned(); + } else if (serializedIndex == -1) { + setConflicted(); + } else if (!m_confirm.hashBlock.IsNull()) { + m_confirm.nIndex = serializedIndex; + setConfirmed(); + } ReadOrderPos(nOrderPos, mapValue); nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; @@ -590,7 +631,7 @@ public: // in place. std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; - void SetMerkleBranch(const uint256& block_hash, int posInBlock); + void SetConf(Status status, const uint256& block_hash, int posInBlock); /** * Return depth of transaction in blockchain: @@ -607,10 +648,18 @@ public: * >0 : is a coinbase transaction which matures in this many blocks */ int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; - bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } - bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } - void setAbandoned() { hashBlock = ABANDON_HASH; } - + bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } + void setAbandoned() + { + m_confirm.status = CWalletTx::ABANDONED; + m_confirm.hashBlock = uint256(); + m_confirm.nIndex = 0; + } + bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } + void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } + bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } + void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } + void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; @@ -750,7 +799,7 @@ private: * Abandoned state should probably be more carefully tracked via different * posInBlock signals or by checking mempool presence when necessary. */ - bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); @@ -762,7 +811,7 @@ private: /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. * Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */ - void SyncTransaction(const CTransactionRef& tx, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* the HD chain data model (external chain counters) */ CHDChain hdChain; @@ -897,6 +946,9 @@ public: bool IsLocked() const; bool Lock(); + /** Interface to assert chain access and if successful lock it */ + std::unique_ptr<interfaces::Chain::Lock> LockChain() { return m_chain ? m_chain->lock() : nullptr; } + std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx*> TxItems; @@ -1042,7 +1094,7 @@ public: void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); - void LoadToWallet(const CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void TransactionAddedToMempool(const CTransactionRef& tx) override; void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockDisconnected(const CBlock& block) override; diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 22aca65990..2e1fdf4f3a 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -5,10 +5,6 @@ #ifndef BITCOIN_WALLETINITINTERFACE_H #define BITCOIN_WALLETINITINTERFACE_H -#include <string> - -class CScheduler; -class CRPCTable; struct InitInterfaces; class WalletInitInterface { diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 5364ac4b8c..367d0f6916 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -81,7 +81,7 @@ def read_logs(tmp_dir): chain = glob.glob("{}/node0/*/debug.log".format(tmp_dir)) if chain: chain = chain[0] # pick the first one if more than one chain was found (should never happen) - chain = re.search('node0/(.+?)/debug\.log$', chain).group(1) # extract the chain name + chain = re.search(r'node0/(.+?)/debug\.log$', chain).group(1) # extract the chain name else: chain = 'regtest' # fallback to regtest (should only happen when none exists) diff --git a/test/functional/data/blockheader_testnet3.hex b/test/functional/data/blockheader_testnet3.hex new file mode 100644 index 0000000000..882133aa2b --- /dev/null +++ b/test/functional/data/blockheader_testnet3.hex @@ -0,0 +1,548 @@ +fork:0000002043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000000943e54375082c03172552ae841bab31ebf2463484574f6ce6fe9c3723e3defb719a485dffff001db8b63209 +fork:00000020da2809ab72cf2502ecb29137dbe63e51fb82fb5babe6c9530dd86dea000000005dfcdc47012c19a2708b53e820c71b529f616a45529d48bad484948c84685d572d9c485dffff001dd3530a08 +0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b672 +0100000006128e87be8b1b4dea47a7247d5528d2702c96826c7a648497e773b800000000e241352e3bec0a95a6217e10c3abb54adfa05abb12c126695595580fb92e222032e7494dffff001d00d23534 +0100000020782a005255b657696ea057d5b98f34defcf75196f64f6eeac8026c0000000041ba5afc532aae03151b8aa87b65e1594f97504a768e010c98c0add79216247186e7494dffff001d058dc2b6 +0100000010befdc16d281e40ecec65b7c9976ddc8fd9bc9752da5827276e898b000000004c976d5776dda2da30d96ee810cd97d23ba852414990d64c4c720f977e651f2daae7494dffff001d02a97640 +01000000dde5b648f594fdd2ec1c4083762dd13b197bb1381e74b1fff90a5d8b00000000b3c6c6c1118c3b6abaa17c5aa74ee279089ad34dc3cec3640522737541cb016818e8494dffff001d02da84c0 +01000000a1213bd4754a6606444b97b5e8c46e9b7832773ff434bd5f87ac45bc00000000d1e7026986a9cd247b5b85a3f30ecbabb6d61840d0abb81f905c411d5fc145e831e8494dffff001d004138f9 +010000007b0a09f26fdde2c432167d8349681c7801d0128f4dfae4dc5e68336600000000c1d71f59ce4419c793eb829380a41dc1ad48c19fcb0083b8f67094d5cae263ad81e8494dffff001d004ddad5 +01000000a62bc0c08afc1d12e6c6a7eb4a464c848190ac0e44123d5fa63a9ee2000000000214335cde9edeb6aa0195f68c08e5e46b07043e24aeff51fd9a3ff992ce6976a0e8494dffff001d02f33927 +01000000f9e2142a93185496f7b21314d8b6fa736d0a30fa3a6d339ab3a1ba9c0000000061974472615d348df6de106dbaaa08cf4dec65e39cefc62af6097b967b9bea52fde8494dffff001d00ca48a2 +010000001e93aa99c8ff9749037d74a2207f299502fa81d56a4ea2ad5330ff50000000002ec2266c3249ce2e079059e0aec01a2d8d8306a468ad3f18f06051f2c3b1645435e9494dffff001d008918cf +010000002e9afd58b91f15c3ec9eb0f01ed9d503134da1918b6bb416a9920e700000000029fb495afdb58f3a26d1c90fafec93aed840e2fa37ad6173ba1e7fadb7121ee57de9494dffff001d02e7f318 +0100000027e0ca29a9802c0a2390ecfa90a9bd814fecc54446510e155652dead000000007e8d5344557575c8f018cc62a32e8e0bd80638643b4ec34945ec4662fcab138142ea494dffff001d04acbc3c +01000000001f3ada9b561378e324e80ee68facd5d232f72f773b86328393054700000000eaf3be35e3f0ace8b6abdeb5509d72999eae2329657238b53fa437e319c8e96b99ea494dffff001d027801a8 +01000000781bc7847e15c3b936a6a6a178e38fa29ee6e4916a8a62e10795c69200000000d44c3443fa8bd88bf32b94b9257f09ce6fb6ec0d5420504d631568f8685200dfa1ea494dffff001d01f781d0 +01000000133991a938b505ee8f6f347f313c3372d82a9d8b42b08b0dd0fc086400000000a0ef58c239e0197a65aa248c2cf52c437d8c8ea30d1b835e630a87c941f7d4e9adea494dffff001d030ef2e0 +0100000028d34cdb13e555032e4bec55fcce3d0fef8212803fb1bab851e1259400000000542c71544b9f28bd5a6fec95ecd509ae49d0b04f8718c685d0751f71d38285d0c3ea494dffff001d056b3115 +010000006b00cf1ce31b33fe1e2c4648a0834dedd972ffb2a2f341f75ad7cbc400000000adebf7afcbf176f765aec16b74d92896f55c3d65e14dd1a8becee0871000291751eb494dffff001d006f85e8 +0100000043a78ddf30a2d28a42cc66f90d13cb8211ee0fca9dbf8a4cce8c19fe000000004edbd2b89cb6d6fd69b575a62bd4e3103b1e0ce19e31bccf9a093ad8ccd753cf7deb494dffff001d0591a0b3 +01000000489ac81592595a4004e14331cb096ffef12b1daf709f6378e9c3558d00000000c757bebd6f2c2c071a3cf739a4cf98b27441809790a5cf40652b46df8a98a473b0eb494dffff001d011aedb6 +01000000a9c570a45d959023551f9a694ace9c12206174f21383f30949ca3b9b00000000eaf93dbbfb3551a1ff8b6bd5ba4cea7508e790c23cd07b9d9e791936a79d5fd4b3eb494dffff001d0385a7dd +01000000d35d5fa860dc70c8bdaf12f18e16d8b4cc29d141c28d59cc317fe5ed00000000507dae091a9657b6c073863ca71ba6989a2cf4417fb81e940668568a35d34a7119ec494dffff001d00effec3 +0100000073379e3ff3dffd006e0090e52ac571a9a309490a23e64d15f8af291a0000000051f1c5b2b7c8f980e7715b4d3ce0180f99c44a16fc9c00ede2f5984b8d7cc22d16ed494dffff001d0082467f +01000000869845a3343adddfa5b1f534b507d9b67c3685b0f1d89d526cdbd34200000000823623e8c6fe2c449065d2c0ae57aeb4bfd8e9687126a6c99d1ce916e2fca63f4ded494dffff001d034f940c +010000005da49f64cf0025ab1111651d94748b00bdb00b780744b88b42f962c200000000fee9e254a5a74c858297e89ebcd2305ad2707a8acc131ad07f6abc0d8e38def969ee494dffff001d05c512e2 +010000006a65bc120bf3e6dfadc3b9543e48f8876cb826aed0d8f809bc34bb220000000090f489f48c88442aa7d9250f743b386558ba1fa2e7e240e5d32195d56cf1c34ffff0494dffff001d014394ce +01000000e3f21ff9cc51ef282bf6bebc90e6f96968a36a704452192724c839bd00000000b6d553c98016b66fcb4856ffedc13a2de720288d4c2e8fed86206981259791a23af1494dffff001d017f7044 +01000000e16daff1b16a81a3058d982e79550c9c9ba84a207a8b84ae092eb4b300000000f2f34dd423f99930aee95815b2885906f9cdeaba04a9bb076c1f359c2031732059f1494dffff001d01b322bb +0100000059ebd22dc26158414c60868355e78ab4b6891345fd97602f6c106d9c00000000e6c5bea3888e891bc5f9f8fcd166d332071d3b434e933763fb20db50e47dad3f5ef1494dffff001d0067a0e5 +0100000015ddbe82f202b27febceb00547dc19653604ef434f080848f22e3b0900000000e26e8971a53396413f0f39b88a697f593993999c8d07fa2dde608111fb2ccbd3daf1494dffff001d04362f37 +01000000a76595e37692f85d5de0438da8e75b5f611fd7b7071816b9ace8ba2b000000004cfb6d8faca8e8e77a71359d2cf0d12d2e52f266591f5fb807aa737c90869d2a81f2494dffff001d040e87a6 +01000000a774311853f32f32d87081529bb0506d5e4e90f7e455bb640081215f00000000228b387354daa9e5d38201811fe746591ab08a66bb3c4fc796a45535acb8c61baaf2494dffff001d01ebfd80 +01000000d0efaa4f6924659f1e0221f910e99f6fae76b36b759b212852d1343a000000005e40d3b65982e929433ee02037a60f05b62e70e6d51f608974fb1f2926169398bff2494dffff001d05c41c3a +010000004c600547d8ed4b9dc946bb455f64917131dd98974bf2dfda05afc3a800000000e5b01ac4a611211847b0777b9ba9e396b0b7348ac401041e4fb6168ed091691ccef2494dffff001d037a60b7 +01000000f571553e5aa32b8374bd5a0f3c58834a46e05727b64dbe62a942376600000000a91cac1e92c7c597af565594565f5b54e658fd2e022e4fc35eb92aa165d9aa4fd3f2494dffff001d02687811 +01000000d8273e218df68e333782ab3b79929a8609404bd85bc225b46debba3700000000e91f9128f70a0502d338221316a0a3a1f4181821f9d2220c3fce7b5cf8e305d514f3494dffff001d01f22226 +01000000ad615025da247d14a4f092f21c4fddc6d1a84e0b4b0929f9ccf81c180000000069cabd55fc74596566ed1548b6b4ac23943d33e817028d8bc8695a7bba82256e6df3494dffff001d00e066e2 +01000000e7da808f2e62c3ec1b443e2979c972f5f69f32146cb4385bcb6fdca2000000002f4092fa4879ecc1a39471c41f8fae20f10ad2204bcda34e79ac37cbbda973757df3494dffff001d0316a8a6 +01000000928b86c36f27d22c7d2baf27f31b50bbec7754d33d12aa37342723db00000000ece3fdbc63b66327abb251165ac0e19b1a02fb79295c9b6eb8b38b68c1c59107dff4494dffff001d05bcddf0 +0100000088c96b45d3e252cdc38780843e3679d74e858a1d218f9e3e0866335e00000000432cba2143d62f6349faf4f7956f7354733518c22a309284d96236a9d8c9616e0ef5494dffff001d026ebb97 +01000000885d76fd42926155ff9ebb1d3c41a517d7beb70564b98c3608719f740000000011a3bb257acf328a5bf1258bbb653b88a40b2d2a66db695a1ff1aa9d31d5186590f5494dffff001d02bcabdb +0100000093f416076c9b2eb0d147e8ce3bac03aa0784e44a6de6fc6f61183a2800000000be46ca90dd31022ee15f74c221208eadf1840e208a6adcaf127a60cc61d959c4d2f6494dffff001d02d68fd2 +01000000f9da861df4a86158e751c062e0a82d3c90e9038330f6447df4aee3d000000000972534907cdd1bc56f21adde0850dd17d988711a5c36ff81ec84610b4f762e9ae3f6494dffff001d00e96635 +01000000303a04a9f551e93a8ca88ceaba1c1d28892323564988ab7f99a9c0d70000000087a11f1aa5769968461b251ca9229f59acad999284e29976331cc13da46432364ff7494dffff001d012c54d2 +010000006b82062aabb19e4cc89addeded5c65ec28684cb54d30d463719f094800000000a98137fab92249e632091c38e3fe4c7fd8bba11bc956fe83c41357e217929e1e8ff7494dffff001d01edcd15 +01000000f7332e78d21adde6e52aa20362c71c1fd7bc745b3edff6e72497067500000000d521571198d3d2c4124b8bd9ba7842716b3bdd93236a92ca6af7c0a7adf9633c2df8494dffff001d031b1c13 +01000000c9ebcb8ea6f7e6a611117d0268639625cee28155c88708b4c09d4aea000000002d4c50f85979f0d2491d015206d867b273b353c253a173abf18dfbc5d96d088e50fa494dffff001d0397f4fc +01000000562c889f95c49db05f6a33277aae9bfe68e92f00a5d6f67ac8c366a100000000b7b0896c4a53b6aab15282cf53e1119676d89be0eda11064cadc40dc8ec194dc66fa494dffff001d01f0369e +01000000d41cef71d625aa5380f6cdc6452c67951e7e4f5b27b7904b4c2da413000000001c1e1096ab473aba614651fb98f47a375c03ec470daccff39a2d2d65bfd881b47bfa494dffff001d02454a15 +0100000095359a35957b89dd268576d562f49db7939baeda6de4855426ab5d9c000000006f68039da08fc314bcc71631be6f4b2ef5e0a2f9491fc078b11fad3dd49ac287a0fb494dffff001d02fbc213 +010000007d505f65addb5a3b50eb33cc5cd3bcafe03ee597c4027166aad2d2630000000090d221539ba3dabdcf0eacfa9d63f272c59dcc07e5e193271a24f4c11caf2c24d9fb494dffff001d05542cd6 +0100000013d5bb77b9235002acc75014e0c061c79de1752d3edc6859b4c0df7a000000000d17f332abe46c092877537ae764aa99e9b25d6bdd94f4007eac43f4861cf4675cfc494dffff001d02f62def +01000000612ec5ae40cb2a58e18d3124fd70664a4fcb9329f7d268d4bcaefacb00000000f7bc31a9984831440cfae529df94ba1680fa15b4490454454920ea8947af4a3765fc494dffff001d01c6c1af +01000000f13ce1836e92f0d12e4892eb229cbf6d50b9b2080af16b01f00b25de000000003f8f64092fddae84dad92736fb7d350e25bda6118bb1c660b06a4a5730fd352815fd494dffff001d055e65b0 +01000000ee24ae636bd70cadac603bf8cc631369bafbdfa8ac8effbc7dcb0fa5000000006faf8082545aef971aa4f92bd411947929c4949362300cd1302ec8bf74091fbe35fd494dffff001d0139a790 +010000006350a3cd2181f0eb75fa8c4022634ad85b6b9e1f9b7346a2eef5517900000000a09d738f407e2dcc888e8ecbde93458d8599720e0359a380d503ee56c688ef72f9fd494dffff001d02661caa +01000000a29d92b014057dbff2775280b8bae8b3877fa9a345f4972034c0711b00000000447bae5734b6c67dc36ae088869c18740a836d7a9feff0b349efdd7373a21358fdfd494dffff001d01b13c06 +01000000dcbf4a9455bf33482c2e17640cb89711468c0def1ba9f6e26e5a4aed00000000b555247649a364adb88b07afac3aa2f9ddf5c379154cb17d22f69fd74c84a2e24afe494dffff001d03144aac +010000000429b2aa02abcb8275705dc6482230dcb7c8678388fe7d75022c1ed800000000d5efd425dce6deb5750306ce8a0e5d045e0a607ee0b530866833e956dbeea0575ffe494dffff001d026ba2bb +01000000871d8a30b3b39373d71fb9c5c7d8d7a3c005ad688d9c8ca974cd330e000000005ec14d2b9adfccae05f78556b12fe2ca03cddd8bd67896fa5c6b6fb090e90177a3fe494dffff001d0147b26b +0100000019fd8a4e03a52e43322506693b696af00b828361e2a0968898708e43000000003853a270a7d9e6ad80fbf911146428c7ed63b2a1154df748f5a980fe20e9e8e2b3fe494dffff001d04538437 +01000000d250d67bd2679caba305e5e13698c7880098a1a4383c2f464fe9006d00000000d793e7c2c9416eae3d8d81046985db5f3d1df7922e869fda6ec5b22181ee46f725ff494dffff001d02391748 +010000009d83b8dbaae3dee980529b6dbd307df153a22b5b0fc35ea694a1faad000000003cfd458875824fadce673ca8c3125fd12c36b7230035b5f056a12d4e0d6b7e6b7eff494dffff001d0448653d +01000000a7d1ff0415c1d1be8a11c7dc20557e001d2fd96c0f396f52969e330d00000000ca20885f915b1d3c5e614cf76067fcf8d9894204db63664d512806ceab58ea27d2ff494dffff001d0327cc00 +01000000d98a10f08f4a7aa698ba8564a85bf821b747c7a44f656fb2e3b9335f00000000dad4bbd7663a69c07ff9579434972bb203d0d78d07858ddc7ca32513101c964e3d004a4dffff001d04df7568 +0100000031965a378dbb0d024df444b3ebf894f370d28df8abdd54565b598b9e000000007ad93f6eed4656fa7c0a606c08b067be35dfa5392eda5d77f133dabdfd08739794004a4dffff001d04facfab +0100000004cc9e4dad8b5d2166fb175e7bfe13ad56018dc81255d335336b8b38000000002d011ec2248c2899ca72a01673535807e2cd0fbcd4465ebafe2a002f3f6c9ec1a3004a4dffff001d04d4b0a1 +010000002093e7853950d588f1a36601643a97b7e187f6b7b274ae43d82410a6000000008ea10bf2d0d535c64d9db748aec8723566b270e0becacdb07438273654e59feea8004a4dffff001d04d4db49 +01000000eff17d4b5360e16151019597f732fc8f6e3b19ecf0c88e2ab1a2bd3a000000005d9b9c1d04856052c8f01497c9ce05d01c4c80c1c4296b15549ace3e04e2cc24e2004a4dffff001d02358897 +01000000bf5ea56c32a49508f0987cf0b18513d285edbe987b0dad25ceef3f1b00000000ba933ef648e2544227c7db41de45f0cf39aa612478c126e5ce5393668e230f1213014a4dffff001d044c6575 +0100000091ff63e6ab577bdc347fc87265fc316d53303bf28d0294a24791143300000000a97e4a6f79bb595fe0239f31549d5b06e13e7d5062e3707ebab0f38f0df5d1832e014a4dffff001d0083c449 +01000000bc749fb377c9a937702bd4ff35d376b43f5bc56726029a93a38e8232000000002bad8475b38ccd82eb98377132453c72706ae9626de3f913e8da2cca6795481166014a4dffff001d0594c61b +01000000a02349924ea393906e7fe3ffc2b9d152fdf55bcc1bace5251517168100000000448e52c8902abd28f82f3ff5eec8c97ab16b71b61674713cbc4f9a896f733b5ba5014a4dffff001d05c09af9 +0100000011f5cea7297afda99c9a141c4d438ff7708f451b571b4aa57b4b69f00000000087550789e53e3c66c4ce24d145f615c95f593d7557fa3ae2eee2a1f985549f9bfc014a4dffff001d00e035ee +010000004ba4c615f54d1557c5f691cb614cecb9edbad0011ebbd3b94fd458ee00000000c9782c5584bbe7c3a51f048dbf1e428e0b0e092607ea6514c401f13a45afd2b6cd024a4dffff001d049ecd2a +01000000718886f10113146ac974a449c8b5bb205e0307f42d1b81990cf1991e00000000b88f0b4d80b631a24a15d7c640ecc15ee82188bb16d692be42467e21b3971c1421044a4dffff001d03e07f40 +0100000081fbffbf5c27e4e908c14bc8f303a4d91bb71ae05445e839c531ec81000000002aefeed1a7378f70e1f434bde89af706e1777993d25d9a95f5b4f4a0d83bcda8d8044a4dffff001d02cda9e0 +010000006d7f371b437cc054548511f0f9ec50b6e04bb4f3a4915a099c3bad36000000005b72bacbca2f6bb848d4753733f6427849b1aa4805b244e2751c38ff8d3e339c41054a4dffff001d0018ffae +01000000b82c2d751368b79004a8e15e5b9f1bc7f620d5de2d6105d83d8ec2f6000000000cdfbd9bd57b78356abb0d07551b0c5c20d5258091604be05674d4d19455df2fb8054a4dffff001d003c4d86 +010000006efef1bc9d35256bbe500ee9c92230e0f987296e373e20ee7f3da4710000000030a2d3961a799e229a81337790254ebaefa055bb85ec7f03e5c8e9c3250eb35ad7054a4dffff001d0514a76d +01000000e789ed685960b594652b9b83a28ded0995725d6ad82b2f358058cbe80000000035b73fb2084e2e8faa28334b17c12668004b7cc8965831aaba65b38b7c54ba97ff064a4dffff001d055247e5 +010000000606ede205a1002e0995f8d009de821d488e22fb37167bcc9120afbe00000000f6f0d5c7f8204a7142f2e37a7cb406e0a57fd7c9784c12111c55a1cde5418ed11a074a4dffff001d01a5f8d1 +010000006da3e0f7fa132425557519508e4e97cbec301a33e25d4b65ac054a0d00000000d657340c13f9f8accfc3af62529a25d3d9e656caf2993fd3166f73850f397c228e074a4dffff001d0309686d +01000000cb9c32345bb264b3b2a6e96c8843f65c9598e4948737c569c0a00073000000000894a15ed694c745d87d2135f90eb9dcf8b454937b48c82132d52405e0d4c4689f074a4dffff001d02d5845a +0100000095349f59ed0c0ce5ab0de43b9d55fbc7b8afac7eb6dacebeaaf290f100000000df9bf22064b03cf08e658257b5d19662fdbe3d7acd27894cef0d3f9ff5a657bbbc074a4dffff001d00a13d79 +010000000be1a15c8ae486da44f0585514eea60780091e52e35f838cd1572bf50000000077333896bed3021acee751bf0c073d95bbdc99125f4298c052db8998bf7c3270f9084a4dffff001d008f86bf +01000000c0b7b630de7b1bd92c4be9d32e19faeadd68f60316ec97db96eaec0f000000005e3c98a964d941f4abd129531d0ee81cd5e7dc098179c3188ba36ccd1e5f9fff7e094a4dffff001d03d8a118 +01000000ae3c51439dc8f2bdd807e1d88c25a5a0b1a3005bcbb50bbb4e48493f00000000ac719d460d514cc4489597ac78e995276865ac07d7606a313c12b16e769b9294f50a4a4dffff001d04735f73 +01000000ce12350c698e84085e7b62c039249c63cb6a6cc9404776c9cc7fba8800000000ca128b2e74fafb345ad249d61c538f75a2a230bbf2266d470d47986555894deb370b4a4dffff001d001c0a17 +010000003dcca8252a636d4c0b8bc4ed9285b749434aaebc29965fc691a635ea00000000baebe5ae323c2bc5c316f6a8f1947b4666c7707c9ae127a03e6429fea7624ae53b0b4a4dffff001d00b4a44c +01000000f6cd7f4cec06d5c6aea54e64b45f049640680e4cb8249cf18587b314000000004a1b2b51da86ee82eadce5d3b852aa8f9b3e63106d877e129c5cf450b47f5c02480c4a4dffff001d02552721 +01000000c64e139bfae4adcd96860bbef3969b84851dc4d4fd8d06f16cd03698000000005e4b195fd24b314f69f7bb5b0139861b0f07a1286b8f6f42dbb6c82524bfdb93890c4a4dffff001d033b3297 +01000000a3e811a098612235feb5e1acb407cd98132a9d5f4dd99aa8d8b576ea0000000025a13ad0cce0eeae69ddb545bda3b490230e68dbf0687af3ddfa132caebadd7cb30c4a4dffff001d04a39aa9 +01000000ab5ab86fe14c9c765d8cdfd67f9bd5d41505f9f9f67e4da1851f099700000000beb6740250c060fa7b472f4daea18186d47e266dfafc88f088d7efe3ca5a2ac1060d4a4dffff001d0511eae4 +010000006ee9f67721369cabaf6e3e9045b2efb70bc1344256dda634a92eec5200000000d5b0833f82460e5ea635a31c60314eec20bb317f2cdaf354023ed4225e31f264380e4a4dffff001d03a74f10 +01000000b3ef61423747695daed4acef8980b5ef4c8feaaa908b3fdc6fbecdab000000004fd416b35e12b775e2899ba509a06822ed8b6627311364f5195d32ca6a314dbb3d0e4a4dffff001d0255ab96 +010000001c75c30c7bb6391ec7d94f6d52bd2aeb0b8f5224907b0106791ccdd70000000016f86bf97a3dda131f108ab4ab456d5ed3cc5e67eec631d8fa044b95c19f3449410e4a4dffff001d048e1c98 +01000000a8bb0604235d5310973b23a5c797442a1ae8c07b96d8cd33c191b45300000000322948a4806acfeca2b32248d0e183c8eb09d5e5ef48adf33777307635414cc05b0e4a4dffff001d00edcc80 +010000004d37f2af0c42371bb77b52cfe7f539e550e126631dbae6056e54dec80000000014238140ff083fe67f0d5c0627857c4330c4c96187964f42b680ec460ad0ccd6620e4a4dffff001d048185d3 +0100000087e774423a88647a4567d3a15e7099b7f271d8d846066af9776ef4ca000000000d90cd436a707875c28222178146cb93f6b048dc4e7555cf37b96757e3b90a5b740e4a4dffff001d030ccab4 +010000002f3be6a1d59b7786d8601330a47f030fcdf2354275fbafa8f4c129490000000098a23359c17ca2678e2039c8ff9081b18c4913749c9a081ac3f62958f09fa472e10e4a4dffff001d00c7fe6b +0100000050ac3cacdb94018a26258b82299da1307ccf3e2bf62a8f4acc19e02c00000000e09f513a024d3e13473d7a65f79073b36a90cc228613672d2a47812368ff42d1df0f4a4dffff001d007f726d +010000000e044393202d6b239c902d3f634e3dfdfa31ae439d339238ab1688e30000000055b5d3d496e196d624a471b818ba0b1778417ae335a544033536654fdda3eef6e80f4a4dffff001d03ed4240 +010000006396e6ba5ef4924d42f4f3114ec7507b81bce51e9e2eb51ca9c53c420000000049af9208af7b7a06d65ce1cfcab6ad9123a8dd7538fb0aca332c63429ff48d59ee0f4a4dffff001d056528d3 +01000000497b15826573b28c3e83a5d0c5ed30cf48b97dd6bd797849144ea2400000000018b50db063333a3261b9b41e887b4aa5b69becdc9967550507c120e22a76496710104a4dffff001d0408eda5 +01000000f5ddc74872eb899f5113e002f642e7b507f871d99a9900edba304aa0000000008f6546b850a14744afc3fe55f76f3959f40799bf4dcefe01ae4dba5903cf2fb553104a4dffff001d018f658e +01000000918a758ed54c9f495edb24ef3fe0f4432ede25853c324fc0f33a458f000000007a8e49b22114f17b5933fc7a8005421ff8370b8c48fa04c24323e91bd02d701492104a4dffff001d053e6e0f +0100000035893f7cdeb0e9af7d9fbda1584ef6d5219dfbb141b07b31257a1658000000008b9b3abfbe24d0e375deebb5f41e74949203c00772a678ba69c1126156c5489bfa104a4dffff001d012a69fb +01000000b6f8c48e94ca346b12373281acbaa08fa54d1cfdcd9c01e020cdda9f00000000be5b4753c6062e3eaed75f5412e43d6dce8d242c5816b436689f795f90536f28cf114a4dffff001d01b6887f +010000006006db00d70ce04a9940c203dc865b3c5d070f8c2d1295498ccd6c32000000008bc41e410a44b764dacb38c1138a3ff2c038a188a063509c6fed4aacaae72ee67b124a4dffff001d009531e5 +010000005e0dc170558d7b2872ddc85f481531dd823dffa66cc620c065adef7700000000e82e91ade6f25c8c6f4c053aa62d94926324ced07ba2f3aae072b13a2c5dc5f89e124a4dffff001d05ae2281 +0100000056320cab20bc1daf4fe3cf0115f2436523e44c40ebcf8c18e6b5822f00000000ce415eab9cba354ae042c22ac9f06c1a69d7a5dba67136fabef93d82f374dd3e01134a4dffff001d05a0d736 +01000000ccb063ab7d74a4030fd155615f046f95c8068078557568ce6b8092fc00000000ff33b27214141ef3d183b1d2499666c8635a57943ef5f515f4e60515f9ea0064c1134a4dffff001d021fa95a +010000007f6d7a61bd46dd27be404b8c883b812c2899095462591dcd75a96f1e0000000091777c00b7168a888d7a7db4b5f78758129e79ef909f92a84110b9f33f9c4c5505144a4dffff001d03526c4d +010000006be8cfe3e176d34d1a46f68b7d20a01ad3f9e2aa6f7540ef6a32573c00000000a870da2f87071c1366a22e77c829a6b85d745ab2279e0333872518d58b8dc0181a144a4dffff001d042c5db7 +010000009cb87223258aba43742de401d0157ee2b4057da95b23e1665880725700000000d54d368cd4243da3793fe3ba2af1570dd44a905d77ecd1a5cbd07cb8f72ad80f30144a4dffff001d048f280c +0100000052b771a3a85c26bc796ae0841ae894c6ad4527b062c94812d98b9c5800000000eeed0f4d975db8f66788f809ecf8c351d19ff5805276ef31983bc5682548342d52144a4dffff001d01aa3be1 +010000007ca07eb5637ef7696d7bf985b9114de19317a9abdfbe4ee79d8bcb1a00000000d7172956946d5547bd98c6a7040d353c3cb6285fbad096a1780f3b7503539adf3b154a4dffff001d0333fec4 +01000000326f947390bf03abdda16f673d26326d4b159e0b7f732a67286ba8e40000000085701296d47b03f388fd85431c2a9fc817afc9b24870a9a7da850d3a43a8154b71154a4dffff001d0164b7d2 +01000000a45af68df42f6f23ecc47e1cd0f41d47c1e5a26e8343951f9881e51400000000b7c1565d19d406d8c56448571d7e7da2ac559bf9b43887e16e4f416276ccb99b8e164a4dffff001d02ec606d +010000007eaec4cfdb152a8ecf8ec155b7343e2cc04750be00b96c61a477e57c00000000096a9263a5008a48e1c2b527b922a81cfc269fd401ce429976c587a951ff00798b174a4dffff001d015bd123 +01000000db36ec19328691975cb8c6666866be64b5be79ae42bee9fce3b5db5800000000b5e73d7d102476db3ca2379bdd891b311140b49626ef42937356ccdb8fed589103184a4dffff001d032d07eb +01000000d8f8a6686ecdeac529caa3ab9ecfa84a5fb62b06849ee09b8331d89e0000000038b408676c2a78fc63ddc1807804d17e8ad9433387cc3cc0edd68c07e4a714b610184a4dffff001d0437e51c +01000000c54675276e0401706aa93db6494dd7d1058b19424f23c8d7c01076da000000001c4375c8056b0ded0fa3d7fc1b5511eaf53216aed72ea95e1b5d19eccbe855f91a184a4dffff001d0336a226 +01000000bca72b7ccb44f1f0dd803f2c321143c9dda7f5a2a6ed87c76aac918a000000004266985f02f11bdffa559a233f5600c95c04bd70340e75673cadaf3ef6ac72b448194a4dffff001d035c84d8 +01000000769d6d6e4672a620669baa56dd39d066523e461762ad3610fb2055b400000000c50652340352ad79b799b870e3fa2c80804d0fc54063b413e0e2d6dc66ca3f9a55194a4dffff001d022510a4 +01000000e846583e9bd64108b3b89ad3883bec7731ddf1688a4cc8f79530fed800000000d2954cb816c87a9572bf822138dc84b5f6847fb502cce3d6073f9ffe40588571a1194a4dffff001d045d675b +010000001d72012c553d72f1f75863310ac0450e53a9e9026b9bf9556ca024ee000000001b7142acd57304290a2ade0e2c96d4fbd3ec924a02a5a0cd30c04f0e96265423ef194a4dffff001d041600b6 +01000000d2c5dfbfa04c7b67457c58f55a8d190dc5f8ec5ab94af969dfb748ba0000000069492041bb66f32c9bd69b74e7ba9bff6d4122e931eacf9c89b45eca2c35eb25211a4a4dffff001d03bca431 +01000000880be932720bbf22f1b14da0e6d16c2773f83699935d390e8621533f000000000f5d2500bce42137fe905225ed9a7380eceb7445c89011bfcc740cf2e9985a034e1a4a4dffff001d03d30924 +0100000000b0b174d61c08a92313345717ca7776a75cb67b77662c04ea7d3e2b00000000c8ac0a2fb1c01e0e0a5339d296eb072b2b9f9cb1d410a1fdd69a2c797094dda56c1a4a4dffff001d05225e37 +0100000089535760639df16a512f9caed73be0edf8c9b5466fcea14336f4a1bc000000001527b6224d45722c8ee351976c69c8fca59c11d3daef7abf1d189aab0e959f7ba71b4a4dffff001d0551b67c +01000000a61d5d887f8fd4c86f7111c2c5a4d0d593665b527cdf84dac7a0d57d0000000069b7df87a13603be78ccb048370aa1d2da0969f3b1822791d24aa921f8e268ffc51b4a4dffff001d055313f2 +010000007f678f2bbdae181d396123431faacd0b956633c30a55a9595ae6657f0000000085daad94e57797b9340c299e483531dfcd0f3c6996da98ffb2ab31bbe34e346a001c4a4dffff001d03ef37a0 +0100000054112b758ce49f1fd22d613250599ffe92c48202b6a477b9289f3d7900000000006302548e973a0d5764711fe84e1900dccedad0de9f054fbaaed3735b70ed62391c4a4dffff001d057c8c98 +01000000bb142886ff32916975d060c649c9119aca0b47e3f169acd3b7f1b9dd00000000ff166532d3f30299c5a82856e3411957dbe35fe7e17c4f58b92b2ef12c399dd7d41c4a4dffff001d04a2a121 +0100000029a936f51d08ad1f1353890300131fb7c04e20606eb48197dc863eb200000000c7bffe64778d6b4815226c6aab915985d8937fb0d3aaaa983bb513c69305caade61c4a4dffff001d0514cd03 +010000007d16758418920cd6d81283aa30108f20f37dc7114076e23025bb881800000000bda8c051f6e99590cbea0919b7a4189e4d3620ff3f46caa4b797fd52e204ff1fcc1d4a4dffff001d047ea807 +0100000087a6243ae1dc858cf91caf8a1f92dc473bdb14203c573b9d9bc134cb000000004caa084ebef276e6c454dff401271b39e55da21a8cd5a3afdf2d0e0f94b94a2dda1d4a4dffff001d039d9a6d +010000002d0a1a0b18f1f74ee797beef1bd4766a050a3480b5d7457303b5c54e00000000955ec547d5ff2bcbb3c9f108828e431a674576e1de0b8da794bfa3a70b794281ef1d4a4dffff001d0019c619 +0100000060500ea2003736b74596d4a507f5cf001daa55e7c93b53c77d32a32e00000000c545bbc6ae68433be1fbbf0ebf59f22751af853d0a6fd6c944458aa73ec7a014f71d4a4dffff001d03d8b5ea +01000000cc3d1b428029cae46634a9b96857475b2bc59619b8408e615f65b7b9000000003563548d04e24c89e7706a3f4ad681bebb3017133e87f7434d824db4e51f479b891e4a4dffff001d038a2031 +0100000073da9ccb3fdbdb4a9e3723a4bd5270c70ef3e78f448fad7e77a8eda0000000001f0a3749af61eeb59aff1499892c6641d1a464a26c156608d02cb74c264786ccfe1e4a4dffff001d0284512b +01000000ef5a98df2a193c1f8a5c271dc4d45de465b7122dfeefe96fedad105100000000cf330295467623ec1378dc6fa312103ad8a210b3e1351f2f4b6a57ac43fcd472071f4a4dffff001d050ac986 +0100000048256cc5b9ec6e7a12c378c93c1dd2ada859c9a9997adca75166c935000000008e3430573cfde2f3e1eece8aefe661dd841bcb665d35832415bab4f7526785229a1f4a4dffff001d02ae4b52 +01000000eb5d7c4b706d8891ddae3ba5bb57fcf509689fbc196f3ef73837f27b00000000226e90ccf96f41e04f011e69a86e18e96c09df6fbba19416132247f1d2a6e4073b204a4dffff001d0488c17c +01000000e5214de98aa5cdf1766c5129649895816f49ec82e93c4bbda787897f000000000abd68f73823585582e65529a8739e90bd943fc4e214ac00a20ca0369b70d45d7d204a4dffff001d041786ab +01000000fd4fc04cdc29aaa117b16b2420aadb9bb92fd19ef2a7aec3c40f71250000000078e5266df52051011f39eb29939c8782564563c20b3856f7aaafa6dc52921359e8204a4dffff001d0205fe6e +010000007252c67173d343874ecfab4d5f57ab5936f2d87f173047c99c20e73500000000d2b61f338da6ac531884c623db2804c0d7eeb84263b501524cbab6d5edfdf56f00214a4dffff001d0027a318 +010000005a7746eb6d1d19cdb24466e0a87a23b6ba8c2e461ba8928edb84253b00000000f1b03cf0680b9ef33fd311f6bbc6db3f1c164f9341f48a02df1905cec4ce241b2e214a4dffff001d03246ebd +01000000e93f1fd6ddca6d8fdc3ef50fe0f31769200f8fde592a0d5d6f8e1d290000000078966e9f0a2d4452ab2418249fa6fb1a325a04f039d015899141a82aa5a6c05c92224a4dffff001d01d8361e +01000000dd5f3ae3d2c2876ffbfe0956b914fe72750b160b667b5bef5aaee61400000000434d2b0f298874c3f6d8467c07dea6883a650de00d48298cd6fb48e8322e1058b4234a4dffff001d0161f3ff +01000000f814899e7f50c4494806f75523c9d8ea6c0198d13f1f14431fb541ff000000005b1015187325285e42c022e0c8388c0bd00a7efb0b28cd0828a5e9575bc040011d244a4dffff001d0579aa5d +010000000f72c6372c87d91f2df95b0c9e91cafab596f29270cb01cc67186040000000001fe6898ac074a680fe7458ff87a03956db73a880d2ace6539efcc43002bd97ed87244a4dffff001d0434b3b7 +010000007cb39114d2372ca1d5dbcc3a1137cf9314a666349dd79b268be1f15100000000b17cb4572964d7c6d671e7cc67b04b9fb1b68b31e52e6b4f956f3a0b72ccc4ccc5244a4dffff001d057875e3 +01000000f8d8634fa1aab0666f63fbebfd61e0ffc1dcc647e218414c528e17dd0000000073a2c54d536c19f0d09156efbad18ca6f96b1e9f3bc8490342958f24ed8fc32d28254a4dffff001d0130c923 +01000000943aae5118b0abf4ff55e050b234c21d223871e815e9fae6fdaf693c000000006fd85c0213cfe9863573596a4d5f1509ac41a91b572e6c1bdafe46d9249a5fa4e9254a4dffff001d05878bcb +010000006c6040618ce7a449cc26ad0578a7c897b4464ee32260014fc5ce6bf20000000096567fa4ac682f9bec7e646452d3bd69088000b19bf7a90eaccc197b632fa79bf3254a4dffff001d02a22cd0 +01000000c067deb4fa218c0f26a247766a969af8a475e5c88c004c300c1c69b100000000ec5a827a707edd70451f070665bf6a9e6f4dd8f815b0265296790f24024142b181264a4dffff001d00e76cbc +010000005cba08b87cefedceed1e60297564a3eb9e9e2bf942bf63f74fdd7f3300000000e3082dfad468d5c0c8e2f8857a999f898081c8cf59e48857997152445a57218095264a4dffff001d05a30a7d +010000001dcc225203fab8d972215ad2311203570fe49707799e6871908e37d900000000cea1cec6febbeb980af51f052dc20065b95b2d65520205616a95284480a4219abf264a4dffff001d01b05d9b +01000000644fa81c1f8c64f08712b41b616a24d3e8af833a4f370c188068133a0000000069f2096bbede7015fee2fb307f7d7dd084641b7f4af5c3074dc7b2b6df03277c80274a4dffff001d01dcf233 +01000000ba750a8cc870173cd7f17dff4c23c228282d9aebc2bbd5ebd9449c7b00000000b28b51c3a1a322c8e29c2b6808ded7981dc085cd7fb529184eff6ce556e09ccefc274a4dffff001d02c039a8 +01000000e0f40d912882e77044fee84e325fdfffbb3aa0fb1fe6ba864d5be65d000000006141e05ebe4e62fc76d0c9f1e61a4d17e6509209309f6fdbaab476fa227f1f4fc9284a4dffff001d0306d223 +0100000003e8e5f89f6d5ee55c6e8ee0d4c1e88d8e4a3a5f05f0c9ec32d580e5000000004e2b02e05fea22c5067327060d3c00482569021252423d372cad30746408d0fcea284a4dffff001d0410f81a +01000000f389178af7b0ea88a83ae251392f7eb336771c8d7ae666d1219f2455000000005f98a8017e8458e6081be384b89f4ed68a6aaee5ac41cc0ad2331929e657deb27b294a4dffff001d04c90ab8 +01000000d8cec9c7b6dd3093ad29b64c234258bce36693f87a4167d6234342cb00000000d5cfb9095ceb210b374686dfc11fb8d8c7932c30b4a3916c7fa4fa7b760a0831122a4a4dffff001d01708f58 +01000000d850850454d9b392ab01e886bacb717a5615f8f70b6a4ef9bf788df50000000075f11e157a482aa640ea8dc7e038ffcbff0e9aa758ff092222fabc325b9c1a56152a4a4dffff001d021f806d +010000003cf1b7fbfa5978d5ce1cae5a5f454d41f840cebb72ad3d600f551901000000004503bb32aa7568d6abb1df7a05b80be6ffad47e5a55886488736b5c344a41d431f2a4a4dffff001d048df744 +010000002d7f6a8f2dec6f914a7a63052facc546567fbb02742dddb150427ab10000000031ea5d7ad7f128ec4daa2855b7944503da503818a75f049dd6980e636aaf59af242a4a4dffff001d017a6543 +010000008976cdb2c5a16c0929b45f29784ad52cf3db3035f112e562ed42447700000000bee2958fb6624ca2382e4a6a1bd7aedc2c58edcb7266a106c76d4504fd39eb89682a4a4dffff001d04d4a620 +01000000d6d7f91896014f1f4419628791a5bc39263704165d3e14e23895f57b000000001ae0ce43e200fa010ca331ee891ce9ee93d468c602703a23ca2eeb693c1e05e1862a4a4dffff001d025a8bf1 +010000002be250378c6001da52d435aaa1240522ccb14a94880ff0e0d1eee82a0000000046297804a9aabf1d08096b034a84364055573c45d09be862c33ae30beb3b5ab5ae2a4a4dffff001d01a34339 +01000000064ac21081e5a5c6509f634ffae17551bd322e46d3396ac49b8d68b300000000d15854d1e5ba349daf72089f470b24557a2be25105b7831a3f18a62fb8bab677342c4a4dffff001d050c273a +01000000ad1cbb656f4799fd8e9de9acb70a47d589ef5311dadade94c494ff60000000007546bbac9ae1c8980da6e8c154b368eb4df305b6f3f27ff38f195a13c9ee0484922c4a4dffff001d059b5c46 +010000009f0d835251ab6812b04939a5889a36d50f4d7486ef98a7f61e45f062000000008dab4ee487e33f872140c04372bbdb6c573b7e9e4ec31cb5d8dff36da17bd8f20d2d4a4dffff001d016572b5 +01000000d1105edb3d0105e862f3bc95a034c0d0815a79505b4b68fdcce9083e0000000061573706774bc7a579a7968281e10612b4551195e16c8051381cdd3a6f93f479292d4a4dffff001d039dfead +01000000d1e7c872bf92ed9d9007926fbe72d976079ae35efd6f81ba4101a98600000000b4bbecee818dd986e5ab82f36dbd5ccc29ab134614e304c0a397e14082fe7bb73e2d4a4dffff001d00b8c45f +010000009287b073f80a1a91caa1f664b8c9c578837f878e6ff04108db28d9c8000000007dc81aba2560e72756db13b1e427c9fb4bdec85486c65da8aca5bf968a5bf51e412d4a4dffff001d03a98277 +01000000a3f00f008ca15686450bda91266f8d01a99f345c7846e9e5bfcc2628000000005ac244c2a763cbc311a245df0d6f98a29e187165048a9da449d29edddf6b1923d42d4a4dffff001d02df0172 +0100000012ced3b143532ef3999faae421b6bd79c8ad62a4dc8db862cb05bbf70000000060e6e42fad3fdc3d353a22f1699b5ead453eb11343a0e603ffd8c77cdc773be2892e4a4dffff001d04945d60 +01000000f0c39624b690456af00d742f323762471854e1db5c96099adad5e53d00000000a2367f2d2214ef900583269eb812ed05d4a8aba11d40081eef999ef16af1d5ebd72e4a4dffff001d02d98796 +01000000294a2e12b2716816a62d761c624248fcc10ae22cc1a80290432c85cb00000000fc4f1ed498c5f31fe90b10389f12566a3350a5080db1dba1f01f8834e5813ca9e42e4a4dffff001d054ad0b6 +01000000774ed03a366b9909cb163ca0178cc6b42847461c4a672a537fb7aee600000000225666176205fe41ad268ba6f5d15633a5381c18fbb6cd2f83700ef928c58fd7362f4a4dffff001d04928537 +0100000083900a5d5ac952d05976df43ee7e280faf6105038a4afa75c4d8a2a50000000091cd49f33c9f5b51fc61790d25539d2e896e87c8c195cf305b499bdf42e029a0d82f4a4dffff001d03994aeb +0100000010336b8cdbaaa1bd6ba9b1e41e85e17bec5b68bc8ff043e5731ed09200000000fc5eecfa90d46aeeda36bb1a2f2da61e4f9be81253033ae55625d00acb13ef35f92f4a4dffff001d049e0a22 +01000000ec5b52a76675e3b1de2e75ad45d6498684a0e81092c5ee36e7fbf60b00000000474d71b72f905a8084842ce4202c2ab9795f3abcf51aa7c458349d21eb6e310498304a4dffff001d015d80bc +010000007af7e899b49494f77c351fe434981ad8b6352ad62527ed9d6d23a8a700000000bcc43ee02af281701574077d6916c07d6bd15cb96c623f6f304de260f616bd57ec304a4dffff001d03a0956e +01000000f22be93799f6fd1527bfa224601261300d38571dddedd804c0c7b94e00000000ca1cb89732eb51c0ace08a564445a2ee762a2cc819209886cd0a09993951544ffb314a4dffff001d03b10c74 +0100000099146a7924d99cd7a2c1e17ce7206712a8349399b34ec98eb706d3b3000000002c4fb29a89bfe568586dd52c4db39c3daed014bce2d94f66d79dadb82bd8300024324a4dffff001d04edf452 +01000000e5139dd4cf7511d9eddccb69d2c7aa0917cbe49eb660fe9293667aa80000000068e91b23282bef6f06f22e479587d03f6e1a2e4891b5d644541f1e10275e52e142324a4dffff001d03ef54c9 +0100000052a5c0e3a6cc34383a58d939053af3261acf266ba6cfd3165435bb2f00000000a32d9b10b7b75323db98d486828a5ea4f9e7a2609b76e496f6d86e0ab13a31587c324a4dffff001d0031025a +010000002ed6742b063239cad1841ce6bcf676a59c66e65eb4a0ccf68ac4eceb00000000efbe4df65aa1137ba3efae5236178fe6646d6eb96a177d5394bba48066a87448a7324a4dffff001d053cfc69 +010000004d4a3f638bb32808c3667937b3a0847eb780a3b3fe70875027adac3e000000000ecc7b73d8cd5d377d74d836bb6e3473478554a923154286ddaf6985948fd9d3f9324a4dffff001d05bead3d +010000007b86d803ae4a477ebf754711cab10b1b7799dcd50fdd6f25cec45e1c0000000033c06971fe80386570f8daafda6e4ab7e72a18624e211481e7b96633c625a52b3a334a4dffff001d01146b3f +0100000034231ba96cab51515ffaa9831930a46d1a8df9bb83d343690edd9ca000000000848736034ba62c9f4f3410b11d2a5ec921592f40b2ad6b1d442ecb3188049e27f5334a4dffff001d00a7bc36 +010000009427d53219ae3be5d968cd3dbe9f52232b0b32f662bbf74b4c2cab8f00000000755e1e0417036010faf0520d2524c806dda1e3fce2ea99dd4e70a42efe44f64011344a4dffff001d02c03350 +01000000db9740f3109d4450584d3223ec605be1f62457d8af2c22671b93b260000000009e93a056a6515e7916fc049578708d188c2146d3c12638acac92e0b72e076edd72344a4dffff001d037e783f +01000000de444daddd51d48d32e2119e59629110ecb69dda27ba85d7e85d40e00000000069b02fd420c2e86a575941a89e46d174a4e0fbc1379dbd6f6d88f3ee58613e5087344a4dffff001d001d016a +01000000a173420cdd0ccf0695a0a341d4f8cd0dc7cd8fd3aa3d01b68066561400000000cf0fb8b5f6fe33f698450ec147ef896ad953e677b4033e50400ec15454d3067fde344a4dffff001d03475525 +01000000343f2b87a053dfebef86bf7108c93e449c6e289e71a75489fe6125130000000088d412c44abb44236383afd182862dde877813143d21d92a3cc373d4bd4017455c354a4dffff001d02b22dbe +010000004375b71a891e69ba37f9c0cc54373be6796f43ba90b16b12564414a400000000e3abf5981a1bd6457ec0cdcab76cc2a176dc0d7e16f6d3781aebc684f13cc4fd81354a4dffff001d00caa9dc +01000000f7b2f9d9a6f44012728b6e09c48750838780f1e1cac9cbaa09e011420000000014bdd0dbddd8e6c917324a49df6459de897031f514b77999c8476d287e47f23b84354a4dffff001d026719b9 +01000000a688cd51d1d4a0cf1b438ae4347bd2e0b07fef348ddeb6a4168f71e000000000e7da7a46f5efaf4fc835468b21620987dbdf8b5f66daa0ffefcca5b0cae2e533a8354a4dffff001d0017ef75 +0100000010f5b842b85241ab32f795ad605ee899389c64c77126503e8eba7f4d00000000df686a7f31c2c1de6a608553b26d6336434719fa45428eb3df59bbef75ce9e7ecf354a4dffff001d02e94089 +01000000ee47f9bf0d5d6d59bafc6ade45ae6da0702b2ae98b792f7a6b95d13f000000009c13741588ce9e729f1b29b82823a97da91a3c6bb89d03c9b33c6cda9d71f0aedc354a4dffff001d00ab6a3b +01000000a0396d99b1042613554a8fe19f38a485daa2eb5b65f5347f3375a838000000008985dce081d487c7eb8f8b871738869922dd73120c75242333c45895bb91e64638364a4dffff001d04d09fc0 +010000000787ce301e6110773cfffc92ae93a3e017ce136ac2fbe2004bfd06ad0000000069ed55457af8174da637b03acdfcaaaa36cf822ec6e72145526cbfc03a6a83f546364a4dffff001d02fc547b +01000000ae76485a78a7a225541cf8025578b9085d39b45d7c2f68669f05eb7c000000005ebf1334d253dd7a7ee381625bc200973e7af65a8a022de034d6955e37823c628b364a4dffff001d06e25145 +0100000057ccd09f8dfb8ff31d4ec9e743ce56ed5c5c501046d709f49c356f08000000003f818cdbb02b1a468aa23e9795f7518ca5a92276559fc40bca459a79e010bee2f7364a4dffff001d04a53656 +0100000042284cac669ab917936f5b96469048d55214c074eee3c7aec95a07f200000000334a4de0559a4db974ee451fe484a0376a44470a1495797992b9a6a0ec7b24cea5374a4dffff001d03a8bda4 +010000005d820c5c53908a13f372a860b57050b9c60ac3360a7bc865fec2029200000000d29036af962f1d725133c89721cc7dea6d3cffda3a529c82e39397027aad7efbce374a4dffff001d02146600 +010000003c3b2d4120567d650494757e86595b7e9c70223af627fa1857ae9cd000000000a4f56464ac27919b3cf8c9ae4390e679cbb2f878dd7098057431c0d92171fb29d7374a4dffff001d01dfdf26 +010000007e74fa0f82027865fdf1aa434e494c2a60e6a9e1b70d2f7c4fc21ae5000000008a360dec0defc12a33c3d959a7ed168aff8e34547db15b516e1b8cca754c0de543384a4dffff001d0568ea46 +010000004960b70d04579667192055ae5e019eae175dd65050e58be04d10b7f5000000002eb73ccd1a64ddd448405955586fe5c95bc54928eb34679f74bfb4c9da8e1b92aa384a4dffff001d00cfcabe +010000008954c37766bb7293e105c476cf2b320cf9932b9543b1398823757c0400000000f85f4ed98adf68f14127aea968a338f2109597b484a808dfc8638112ad0623428f394a4dffff001d036b2da4 +0100000052d3ed4b6951d3d5db9100bf46b7dbfb177917539341431898a0aa5700000000d226fea91b99c5a31a034d340f647b722e50950c96a876eb96569efaeaf3b227a13a4a4dffff001d0051797f +010000002d54616f93e70461538548ac861c860a8bb9077c3452be5b4c00d4c5000000003f88cda74f26fa4ceb0a97161e771a8b1f6caff1069f7b53ab49386916f3820b213b4a4dffff001d024510e3 +01000000e226b7f57fe7d32fc6166057172a4c2931bd4bc3b619a036df08c95a000000005b7df01117c335ca3e8a802376e171e537f090dbe2e3bd1fc291e45e219a8717323b4a4dffff001d05d6def5 +010000005412ebec87fb8c61287f1d2fa1c52617ded826adec22368b84c42bb5000000007992420cd0f0f24840c8d92a4a865e6c906e473c23f1cbe3c183de70c2b2bf42453b4a4dffff001d02a246ad +01000000ba24ca40c17418d7bab5a3cac3c0bf00b9d3a0c09ec5771d0b14015c00000000a30e3f1429bf2a8ed14ca6ec9f3396b8593b8bb6ac1e4d35bd435f05058094ea983b4a4dffff001d02c75930 +010000006e823e852756b7106a0ba02b1b51045d87582001c006a2e8807cd612000000003c11b146d43fd62ec36b733942a52ba0c352c95a3f078808a38d080898cb8330dd3b4a4dffff001d053f9e6a +010000003f03adc125b43a2c8cf9d47ac43dc81fddbcf12ffccc11ebb226520300000000b17c3beacfe667f4014c9da9057fdb6252978fa76dc4dc2d9ddef0562d293d39333c4a4dffff001d0467b375 +010000002bdffedf5912eb0e14baefa637c22cd523c8eb4147bb111db143ce9c00000000e24fd45c98a5b07ef9b236be48aaa1085b1bdf993a1c0db1dc04766895b8a2fb813c4a4dffff001d01c464fc +0100000011c1b397772463dcb8430f661d548b3c55c61d415918681d6c4d4180000000008c32f04b4b70f43c849a8f424c3a82352cc0bc6bb25b2c2ec4a039964ea563c4893c4a4dffff001d05292e68 +01000000a0e705a21be059d1f8780cdcea04e7008b7497cfe8faa0fb59a666600000000059f060dbd892c1499758c7ce9bf8779d8e4dc298484ceeeca6467c17769fad5ccf3c4a4dffff001d013a65ce +01000000492c5c9b8543822db5ad8694cce758fa2019bd5b6f12db4160bee95b00000000f640c60ea438dc020048599869836f5323ef47477ee17caddf076ed428898f71da3c4a4dffff001d0065ac66 +0100000095e3a7f3bdbcfd7a40901eefd95edb7d6d60562f6a427f7986d0861e0000000059ac3c37adfa89b9a907ef9d485c57262e9283e1eb96069c2de04369ef1b3c76e23c4a4dffff001d046e85da +010000000a3f0effcb7af394aacaaa6e2383ec478034ae4ec4d488e47153d21000000000e25e353605728130ebf943b1f468937fc489589975c13765fd677e5050b487df2f3d4a4dffff001d00fe3805 +01000000a1f0d96d647241f42d4a3e02ea933690a4cc33542ed89de511c914c600000000153aef1fb8837f74a82054a0d9df9c566ea9d50df292ff62288082f311dd4212333d4a4dffff001d03d34187 +01000000f78b3189170c0de482943849707aa16247e5d62772953eb54bcc4d9a000000002183968b34446981f960895ed3713dd60fede3a9eeba1d40389123c6c409d3ba6a3e4a4dffff001d03d38a89 +0100000059835cec3351179502bdf8f4b0a0542d3ad336e22549c2a78f1af97d00000000f7f8e1a8f0b4bc0337d67fa4a2fceac7e83adda354d6fa3ba18dcf7e6fa2f2b2c43e4a4dffff001d01558b25 +01000000c6a2e4d395a1899b4be7d915c8dc660efc1298a34b66263044f5a5bf00000000e64b9c2a409a4a1310fb4b4f59559f79b15dc85e686eefc07b0b0a6b7fb6ee5bf03e4a4dffff001d05113e7f +0100000067e62d9c29833a10aa99f00514ec678c06a234e05f5a1de4412988ce00000000e4a12fde56c03eea1acb5eb1b57d35ab1da6e55d544e8212c47ce277416b1b3e0e3f4a4dffff001d00652740 +01000000d468ac8c936b469fdbc0510d88d031ad3d22c3858abb8f89184f0b84000000000fa35a88c8fd0bf5897b921ac75fac304760f6d60ed2355cca82e689ebc0cd53513f4a4dffff001d0405baa5 +01000000d69dcd4cfb5a9ecda166293b3c0bd72e21a804b3a565c8ec4783f8e900000000b3ec99d78bf68d284104779999ff447ab7bc73a783e9fab814c6741e71849cf0d63f4a4dffff001d0355fe58 +01000000a4ee15d19e3a355d221af4e4d6b69a84510a60857ead0af7e3b086c1000000001c25b8c364fd6f8417d45b9501384908824493931b544baabf2299e25277305540404a4dffff001d052500d7 +01000000deea32ce4f7ba1bee123958ad36ad37df8d53372f518a88761d14ff600000000aaeef85b297622ecf311dc5d29669fd4e3863454c6feea361ece4f547ba52e6a96404a4dffff001d03a82644 +01000000b8751c32196c89a35575c5ea97c6395e6dde705aa771287c867b909200000000cad183a8636b222a8ab775d34658b1de92ed57818e26203ee43ffbd4d8bef95b28414a4dffff001d0587f431 +0100000035a68fc6ca182ccb6663ee8b7e44556b950a8351bf2c64cd75dc2d0400000000d59d2a49b1883c6f7ac68a9d2649dc0dde3f0205e19d8fdaf8065381f9ba61cccf414a4dffff001d0592553d +010000002e5b592b34f37b1ae9fe17aa093d2d9c815aaafc5ce3dfb8b91e33d200000000a2990b3a24b766c99702c1d157925089bcd0d377cb70cacd30be33a3c90bd067e0414a4dffff001d03c217d4 +01000000773bd09b330364f704d0b3fa46d8431c5572a100faf117958fc15a3f00000000e739176d62b588566afa47f5fbbc0ec01aa3f058c036ca0ee8f3cd13e4223e01ed414a4dffff001d056b45a8 +01000000e9aa44db469453b2c657b1e4f3e29aba0532b1ba7ce4a8bd8dd00c4a00000000fa5f2cec2dd5e7122182c8e4b2f10a1cff77ce7972ceb53308b827aed06751fbf1424a4dffff001d00523f0e +01000000337df8fe8603beee390360abeed7e956a280fda2dd5aa238e110de090000000071f85d3e358a18a3a3465a1bc93f17e7725549a9507d1b218dfe6d1dc349641e9d434a4dffff001d0042c372 +0100000048bb0f9a7dc5c4fc15e8a4c5ce0adefc49c10db0a582e0476c1498ea00000000640a330a8e9f920e7684276e987d7f5a682838ef8510fbfdc9de08474ed3e74ac5434a4dffff001d01a51ad1 +0100000098349ad38c19d6e481627c6523d9d4f52c031525b8660ed55c215679000000008c06f072a655daa64b9910b1894ca250077f930333c0281c843a1198a6a0924629444a4dffff001d02e40f45 +010000003ad363a33fab1606f7a32896d093b06b84a7d6e9784047cf8b9bc953000000003093ae1fa781fb09af9fe147aa05ed26e90f6e7312a448e934afcb4554d74c11b2444a4dffff001d0029912b +010000006b8abc2fe01dc3a118f4d18c4f0b36b640fc85987db55437099aad3600000000f2e0926ac96d0c8010e095b34c5089f549f2fc8fa626e20b2f0b68dd46a72c2c06454a4dffff001d00149193 +01000000665559872e38dd76cd839459a94b0f6efeaded839d785b2c43c0be1900000000c305ed4fdbd3a11585c8805863472f53be7fb65b7b4404a688c070e8197bf40edc454a4dffff001d01b3084a +010000008fc81523766707ea9cdc7926f5771527eb6a5e6810f86a836ec81d5900000000408efc695c947acb1c0239a9b011d77e55b702fe9c2f6082571ca3bd9ad46bccfb454a4dffff001d05bb7361 +01000000efcd6d1c267d0b764044e85d112b6a32cabb8698bd13b4e947c178ed00000000fc53d40de77f595e1419b205c2dbda50be0a5595c45e001f8dfeaa8d3f19d90f1e464a4dffff001d01fe7935 +01000000339acd071c0a09f678896b302b2c79833152e048f4536994737db5f5000000003c651e6ebe94d51b8d1d1542f3b43f0c26298e4e5c8fb3d275e4e13062800f6c9b464a4dffff001d05eb40cd +01000000ce3f890a2580af6e15d0305dbf50d1fdce03678b626ef2c6f7be3c7a0000000092b0e20be269413c7f519a5e7fc316074aa24e930b8a3edb8a618077567aa832ee464a4dffff001d03713885 +0100000006f4241b0703e90950c5ec74c3e033c39e7b0c7b9b877d5515a6ef9600000000eceece737cbc2bfaba21cd1cd8515d614c2a39ea26d9b41b7daa77a17dd5ea5121474a4dffff001d0326e2f6 +01000000e65e080e4f1df42a905b85fd5d0f0b2d70f3e987e0fbba97cb41111900000000812ce3ea678b31aafecb99db5b0ca6718f9fb3474e0baed35aa6aa7290f5a40972474a4dffff001d043e313c +01000000ef823ae34b33e5aaba6945f46e6c2bea1df86769d877ac0b8ddb0811000000001962162ca36701e79b5ee44367d46a8ad8421b1f80aad651a02022a7b9e1e0a3a2484a4dffff001d0259a126 +01000000c65b1537a27f39bbe1a2884f77807c958faa56f3819aa5e4d49472bf00000000ba78186dcdbed13712e6c29812a54fa620fca9ccf297b8219f7ca736e36e46b7df484a4dffff001d036752dc +01000000d48b9919f537d161b9ba4404e9ba71bda419efe7173ec3569243364a00000000501790f1566c93f0edd817937da1e287147de7bd62629b65b395935017fdc80758494a4dffff001d023a2b4b +0100000083aa1f8934efdf864a78a3594d5c16f7fc8303fa38c01cff7c0c94ec000000005f8a10b8f3b9a4f159bb071ed05f349849e16774cfd79990af242f0ff6a60ea5314a4a4dffff001d04ad807d +01000000f699462ec6c1179023d1504bd5b9510cad887286984b783c943a33610000000074895c719bedafb41d7e5833189d876220fc5cdca55c6ad4b94a971a8e96a259374a4a4dffff001d002f84a0 +0100000077393e4b34076f0a6b6aa9350c799c7b32756a200eb1a7619e1b036f000000004c6b88dde02bd09335ea8bfb6db1e0094a58bdcb59d5ab78303034ff972b8538484a4a4dffff001d013ccb67 +010000004828e19e639deeb00d55f748421f46dc94eebd75e376104740535adf0000000014da74d7e2da39b2b2d676b957e3edc3619f4999922f3fa66c95b64be8fd92a34b4a4a4dffff001d0260001e +0100000092f7a9432462b827705bf9b31224972d61cc9fa8d478939e015ce3540000000076cf4d344b2c5db45b55ea38207cc477342c74b993c401b753378509761729637c4a4a4dffff001d03396962 +01000000b87a944ec8bbd6a73f5a6054efcea696124dcfacb88876995bcff95d000000002dc8a455f4bd8723a5d58a675c5a5e833ee411490a443f6d47f08a6fbf7594a2024b4a4dffff001d03699294 +01000000bc2c1291105e0c6209906934c0f1fe57c4ad13dd74e43830b293c6ee00000000070ef89563a7d8b1c7232b9e391813476ce93c9160051ed55bd396c6747618ee364b4a4dffff001d03bbea80 +0100000062db800da374d1b83b1b460566da0a35d277bdec5a6ef743132b2499000000004af49eb22a467e87048f4625dc9021249c16561d372366a21e8c20cad2c65aaea44b4a4dffff001d0142e34c +01000000e09e577320f539aef8178a3ec09dc306b301acf52e135839fb3fc55300000000734d92340bc4c287b0ef2b88001bac1f41bf5c6d3c725e0fc39e7c593d6ebd4f9f4d4a4dffff001d00bd8009 +01000000389998eeb4bc9b68abc63e8ed99218d00b857dcc5591966d46af4eb500000000752857853f92f5ca863f3254bf18ec5e3c62e1223c3f7968894048d40413daf3e64d4a4dffff001d00cc8cd4 +010000005faf1cf2ae1e1231a1b5aab191614ffeeec0ca86d86e105233c681230000000026dc493ba668f17fde5156a19662ca1a5bfd93b2376edf51ef9e3d623e4ef4a0f64d4a4dffff001d0183b4ab +0100000005bf42fd63d8189e37f471cd3e8a411bf81b4ff8e65532897b755caf00000000e53a837e9e3c05a4b635531c91c39c3c45565dc75e413228dc93d0c764327f86154e4a4dffff001d036f8a98 +01000000dd081cffd3a2812f5c770b8e79aea5e63d67b972ebb5d9dfe8cea3a9000000000803c135f2c72cbd06b77cc2a2a13a3c063028bf4b188b4760bafa112e71e579f74e4a4dffff001d05fe4d4d +01000000a9496d62f029c4b765548666b71a84adeeb9dd5674148b708b7aa70e00000000ad1924dbb0711c96b12ad1376816374008b98ffc43677b0990bca7973432f814334f4a4dffff001d0190dbb1 +01000000ffcc01d81e891bb248fda71e6338614dbea2d46b8e5f71460e6ae2aa000000005510b8c188460a26868d9bb3fc9bca7fca4d9b20f573e182466398f793d5ea8e44504a4dffff001d004f08f5 +010000004300ed388b39e36268421a7b15d337598edb40c8c290166b4376f2490000000054d4f73e569ddc7e67130ea0a43a352064bf3ded26f80877536b0e7277237a7857504a4dffff001d0268c158 +01000000d7e1a7d5fb8d460e90f62666eb9891f9fab06cc2c220899498cb01ec000000006a6a5be5a31aa8a7ced02f8ece18f44261e96ee9176b772fe654f1764cf6a4fd89504a4dffff001d02c96f9d +0100000049599a3ddafecc620c9ad61cc4520a0c70fae3d34dec595669587d260000000018073767fa7a1ef0c4a570f373492a822b5fad5fe3092dde239bcd8081fd3e8c8d504a4dffff001d000e0681 +01000000eed0b252aae103849e6805f28147abd17f294fe55c5c2d966d8850a500000000e15a0907e27914f317962823d82981df6f2dbd256738860027c740ac3f842bcba6504a4dffff001d05ebd48f +0100000031b485a9bc2b16f2ae5108ec786708f3f14447551e7f0f0a8575c75a00000000f6dc88e1c894cf88e930d30adcab0380af8fe00c3ebce9969c0e43242ca422b372514a4dffff001d0103697c +01000000ed18faa5febd8be98c42fe2a1a5dc9e8ddb6a618d0a05d924c522b4500000000811cb2e938cc88f159a82f1e160d80f4c6b429ef51c63ac9f2724810ff7694880b524a4dffff001d0097c6fa +010000008738d8fc729ed970f5223d77d8e776d5e0569ef2385a33596dcbc77e000000000bff98f2293caadd5f7fa74a7c5b3e2399268c171e5adb490d8e1dde1d4926d7a0524a4dffff001d02132278 +010000007ff2786f280df6f5039bfcdc1d2d18eab08e4120a5f67c769b8b464a0000000077b5007a1d08cd2a6abe2cb56a279ee4167092775aabf4245a55723455f7297059534a4dffff001d03c30540 +0100000002ddd5983c40e70e36643ebbe8567a8c115dd7a21262b8c61d693acd00000000504305d5fc375c8303ca009ce2bb783798b303208c8744fce95d0656375b682ab0544a4dffff001d0483c2bf +01000000bc5ffef87baf263ab01ed870a064c46731fdb99beb941bb6ab73fb05000000001679c9d394f4c959d3fa9e054a4e89af624f9206a777368a00d1cb6b175ac5f7ce544a4dffff001d02c1d1b7 +01000000a0fa21e1683774f057767d4545610a63a4c0ffdb774a6577224116cb0000000025db6d92e12ad8376e7e37c12b541faf358404c4b5617b9b287a43c2db4a067b6f554a4dffff001d04cfae4f +010000003abf3b8f61bdfc588bb0440b0f61cc8894cee1291298996712b3e3c8000000009160b7843ed42d0b5ba734634abb2dbb231e68bc37a1d508b2f27ad88d040e3e5a564a4dffff001d00028074 +01000000f23f3229508a7b80a755b69ab0dabb405f5eb566419eb60e2cc1ce780000000015819a3494e7b49042529989b007e8f89a6f69f6d22acd6f0132c270923a192eb9564a4dffff001d05ecd17d +010000005fc1bed214932ceb26a0a81a4dcc6fe290e5b7fbcdbc490f89c310e900000000c6fe4434b767859a8b403df5f195e223e622058ea98abcb0517bec7c5a734fd4c4564a4dffff001d030425fe +01000000390b6d99e31e1f64e6c16a94e7fed310d80bcccf9c7f9e82fd8bb1120000000012d6f8906065837ccd176ea73171077e73a29257d7e0309377e710ebb5629009fe564a4dffff001d0391c3f4 +01000000005ca35550b9d7bb3026a8a9e5c512e0c4714f11e3066f99be7323100000000045ab4575f33ef6bfc4b1c9fce77ea21cb9b606acf2b9a0331b8f9f931169fbbc64584a4dffff001d03f6a4c3 +010000007f0ab1ef14e058aa9823bc23ecaefb694cabcc7de8ca93966dc1cdd300000000272ef40d0354ece93dda9c8370dbaf29c6a2894fecd84a063c5d3de0ba90a088de584a4dffff001d049fcb9e +01000000f6707f93948703990287c88f79c47c2171dbfa2890d69c742ca6dca5000000001f75bdfb31993e02ff9de178dd3f1d765fd7bfb4dc87280c40f21270d7384df6fc584a4dffff001d00b0d294 +01000000096d0d56aa45bfdc9561a804aebf045d86dbe4538ba8abfc483800a9000000007a22463cae54d2b07238b58272c2f47c7bbc3bfc92a15d836f014d34160dc3c10f594a4dffff001d03465905 +01000000edc840e0c2f736161507190b975b87e70ee7842a0449b1bc785975070000000073998e2bd1d6f08ceccde03077d9d86adda70cd56234563c8d37e275767765de74594a4dffff001d03c9895c +01000000fa8f5340eb43544d2562339f1854add2ca2336be9ef3f1659cd2b3c300000000048d698b6d1dbdaecb6b5fa3cdf3c0b06346e43c2aa592606736404a17c7080af85a4a4dffff001d02cc3a19 +01000000683e618822053eeeb0d0a85cb955b3eff25df82e41beb94adae2e81600000000ef84f5c1c80ce84101f205ed007bef67688a709a1638b20bdec8e2d5404820f6cd5b4a4dffff001d0004d110 +010000001a4ac80ae8fba6ec44d500b823515af90fc24eae8aed36d88f73b39100000000d20b7be0fb4d88ad841c2d111b49b33e7d3b786ab431f3b32e6a75d2cb8299bfcf5c4a4dffff001d02b5e1c6 +010000005d8227de291d61223a71e9c11f27298dfda9b82538dc67ecfbbae9b800000000f75f109d50bb4674cee3023c0780403b4998067852cdb56881e69c4412c445b2485d4a4dffff001d00bd7afd +01000000140bf311410ecb5324301a67d5303bf817dc0401cb674e1c9d2b05e600000000cfd1b67825492351322e5e64da5db259d9eb1fa2b309a9917c921685232c9435775d4a4dffff001d03dbbfd3 +01000000a51f6b0e1785a625b71505e4c9ffe60acbae34d5a8e463376b0dd37100000000124c2dfbc87a28fbf04646cef5ecc103f568b8d28ab19661327995a7389d7325c05d4a4dffff001d003facb6 +0100000017b0e14d03cd3cfd82a7e8d54fdf338b7679ffc731ee9eece8d10b3500000000c96f851c0cf08646a15dfc49640c994343795b9609e87350e83f7c69fed804eecb5d4a4dffff001d0180f4c6 +010000007efda66c68e131be2f78ac7331d1d50f0203205a1532e96bebf3c3a10000000075eebc78e8a107eeb04c122618d963c038a00c956a67fe0c4933351c92f68def235e4a4dffff001d03a98d25 +01000000528b90ace50435d962ef0e1457a28b1c140e887060f6b277b37211de00000000739c079a15781e97e60a15e05212ca04c97d6c7509737d5daacdcc61982672e8355e4a4dffff001d053ea865 +010000005533106eb5b75af3ca4e3fb1196fa4c0d4e22413617c385e4ae0984d00000000a6b546b2b262c57c6691555d6f2720383c481ad64f7525c0c266669014632fa2ab5e4a4dffff001d01af78b0 +01000000c6d164638ea706719ccf9a75c7b1cba7a4ab982815490ddc3b2567fb00000000ba99c12105363f09230c6331157d8cd67336745aa9e0ecc46494634e26f875439e604a4dffff001d012bdbc1 +01000000364f362c384a75bde3177d475a8c85340d4fd4ab0ed0b30f17c3317400000000df6d00f0aa27587fab36da531d0c21a27e8b9ba3e7be77a780dc8277fdadad078e614a4dffff001d04ab300f +010000006fe6f7e2e0d1df2b6d71c090c4fa296a2d7d175c9b7853851d0fb03500000000610e56ccf020380fa1918b77eae3eab93231c4bb8d741efdae7c5256d7f01604d9614a4dffff001d032e0df9 +010000009f469645bedab4134a07b9bf6108960de1a2ddac68d59eef58b493a400000000faff99059c41eaf7d7ebdd38efd9b0e5a3ceb2ecdbf2df4b266f4bab573363371a624a4dffff001d024ad9f7 +01000000d4767418fc518c1dad34cddcdbaf06e38a73f680556295a88bae600100000000be0a584aa45ca81a0ec8e4943b91a7e3cd4b33fa67da59db3a15979326eb5e0973634a4dffff001d05e4339e +010000000bcd63dc71092a95a3cfa24deb7a99c097b5f74ee21389f4cf2cec0600000000c590cf15019323e3eef49355d341374d4eb747c85118d2af7ccf749bd6322d81c6634a4dffff001d0453fbbc +01000000e244d491df952155b701e23db092c318752154d0c94b7f1918c139b800000000a7f1371e40c1c7479a0182184e7858b9fc654b553014683443fba375927f9d4f36654a4dffff001d03e0298c +0100000028836ee71421916bfbb216200b5943426946b65d28ead2f6f6b639f3000000000494ca74bd3ac3a44c3e8d2bad9337945f89e45eb63fee5b9860d46a7d4c9d893b654a4dffff001d00ef98f7 +0100000017116f1db86d264512d1e919a74da904290965abc0492169532572230000000060081ea832d5c3e9f675736cf23993c36e8eecdde049a44fcf955ea24e7e9366c0654a4dffff001d034412fb +01000000e79cb282d213491706610e7fc8b02ea62a98e437829b076db087e081000000006fd1b2d70cc93f628dc0f1ec50b4fa820007225cf827e3f94aa581c30f5ae51528684a4dffff001d02c8f506 +010000009539a568fa254f13b10401a64dbf10d2709111822efb587824955f8a00000000a4cc887854ee5ae17aee01ff839bc7cf869aaea30fef9c8782eecf239da4e83f30684a4dffff001d03f4d7dc +01000000b021d8608d5c1517ed201830c7ab24f957ae2d71794ff149c7d11af300000000e58177c0a9f4858d209aa9008b6fae86b6b436c84c906e5faae402e0b14093cb39684a4dffff001d01e43108 +01000000b9d79c9f24321d231a894313febb7c5c264796a1bf60815a0cbcc96a00000000951b66c72f193f3b8374fcf39baa8183795f647fa69a5b2e21ee6505ca3ffe2ba2684a4dffff001d016977c7 +0100000083cbbadd846fd2fca57ab49a7f4b5ad6b1b64889b046ecc00424fe15000000002e26a89403d2d4d2c0cd13b959f9b86c6b6baee4ad0d99e6d04ce21995039981ad684a4dffff001d05ba9aa4 +01000000e435195dc29f89337100e0c2e80944137d2ca37e8e00256fad8b3bf200000000a644a58880e151d73b166f2ee7264025292968dfc2ac6b19516b9fea051a001b3d694a4dffff001d00a59466 +01000000fc27d9c3e7632342b188f423df64d41d8b8ca4c75b7d8a47fadfb13400000000f092bd85e17e84ee69919ffaa3a1936fa24ced5b3d0d0ca073398a256c5293dd4c694a4dffff001d004914dc +010000001dc0cbac620aa5469e889e5709b5ffd3dde92cce25715e084d2265d000000000bb549c5ad4bbfcde450dd1998da568ba168ce940479f92d795235e95ffaff538c4694a4dffff001d01cf90a7 +01000000403be502a01dc5d45288d1f305acdc9e15cdad638ad918392738320300000000cb513c7878b4d2c2376dd1a8d91aafcbfce7fb3ebe6bf419ad69e7408d8e620cf2694a4dffff001d0000764d +01000000919b3148b047d8639e8b46bcc4845d91654c03c6249ea4c443e9b99d0000000061a3e0d8305112ea97d9a2c29b258bd047cf7169c70b4136ba66feffee680f03b56a4a4dffff001d03c843b3 +01000000f90755f869f150d7c4a7191ffb3f74cc1752d0e1c8fa6ea51ed182cc00000000cbdf8c487a3679669f416aa49e20d8665e75ad2895d1256589b996a7f57fdfc6f76a4a4dffff001d003cdf61 +0100000047b42b4f634f26c6344841cba855c2cd7ad377cb35a8774e5e7f58d000000000cf16ae92ffab695258610ee3db7e1681447afcfe60c28ab3ae30e3ecc06de718036c4a4dffff001d036703df +010000009c57f3083374e7552e56d053b0f07fe93a47f6a30430fd36eb40b5250000000017eec598976bfbc55ab4c5d48e00162f31b6aca31bc42ab7084f1f139049b2c60d6c4a4dffff001d03b92cbe +0100000087de9b5ae134e6f3c94361941a079bc41b84ccd02a3416e43369ebeb00000000a0fed8fa4afb32368c57427cc0d9b44869fe8faee44f71910e9408636b82f3b2166c4a4dffff001d02379365 +010000000d544d94180289b766a4cf81e3ce5299f938c7ca3f6956424558ea700000000016164d5a284a0b308928c567c1db87c566e87d1f4810489c8256edb290b21512776c4a4dffff001d003abad2 +01000000555e499668283fc21cbbf2d5d911334ef50c6a58264fe4a9f1c7406b0000000042bf33d98dc089614e3ce83d39736949489ae64bdd1fc477f451c2b6b523c8b88e6c4a4dffff001d0210c9fa +01000000fba43e3e3dda545e1f21d1743123d5b717b0ca9c3b6bab06ee978fc6000000007b3e06d1951308c10625b2c7eeb400f1d9de694922b34bc48bc31cc57945ed609f6c4a4dffff001d0256b7c5 +0100000046b8f387b47ed742c035e8619551c1ed480d111d4e8d8fb231265fb400000000ebe3b715280fa9cb3a6de1d7ed2b27c7f769fa8db53956a443bf9fb8051c96502c6d4a4dffff001d0587e243 +010000009987e9a48351803b25ab0eb6b84ef62d92e33a73b2133687e126c09d000000001460afd3c98403c75ab5858f765e6bdc9a91a9a7377d64f262b0c4d4764fea93936d4a4dffff001d04816e32 +01000000f38c31b10930176f539d4c5c54f22a4e65bdf568ca89ca65518207d300000000ca13d344cf8d51261573a71698c8fe10b7e5d9f7134ae4e60682ff793b1b0278cd6d4a4dffff001d028a436a +01000000f0cc611660dbbddda4cc05700f1ceaa209e1d9e166117e5a4c2748670000000014f341b19c7417fc5b4231ae70027556174a78af7e554174541c36e2e1af9265e96d4a4dffff001d00d47799 +010000003f732830a72d4d1043e43e627abb770e9586c38b530c03331655c0b400000000a28bc5d7b507b81bd9208b9e304460a400dab435a53afd843782a9f9e7ef0b076e6e4a4dffff001d006eba5c +010000001c219fb9bf6eab86c72b3d3bc789b446d5f214222d91c752c4cfdbdb00000000a62967c97b01f47d3a252fd612e772eba117edc1f6033fafae4fa66eabcefcc7096f4a4dffff001d019c3e0f +0100000006de9b309fdeb6361b60029f1c7d6de6c99ff2819609a8c236d9feb300000000cbac2ac965c6b2ea471a630cc99ffa835915fd914c107b2db2dae5050b2ea9f36b6f4a4dffff001d05df3994 +010000002b59084f6b07d1210181294223741d72117914a497e7a07d0c730d4000000000b3ff096b0e79008a55e1816779ba4811e3ff6f3a064b6cf84bb4b1dcc433890cbf6f4a4dffff001d04090899 +010000007001e49d2d872afdb4cc1836b2d91ea483fc7aca9abed6e2e78a8438000000007444e881fd0c51e363543a35b27effae3cff02aa828a8de3c37bdc2db2b090bcc46f4a4dffff001d016b52c7 +01000000d5c3785332c02c192281491fb6b1af615bee7e2a2fcbbc124ba72e8700000000787f53156727d75d5b5a8d88aa93400ca4d53b9d90ac33da81545750405cedd3dc6f4a4dffff001d034694bb +010000009309d876716d2ead60108db4f93782659182759f0b351cd92ba9644900000000fd05849b870af4a38ba121721850a8cd344ca95c3aef88c1ccf25288776264d0b6704a4dffff001d00d1727e +01000000e9206fbdca986700b3747c62ab8169e60181ffdd3f5ca43923ffe8b900000000e08afbe6f2f42beeb5891d78d6774548971dcc9b3b7d3407821e3df29fa1aa0fc1704a4dffff001d045d66ba +01000000bc31e83685c5e76b127e81f12e8867ab8342c02ca51f137a6abed1b3000000001e6e383300d86f0ab1c84d68a38e3a64f94eda07cb5d7b6b20072cd5a610cdaa42714a4dffff001d00cd948d +01000000048a5ab82d24f3dd8bcf472d9081d4aaf4f16a16113821e0ec9d244000000000c1f04bbd361fc5125d1721c032aa88337bfdd16267b16498a8f80f00e2d3ab23cd724a4dffff001d02989202 +01000000e891684a9cdf9608002bb1861338f42e0a42ab96d9656f9f40edd3280000000055248a96fb577bd5c2c929f6b0c5757284ebdd13d09200fd4c342604ea3e5ccb7e734a4dffff001d031094b0 +01000000904984b520e8c777654412655520279fad1f546529d4b255136e42430000000035c50c2c01355804c412bece60540459c86d4a93f336396a664e88acd642347600744a4dffff001d017ae930 +01000000a31367e28327fdcc4b9c28664e5aceff0c6439f86378f8d88a42017200000000c26f017904f09d6c5cdd1b33194d0505a594a6b37ce0a7635ba56ad35e63183f14744a4dffff001d023e46b9 +01000000e78b13a567fa3b7f4329ea4a3700acba52539ea92128bd929e05853000000000980fb406fb9cd2562abbcbd3d2a9698da8aeb255af37c1237a552dbdddabc17d39744a4dffff001d057ca591 +0100000085f94fc42a95c9958bc3e963daf2c1550d97ca945988180a9fa276d100000000931a84625efbff3f1fa30974dc3b58d89a3a6c8c71b673e6a9b84e659fd657d3dc744a4dffff001d05acf2dd +01000000877bc8f841c9fc5f943d51655b089a4d7f545cf233f7b3289d6b113600000000d9df6431534132d6fa686d7496a4825304f8aca55654260c56d4edeaa4ee7bbae2744a4dffff001d05c15bd3 +010000006730e2f111da5208e30dc182cc59c9318675812c24b596e3e5398a4d0000000092c49fe9e2c4008f430cb72949871a174abca604b3e4731d51dc02337cda177ea2754a4dffff001d059577de +01000000a924ed313100b3de054fcb9e9748a12692e0f66d375cc05e01f163760000000039cbb3487e69c00c58056f6cbb3d5a3c136effa76d0d9f0df9fa8963ac0a197bd0754a4dffff001d050295f7 +010000009d977cb94b7caab58fbe8bfb6339a7bc09dc50a8aed9dec7a624e25000000000a1275135d09b38392ea718c862397f389bd358fc242242072db18a35a211ca93fb754a4dffff001d025f7654 +01000000f03542a950904c6e1ff7e8ddcd6c53a9438000df6d3846300afe005a00000000ba9ebb9774f0bacb321c7960e0e13443d58270ddc5493313d96c7784c47f1ae4ff754a4dffff001d042989ea +0100000092f5763855630332ff4790e27b1a8a37975899d435190bb50f8448d800000000c0e15d72865802279f4f5cd13fc86749ce27aac9fd4ba5a8b57c973a82d04a017d764a4dffff001d03bc04b5 +01000000b2dd87c5ae6f6368f5c00991d7d900c14441b315e0fe3cce598cf0a8000000003e0462da7bb4bbbf44948d8cb4af46d7fb0017f598329f2139f52c76a433ff819a764a4dffff001d01486ffc +010000009369370b1d364678e42fbb797a4ee0121fbc3916e5cffbfc5a8e1a620000000031774efc0e33444c762baf022787fbede880fdf466dfd754e3e457f4afe839b32a774a4dffff001d043d36f6 +01000000514da16d5dd2a4f82d4e52941092e8508c4cb7339e4895eb255cd87d00000000b6b3d4911dd13b8bb3d06b75eefd834250ee263cec3e94d9b4c93f600e1de90f5f774a4dffff001d01e3c40d +01000000f3a31ef7b1009ab512e3f3f03860dec8a09c3e7ec246e75163708bd80000000035f2a99aa12f8fa65fa0ec244ebc42cb7161e80bfcbfb1bf3bd89c3585368fe504784a4dffff001d01275ec4 +010000003526d4c92a0e4a1ef78043d2832b94108ae0d3dd21f7738e2033f3c700000000a5ce8a4c43d3b59b6fdc38416eff39c2d1068817022c79a4426b071b85a30cfdb9784a4dffff001d0052c6fc +01000000875794831d6f2c236759cca424704c18b8d89ef9f39347aa4b4fe46800000000dd4ed8001a3ef95027770fea474b5e63fefb27ec2f6b8648865f15e69308ff8734794a4dffff001d004298dc +01000000fb0922edf49601327afef737beb3d683f98b19d128f1ae64725b5abb00000000bf3d088f283f962487807e618960923be192a236beef35f4392d7ecfeb6832ed38794a4dffff001d049e4ba7 +01000000046dac6511ee9b24b34cc63cd704497d6383d1794cc058b28badd5d300000000e885af2ab2be11c610d75db7eb1bd6eaea463efae811fec483ffa41aa7e83f8b687b4a4dffff001d04f872ed +01000000a658975dbe7c08a66b40f3a2e2f0ef98a255d60a79ba975b89a06204000000001e7d0335d1b38ddfa9aa6303ef27c0c6929af97154ab2062f3934a8a29b78a58137c4a4dffff001d04ecec44 +010000008c36b480e7358039d56678918d633d9e92a8f3bca09cfff8286e3fa60000000013ac254600fca551cfb3665dc3beaf8114aec034861d36308d5e6b213f7c01c5a57c4a4dffff001d0061a279 +01000000169009ccd47ab6783ed3657ecb5c962b3b3d3d0edad231df0c9f193700000000623d61fe4724621efeddafcda266ef8edb29c8f2ae051f4c6cf6eb2edd214e5ca87c4a4dffff001d045f755f +010000000b89ccf6b22dc7ff1668cd3de5d6a812f1e5ef46b512cc9e352f3f3b000000007c644560629caa21e1a3fb2bc113deb42f78541283833c4a3abbe6a6fb6d877dd17c4a4dffff001d0588650d +010000003f9e8ff2fbb3e04c36c9683dd534a2d4fee5607825e10a2b4885e8e90000000034dbb7fb145e4c0ef8b98d4a03758ad3e9fde5016b147095494046dade14e834df7c4a4dffff001d0320c105 +010000004ee7497463e160c70196b1412a2ccbd2cc41062a773c4a3202b7b047000000007cd53c97e3e7d4f9f8e172a6168aee8539dcf2f4aa98caefe37be637000aaff9fc7c4a4dffff001d04b19e89 +01000000999ad8365c277b20b05558240e3c383ebea0d478faa53122dfa506b50000000088b03731e11466e46268a2473feb0bfb996a77f1d39d1ad9a23d27aa158ef8719b7d4a4dffff001d041a251c +01000000864569f3105e048d65a9151047f0ca03a5c3311c39dfd84c35bc279b00000000b0c3d72e0b5e89d3cdd7b5d44396e6a4ef92f4a2e7e198a6ee583768d86c839fd47d4a4dffff001d0348f715 +01000000783b15ae83eabb19aac502a6fbae8c6b4b2d8dff30a1c3cb2af0b318000000005e2072b31fecdf48b38a60a1613a73f8c370cb4a857c74844ae17ae30adc9677ca7e4a4dffff001d05a98d10 +010000009b974d3c625f7fcd4e18d7b8e5d53ec6db57b0987f7ceef46e9175c20000000005c8b8ec09654909eb66cb5d6f22c7ba56afafbd3e6486fe8885c7f0ef53b77bd27e4a4dffff001d03cabe9d +01000000d04963c151d6298a97a6ad4c762ccbacec3dabe179b3c39ab96eafed0000000004aa8cff9a7ef9a2029c8f3d45561888c3c50d9d7e9610de7c7976f159bd0d55ff7e4a4dffff001d02e2bf3e +0100000032b6290c64c98539ce9570e69dec2e354bce3909fb62badb2c6dbbdc0000000027cb02841e5e22c93f1e2b61c1cb40553e97205e0956a3b01777c137d3264cdb107f4a4dffff001d03e0d16d +01000000b96ea7181758239d2d12e3c43cb81c47110526fe397595956ec24e1800000000e89deade7ce10d96cd17b1371d1bd50a16e04c997331c3a49baa0ba0cf84e6c5de7f4a4dffff001d02ea02c6 +01000000c80ecafc4bca833c5f2b956f4acd585becd120bc9712517eca873bd5000000003c4a3ed10ee0ff614113e34850ec14018c7286ba1868f4eb541ba0c68a0db05def7f4a4dffff001d05c8b8b2 +01000000c451a1fcaa893e25e72b921e74bbbe82e4489aaeff82806bae8e2ff3000000004d8c8758514bb308bd043ba7c6ab04555831f523ef439b44b6c2d26755a110b86e804a4dffff001d01868381 +01000000b8d4747b5c3a2d6c8e50e389fc938ef7b8c04eb4104c17a2e2694f6000000000aed381b06d2dfe28d0ce14b4f43f5c167dcfbc80d44e37054a014a828a2c8c7236814a4dffff001d05517119 +01000000d0a1893dfc1470c05078598f83a28af1e44df83621ef6a34319f1f79000000008c68d3c55f59e4264a26c2cd1a6a3ed4d45c98eefd14bbfb9a26cf55ba30611d2b824a4dffff001d038d2dc8 +01000000d892825bf696f9c10eead1e3e97dfd043618e0123b72dd058988f92a000000007b921b39ee758310c934e9fc074942513e85716327fa08526e089895530fe6bbca824a4dffff001d0276d82a +0100000062f08100a53cfee15d6960d2915fecda72ac40a116600d176bd6eb5a000000007d4eefd21df4c8472009c501a9c023613b9b67c27231f130cfa72d97978ae996d2824a4dffff001d04f2f222 +01000000c5b9489065fa7e1ac4facc51a5a0ccc2111911609f43386ebe7ca1d200000000a0db3bbb22a2a8441d84dbe335c24959ea3d3d6e91bf67e66bbcb0d7e0a9c4836a834a4dffff001d04181366 +010000007c967c39b155d44a57d37c46bcb47506c0b00a7987d9debe642c4c1a000000009969fab4c50985190c20867d5eb2622f8994a72c05fb9c91ab057be79a80526d94834a4dffff001d0391a66f +0100000015276dffb7a0360993a5a83751944fc88a2d3f13f8e9363e5f3cdce300000000cdaef86e7120eb9f7e0e9c1b3009b4581d1133e5e2371f0da2a0b7d314465f8dd8834a4dffff001d04184062 +01000000d9e737d6b012027382d48297bfa52d08eac8ff7aac62810a3bc6798900000000be2b0f66f65fd65f4d4e387b96041ee0aeadeb736b467f8b64e12663a7f8b92971844a4dffff001d016cbc40 +010000002059d85f0b361f764b21d2254602420fbd84fff571daed5304862a42000000000c0bdb3ed18e03a8b3b92ee43926dfcab9a2bc048c98afa86c1f24e1f7f1ab7e23894a4dffff001d03dc41a4 +01000000c34c5ce16980a5ee4c66a17ca4d8619600a15f7e0dff5edec12209ae00000000e7be4fb74031df2ddaab02750360d6b806cfb54cfb9519ee517c20fd9c636b2332894a4dffff001d05c8f18a +0100000071f7cdf7526fe514f6a17de62bfd240d2eb1fd5f8e406da3833394490000000025156dd8c9381fef3564ec230644a84f35524ee664c3643ccec644088b95d86f7c8a4a4dffff001d054f7dff +010000005b09986f4fe70f0f864a258266fc73e908e170c29b3cfcac9ca9fbe800000000e0f8760379c70b7d7904b5a50c59bc16afc4529114d9b9ac1de43e698debefdba18b4a4dffff001d04457234 +0100000067391d93a161d82b8f968b7041a48507ace6bb30d4dfcc789e0b6f7b00000000a035032fecaa07e192fa2c97f683fc201a50f90c7ce4abcc33ee37aa528503e60b8c4a4dffff001d02bc415c +01000000ddb642656ce71cdf2052b12984eb8c297eed7803ebf000dc63a262ce00000000b9bb4103d0d93289e9f9448ed7e63a8ac751ceaa41d1cc9627d2bca7d7c28a16598c4a4dffff001d02242de7 +010000005705c3df459247a82aaa9c3d836ee2661cfe00c826faa3f2a8462222000000002204f4022b778f21a952719904fcddbefad0afb48be8305e89ddaac4ec8b133c8f8c4a4dffff001d01ef8ecc +01000000560f99e148e5e82dc838535b9cbb5a6116eae26b586531fc2e2a8315000000007760f7a3f07d88e8c7361e1223ea4a9e4fc5f9d8c421037578ac06d921f11095528f4a4dffff001d0392d460 +01000000cb6dafa616960c0d1b87c67ce99dd7238f3891d280444d7a15099f710000000090adecefcdd85452dce9b830a3ceb9240da10806a62404f8b7048c2d85ea0f7d818f4a4dffff001d0331ed48 +010000008f7e1c2ed57b82e56977639379c6cc7eaccdfde5f181fa381b0495610000000023130ef1dba152270b2153aefbb5d4e29c22be3e79b2041ca20620c1b381e099ce8f4a4dffff001d05e4da4e +0100000005b132a4f74a8799a57a4202d0eeb09612cc08d295401f007c4530dd0000000073340035d03933e01bff3c47f14a5b0a8ceae33be12a8dd521315628ebf42eb3f98f4a4dffff001d003a7958 +010000004e6790a025117e8aa81fd453c7c6c236da838f3dacb169abe51897d500000000091aa6ac0aa796201482553ebe24961fdb79bcb2c1b0902f2ecd2c9e7c705d830e934a4dffff001d001badf1 +01000000ef72b16c3d1e58804b715c8ca9d02f2158524171a8a4742d0a07974900000000ecb21277a56d483af3a7ea1615a8a6d0566bf87ef230146ed4a8cc8fbae417b2fc934a4dffff001d0370ca3b +010000004fca0bc6408f652b0d9f79713e880890605c993fe954bb73808f2cba00000000a70a8a17b106c669acb346ac08ca99344b307121658cb8cab8d00eba9adf7c9138944a4dffff001d0328fe63 +01000000077ee2849664864a66985199aa49b030f66c79f8022116c979e234b30000000087be9e1e4f0bda5f90e3c1db350737e4f390064d8aec43d6e00ea92a27c2957ee4944a4dffff001d03af3fb8 +010000008b0d4a2ecef90647f9d1a923020adf2bc138626877f0f855fde5000a00000000ecaa67add6b1f10ff91e5df491b59ab1594c357b39ba9f1f06022b078844375e99944a4dffff001d0037c556 +010000000bafbf706c6cdcd91f5fd082255862927a4d3a0457f7d7fcc6ff3e7600000000087028b6299590346eafb168c13132979b89b4912750975d9bac131a7d4c68c3e2964a4dffff001d0525ab4d +0100000025d15b892b9a091f18e865d579f536c655f18a4994e960b06227a643000000005a2a640ca55a1660daf5363b670daa00560628bc3c39a6dc88d7d59cf2dcb669d0974a4dffff001d0226a908 +010000006843e8fc9750cbf5174a0a778ae5a6a63186683d1296a8514359770d00000000c5eb73e7834883d87b8c0ceef4ba9b850fe503039eee9b28bad767afdf0d0416d7984a4dffff001d05d4fe47 +01000000e3756d4e486befea24a302f095aa87c984f583a66687fae68d92ed1c00000000549848fd7d128b20aae864145aa351ba58eee3feffd7a8089c7adf4445b8de874b994a4dffff001d0496907a +01000000cb8461389ac8693f9f277f037cec93d37eb8b6a6558cbe10bdacd1c3000000008aa6ad7fa12e8ce311c1c659166e587c880e3a6fcdae871eed1bb350d713f25113994a4dffff001d04744479 +010000005573c2792f3790bb99c05da5d71c9a24bce92ffeec84093243a1aeae000000005945dda5801905c0a507e521331b06a9263279c4761dfcdaef431c0a76698688b4994a4dffff001d0546400c +01000000afbc89759982446b305b9f48cdb547f3c95760014fe738ec3e32dc8e00000000fea6f2fb7688cb32351b15857d3f250e581af5b20d9379070b5542a00c42ca70699a4a4dffff001d00ac7c0f +01000000e17f97a9d791f0724271f652f057075f27d96aeb365838d8ab17191500000000a91c00538ff6917dce0bf745e5a040479862ef7134de60320297eb029f7bc07f469a4a4dffff001d03b1cae3 +0100000050e436836fe519456bd7faabec5d522aa80bca6a53af3f9d57457c4f00000000c66962dac9d02aab13c8501636c123f672ab771c8e088e1b24275b105d222c7e569a4a4dffff001d02c1415f +01000000dd654b9a8a51371c852e448ca417a3fb05413a70672783ecb1346f640000000044fae19fa72a7abcbf78d966a5097415b961a316322184bce818191677a3f129fc9a4a4dffff001d01c6aeea +010000004386a29fd8c55a0c47b8e97c6d04e1267549de89abb6db75a577d6ee00000000fa993f2f31763c2969af7e8c181a32cb663e07bd210ec14e29fff768621d4b58459a4a4dffff001d03c6039f +01000000c5cb299e1b345b77d730c527034828c846f23bd3630ff74355c2d9ed0000000056ad317661356b368d8589f343000fb68353f92172d8c7f68a9c9792cc94ca5c779a4a4dffff001d0485feb1 +0100000028fd73f5c4415839b41e887ecd606661462ad914da28c2140afdedd100000000af9d78520fff7f6212f88c7c98521e2c9a8d2a23740ed2068e1b2a55e0c7aba71d9b4a4dffff001d03a07cff +010000000cb32bab66fb7c93a66f841608c74f61dc36d662cb01f30a8f01efd400000000f8bef1ce0194b9aee865355e8e5b0881092bb9fe8e3842c4f7110f7306caec26629b4a4dffff001d004d55fa +01000000c2a8b9e3677de7a6f7286c8a458cd5f09409c254209d9d8c445e0d24000000006bf3416c661bd39443f16a48fb560e97558e2cdab18da1e598086cd40a2ac6ca379b4a4dffff001d02c8a75b +01000000653dea66e42fd4d1019adec51e1f7493b06f458f960a6b08501d8534000000009ec37ca4395220c81233283dfbe020c1982b43c3cec6b82ff425173ef558a51a5c9b4a4dffff001d05681cfd +01000000443c685dd9cd61dbb1d4c1964e8eef7a1e6c6688b38775c2707b2f2e00000000cfb0c5cc863b3809bb9c642a497ab8eb5a2b0e6cb448fb1a12b08c3d0f3b1e13989c4a4dffff001d04a008e2 +0100000059ded6607bd4ccfeb4348c41b04a13343602879158b8d9aa1f3acc6b00000000af43000d9c2194875f48dbe377b839bfa2157758b941b5020e58b55e2ed04fb5de9b4a4dffff001d05540f10 +01000000d04014b56c58d45f823589d9d36bb755c50ee96359ff44449d71cfc4000000009d6e5c4cadaa256c698491f2c3086ceafc5f73eda5e54d4ebd50ac8ed90eefe0c79c4a4dffff001d015a6219 +0100000098fbe05200b867e000286338ee405f70ce4362f54769db2bc9d919db0000000078b787d3d8da61dce5a9fe4d4a9232e804021e439906e6fd52cf8f041f948b3d919d4a4dffff001d05774a61 +01000000c5f311edc79caeab3404ee358871e5f886712e56005f4c67141dadd7000000005545fe6585b08eceabf29cb4ff68ba07020fb36520301d4d5e7f30dd1489aa6e849d4a4dffff001d0505dfe1 +01000000f24700786a174a784f2d99ad24d4c0165e2f64acdf0893cd4839590a0000000022c09454ffd3a434342bed475ee4e9f7a34b99d26b65f2ab92bcf7418e77f732e69d4a4dffff001d04872d33 +010000009849d7f7915299b00804b04d89a946033b4bac82b431a04781ee1c6a000000006801e653da56cf733fbab3ebb4d3f248ae5066f00b0b879a178ce3522c7e1572cb9e4a4dffff001d01e83192 +010000002cc41fc32ae1712dbf174b8d810498ea52c9908a96100cbca447858e00000000a9eefb8c49a77795e353cef1a18ddf9f75556b599049cc5c326bbedbccc3cc5aac9e4a4dffff001d0559b918 +0100000029bd58b2759e2398dafa0ed714764e7ea87a61e9966b4777837ad0520000000073173503f0ea326597487757ead4a2afaded9c36545e6f94578d3dc6e5d58fbdef9f4a4dffff001d01bb0d51 +010000008d7f3d0a93bb78055f440276924456111c24af5ed639ca3b3a2226d600000000fce8d0f71b20ad1d4797818e77da4739eabd906dafb8baa38bed17cc3f8d723906a04a4dffff001d04416634 +01000000e26e977c4cabd28830a8e1f87983e91ecbac81030dbbd7cea6efd4c40000000029045a4c17ed6ba6bfc40396d8b9d2640a705fd487c3c0271eafa3c503d0f54b9ba04a4dffff001d02477ab9 +01000000f3249f94c4a61be9fe426f496ddc9670874206c5034c68fb03a77edb000000008f1c9da6092af60d73b296ebbc796caf1b8a095f7ed33078bae45cbd2925029961a14a4dffff001d0253c63b +010000006f6cd48debac250a7b42738755888a7571eb9935bae9e94d7cc28b1100000000d42144297dd9954a62226db7fa2de24cba50b4c963ae6226ae5fa652946dd0a7e5a24a4dffff001d003cb819 +010000009ff0cf741383d94f79a6efeff2a317aaf6375ac3d252db55362280e00000000029e2f50ac5d203e020e7b59855380dda3e918c80627117c6249f987b28ac807bd6a24a4dffff001d030ba92c +01000000f61d5499f92cf040f17a16d02b348dcb3dd041b224625bc99acd35a400000000400de7fdd4b27e6d8d358cbe3b329417d75352a9aaff9af70c1f685cf842560675a34a4dffff001d0271c0e5 +010000000d1e2a2e2670286100f862dbae397fef5d8dede11bbcb013c453e8ec0000000043db438150141c0240a7f3f03cca32f4fd0ebbd6bae6daf898a6c7aac99b8a85c9a34a4dffff001d010d4882 +01000000abcc3483c5e1adeec60646454065ae3a116dc60aea65be6d0e66bc7c000000006c0e3cda9dd0367de3a4e1d356c0ad23cd88f26cdff41b412b0ca1de1d9c738cbda34a4dffff001d00b44c37 +01000000e0d79f654270f553820b56d0332b5ec18a4cb5d969c5d67c8adf9b2a00000000f2cf8413e5df690ebfdc8441586639e39ee7d8571d88e07853117ed693bc0b8d35a44a4dffff001d017fc225 +01000000ee4c6ec5ebb31e14991634916e68379e6f08617bd3e2b0816c0605fc00000000203f6062b808d073f0471cc4bad3fe9cc51cb9c732ab53e46a4127fbf0b960fbbfa34a4dffff001d03592dc6 +010000008000bccda2bdbf0a6293178dd1cdce1a368824e9c99587798b7fc1c600000000d29fc4754ff52a23715d3147c6a0ae31ddf06fa70465f4d09d41b4aa08da707bc8a34a4dffff001d05e36843 +01000000386cd6cb2551941a87716c2c618e1e56e9fe7acb49b0a71f414047eb000000007bd7a97b969168050de5f20cd3d0ea212b138f4b43e3c27755f46aa08f98f1ce06a44a4dffff001d019dfd3f +0100000053402e2e9f85db9774e0193414436cfbbc70b658c4e54913c67bcad300000000fbcf82e5b9b5c1aff879c25c172d4db531905915b205187bc3ae1425603e7c764da54a4dffff001d0154db1a +01000000ded3e38a879dcbfde2c9ff5690db8377d0c596e20b8be94dafe611df000000009d8b84155b519e0c1cd97837329fc3505f76944245c1bd686617779dc700c15850a54a4dffff001d00ea1441 +0100000011a98d2176e7952434447a06a9ce9f726651737d3a3e006373183b2400000000a50e90ca36c2cab93fd8acec6eae0755d7d2e710688a26fc16557bb5c038d12c0ca54a4dffff001d044b4279 +010000008cffb590fc8e390cc07b0fc7d7e1f83033a7f09c20ef114f6efd73fc0000000098f65f5151c0fdce2398474d0987d7a531aab1c2659675c07658b07cd954f80b29a64a4dffff001d03e15e65 +010000000a7213bcde17cdc86191ebe8580e3a9e893e3b9a189e2787394c5cac00000000230d4c63ebf11994b50ef9c5f1b75b2b8ce6950f63fad01665fec3ed5ff2bcd243a64a4dffff001d02706123 +010000002c29a0a93ef692c20f7a5d0a917a69be60ec5bd73788f9ae92a2e7f90000000048634496267a248713364570b8c4cc6ad5b1a3f32fb74cc1f11fd22c3b293e88b5a64a4dffff001d010845af +01000000fd50cc2daafbf969480d2675a4cdc41f397d1d815a2aeaf863236bbc0000000086d723685d916a3b3aec61efe9b88a845d96fb903e78760e0759aa2089de4e2e66a74a4dffff001d0389da36 +01000000e897be2520f0c6faad2d0c4be209e0d4cb2edfacb7579a6aa939a10a00000000ae3f02363ed042360b679fece5726b2499e74f27f1bc596c3beaf701463be4465aa84a4dffff001d04ea7f5c +01000000a4f61773f2105205cff43c8ccb188a0ebe56f0811834cc0772a5a1e8000000003b776a5a9f039715342ed278feea0e87f1454ffdd086ec1e3663cc738965e9d536a94a4dffff001d04479ccf +01000000cd182da4f3e6641784b8035ac96d74b182939696dda02368f01889b7000000002fa23a7882a373528b5403d34eeb13a26fbf25d668e7019e7a0403ccb5ded71387a84a4dffff001d033151e9 +01000000913cb00e5db68a01cd2c2c469773d0946e3e1589cd72c56349d0405c00000000e04420559afddb03dcf51c67a8317370080e517e24ea38dbbb5bd529680c1d2c3aa94a4dffff001d01ae2ae9 +01000000f87f4f7f7f99202d77747619865391ed07d87afb9c7c9a48b7f319ab00000000b4e21e67bcdbd54bee76d62b5cfdb6b5cdfc5fd1c6ffe9fd6b1e2f39677aeacd2ea94a4dffff001d03275902 +01000000c7a1220a895e2a83917ab688040dea8a000bbd5f4858e8bda00f3bd100000000fcddd0541ddd056f19fcfac0e637cc8fab7503352757707daf37da8e859434f397a94a4dffff001d02d171f6 +01000000c5711e518c4a53e9a6d684759ecd07d69b5d859e1edffcf4bcd3896800000000e4ce2882f8c5b8e467a080c647ca97b8c11070b7ad10a1eaeef13e1b49d0028cdaaa4a4dffff001d03ac5ea8 +010000000a1bf9a1db633b5c817be9ecef65ea99ed980a58d070cfab68b70d5200000000c9fca844eac0515369a587359fce6a2cec83ebe565dbf46b92928ca831a1abd54ab24a4dffff001d0370f65f +0100000081f9fcc96f7824b23225d5526b65d3d5826bfa8b65c083028e7b287700000000163cdfb6b5d8589c539ef411a96eb96ee49dcf03ef4538d8f752762893db209515b34a4dffff001d0215c77e +01000000a563fe8e23fd9acdbd8ce0db616cb72c1b05fc8610c4e89fcd32b21f0000000074fccc385cc517580daf8966fcbae398b81ce1f077bdc5732c529d6c3502e16183b34a4dffff001d033652b3 +01000000de5ca94254b771527679b5dc4862a7eee044409cc33ab2960cbe3920000000005f20491b7a3093fe2b342e816a5b8d409916c2f11a6cb6743dc528e8c7ac51b7ccb44a4dffff001d00d437a6 +01000000f8d57b05a64cb39a5adff645ba434bb707aa118322851cbce137df2800000000d760a34f6e7b07b3718a8355b71b014f07fadb917707e4bb635cc1277cb8ca7f5ab54a4dffff001d00d8ddeb +01000000a54d55adebdea113e46d6911a15712f7545709577a5ef6684a37974800000000849ecac186963e52f8b855e51f70e9cfc90cd0f7054c23f1bcbc1675206a00bb4db54a4dffff001d01cec36c +01000000f2f0b39c9e0f8ae81ceaf09ad25ee9166e8f3d33addaca1ca1366c5100000000060e3cb266839d6c8155ebef78939c33b659156bb2cbca1cc95b50e58ad5100b2eb74a4dffff001d02e282eb +010000004a016d8b02a6e3c33705715c29754230218f04485693994b29d78a2c000000007286500c919f491aa5a0721558e4b8a64230ec2d1d955c45ac871da0e74c898d3eb74a4dffff001d036714f3 +010000002d24b64aca8efd3a0a948e813c5303891d1971e6295be97c1923e05c00000000eca7286e07ea252409b51cb2124e3fcd6adb8e6fdcf87046de18666e36037511e6b74a4dffff001d037e372e +01000000dc8280b9a38ed7829f3ae49bf8f01642c0b16b90642b2e911c2cb69000000000a10c2661ab1b02ab9f3c5bf928db087c27c06ee7537b04ee66ea4785fb625c6205b84a4dffff001d053fb75f +01000000280fd86185912d306966b6b7bd90a765e659bad17564825f0c997f2d000000006637b339af8cdf579a250267ce7ce41f19de798c7ee6ff7823312076e0950962c7b84a4dffff001d00c1fc82 +01000000993b4428fc00d1eef46e5fb166b32f32ea75e3e49105a04322845d2800000000fbdd76c6562c86af0e63ea9a6c46a91b8d92c4e7df480977ec0d53b7f3c25b9812b94a4dffff001d0099e992 +01000000c3b9d6eb714b91e367d7a44145f498cdde7f579b68c66bc1b9af7886000000005b0df3f16d5bb399b09989beaf8e0665c695ef49b7aa8473ba8690f0b029ce0094b94a4dffff001d036efa36 +01000000a702519acf76d7519ce42d6c0bfff12644be3694621a942a55e4b36900000000f9357358ee5931b907a8c3df01115891a740ce6f4df224eda4586eaf450d8b9f3bb94a4dffff001d02d9d39c +01000000931d5230c5a2b7d43d322cebf9026ca68fe1a70afe0d8c8abed78d320000000005634a2446d5fb785d1f1582e6f35418bc6fbfb6abb9507213b010e58dac1c3ce0b94a4dffff001d02e9d793 +010000009c7bf22857a46add066ce2267450ddd5a6f280943e961b1db4d3be0a000000005eef5906ff42a8baf4a8a2d8119f88112d802484361ea778d22e44300f6ab20c55bb4a4dffff001d0179f4e9 +01000000e2a926d9239573962e534a47311e7875482832ecc6306bfaf1137c0500000000fe6ae11390e7bca224e9937759c658c9aadc8971e8ba384caf2a58de311826e7abbb4a4dffff001d01d2102e +01000000dd3fb5ca9283091e24a2dec99871d9c033f39b1009f5ac592ae2cf190000000086a971609dbe58f45f99ae1a769c07ed6b0575ffe811844b8f6e04edac722bdae9bc4a4dffff001d037251ae +01000000a7f31bcf8d5a86bda0ddaadc3c8b77ec71af748584b3a5eda6b88c4600000000adc6c0ba4cebd8e2b0154e9f876f1e6ea54e293e6505a5200a4a75d394521a7882bd4a4dffff001d02defce7 +010000003846b6ba73428b23c1aea96cb3a4cf8b72ea4f40268ed68fd16ad9bc0000000050a4b5b4d3fb6c72d198506c83d6e2792262134ac07813baf355921ffee7c6579abd4a4dffff001d04687d8c +01000000df7e6b1a5947867dda62ed7a79f75d939d4190f3a575b447c31308f9000000002523ea33f4e43dc2dbdb14116d5d087aa3544a7f886a69693caa154ae0abcfdf05be4a4dffff001d030a6d3c +01000000799f7e9b6d0ef1bcc5d4e6b82936272e3492df8a920b934dc9e1973000000000d0038b40a4b3c20cb7a6b431d6c2edcd235c59dfaca4698dc0f4a057d89eded6ebbd4a4dffff001d01e9924b +010000005dc85295d33956fc24736fe54dfaa12ca98cae2ba02ef7eb9655a12a00000000b7bae6ad3a263adfc01e5e76acfa7dbdc0e60962230af6fc59003d59b437b28d90be4a4dffff001d032ce3c6 +01000000a3b52bc4a8ade1fb7764462099a412272fa13b3d9de508c356a3d7910000000001eef5a3b0e24e946235bdbdaf78601185042a5974a6f9aa7d8deab62b6bc49c18bf4a4dffff001d05a9f6dc +010000000e816fbf3db7c990098f50722e36029dda668e9aa386c23a201e9d8e0000000027d9b4ef4291c5332a05c4a2918b5526b96a117c0bb2bc97cd18f5a6eea15caca7bf4a4dffff001d023f105f +01000000514062fccf6e88a8ad9d0ff0fa2ca4d2716c2b5fcc577f52e4557fab00000000ca12c4c5fa9b8106d21f12f4bb24e1954ee95686b849c643e0504328653198fe1cc04a4dffff001d022e817e +01000000b8631eb7fbe8abdc32f81b54c6f86c96cdb5d7c270cbc8e8026890e80000000032510d84003ef4d4b634cb4bc914de16f8e9b72b6f1ea20dd9eb847f11351641f7bf4a4dffff001d011d83b5 +01000000bf3ce738c0692a474dadc784640fe4e2f06beef2fffed0bfee65cff100000000a463353790f4669516f33c405164df2ee08f7738bc91ded3ca2a025e18143ac967c04a4dffff001d01fcc651 +01000000fd4bb907a2089b41433dde0fcfa70db3ac3a6f075be657f0e15144d1000000004bc439544765cbaef9a184ce7fb26bd62cf1ab32267eed5fb3bc7050a8f0c427b4c04a4dffff001d05d07be8 +01000000d0d3565bbad3a5fd165a7fbd978fb2ff2974dac1fb4ef21ee0c6282200000000e4e9ef647dfd719c086990944594c3892d0deffc18fc90e092d61a3e586e4cf8c6c14a4dffff001d0188a8b7 +01000000d2d5324fd546db69e8c42e6f6c5f2f686cf29a9fae985224d450ca7b00000000b3c4d587370073598ce1a226be2f70e0ccbe446e80f88f0446dc5b179782186b78c34a4dffff001d027bf975 +0100000074b16276f3e902ba74a5a938a86ad7249db7c620b06023385b515109000000001b94a65422bddf5384bb8da9d866fc7a7e5c5abfbd5867efdc8eada3b78b8e3babc34a4dffff001d02d402ae +0100000057f6b87254267089a1d84eeb06f20c3b181177372b88cb9152f356dd00000000c70258c4f96177b1cdd9d6fd5ad0e9a65abb44e7fc3dbe507f45e2984ab830d9c9c34a4dffff001d03cfee0e +0100000078eea9a9282653e1e213c716004ee3c5b7d323c37bdec7a64565831200000000ac37dfa5948ecb63e944a61eb0e83cbf7dff61cd61cf66d21f447d9a97bcbec123c44a4dffff001d05c7a466 +010000006c0d72985fd57581d55754a3dc631a89e6e4e1edbad1696ec271d6c200000000c1d26e829e285126d03a1943bc46d72c7e68f7fe1855393079dcc6406328ebc53ac44a4dffff001d01ec1ff0 +0100000060f628c70ffabdde8c265ff6aa1b49e0f42d250a0645ac0fa2963ffc0000000074f7d49e03dd5ccae2109db1e4ff11507238642d86fc771aaa999ae48b06f3d26fc44a4dffff001d044b6b47 +01000000f2c8d522c94e93a8ff5d947c057e4125a4deb167e5eabed5dfb56736000000005127f86da79df2980cedb16ab9a4b167e6daeadcda0d645f83deb48a3dcc065eb8c44a4dffff001d0561c0ee +01000000e6c388d72c717914e507f11c58a139137fd483dbd746e9c5f2a1eb0f00000000afa8dd6f0e319d4ddedfa1425f6c03e461dfa005d42d3f355a04cae42626c9b121c64a4dffff001d059f05b2 +010000001b354bd6d10f5a24879851bb2aff42548cb66cebda04e7103bc2521900000000622c689b318941bef087640fddab2c5ef6dd6024f133ac28c1ccfaf30e380bfa4dc64a4dffff001d00484506 +010000003e226e00e126f0a4a35b1c6c09509e0d999122428262a5303e563c6000000000d7432b21fd48689d7e79f9b6f7f624aefe8b72503fca13b814d355618b9023cf23c74a4dffff001d0247f874 +01000000fbb3060018eb1f40d734cc64744134d96536c4a331e00d4c05e2cf9b0000000061b34d561ddfd18ea3381d48a8bbe4627727c6ead00a84030e7f76eeb788148a71c74a4dffff001d012e783d +01000000fff008ec46c2d35cf469eded16dfadc77ca6e9a9680cea0b0611661b000000001d1e1087d20d41da1f0b4a5423aeb281ecd48cfc434feb45a4c6c31b6b61398124c84a4dffff001d05928767 +0100000093ebaafdaac804feef3ec352e40cc78be60e7ef1edb167891ccd99cc000000006bf225e961b1f1b8e042c45966c2118f20871551e0996eaf51bb628cd9d49b7a45c84a4dffff001d00381daf +01000000e85fb976eb4817418c0ed2987dba1674c0fd757f70b470f83d01a2d300000000fd6717e5d49bf303d41d861fc50503aa8575eb52243f426de308869a8cd57c57f5c74a4dffff001d033a590f +010000009cf5f976b9ae634b4c867bf78cb602bee8150ea1838d0ef3d06ce94700000000fe6ce75c462d7d09aa8d917fa90a49bd6a4c41f02457ff40ca47ded089021042b0c84a4dffff001d03a5f6aa +010000002eff16b6669f88de6f40810c57349804014f734c692a44ee822b7f1200000000c095ce4ab7e0e02110b75a33012da97d75e26b83de5ae0bd392fc3b8191b77e8e8c84a4dffff001d032c2bf6 +0100000001aa2c494f2d4b7ecf367734c734b931d3592b1572dcff557186ece700000000a10e60b120c06cd1be1d2153abb7901e2d9f1a3f43de9e6642ad269be1cae22e44c94a4dffff001d01f2005b +010000003d04e058642d22428704ad4337f372bb8574cae072c2a4b469b2e1dd00000000bff07e723c3859fe85bb849d860b21a325b1baf1494a3c1e73435dd7992bf8e53dc94a4dffff001d00262757 +01000000272ecd270665dc39e924838516da62f8588270f1e37812aabdb1d48c000000001725d4769aaca3cf86c5b0dd199bf93f2d16dbb13fbd4029633bdd1085283fdd13c94a4dffff001d02f79a8a +01000000705f1bbf68b4976a9ade4ff01ae933030cfe4cd43e8092c9604442a2000000000cb7f30461a3563992f7b02095263fdceb13984123a28ea68d0989601655e84ab8c94a4dffff001d0017e0d9 +0100000082d6e6bf4ed6c5b989705920effd072fe6b7119ea5b8c8fa6d0bf5c70000000071959f90386cdc263655d36033817a6f69cbc2d99fc56cc81f893b9787907a4fcfc94a4dffff001d000f9093 +01000000374e3a88a3a3eef84b4212e90e863754ac2a6999747c22667c0b29e0000000003a0e3693a8455ac94beb574efd98297948c5b5f69eaf0b813e6d5e57426e39e279ca4a4dffff001d042b079c +0100000003f06b3017023d1cbddd4cf169c4e74879754683f0d7670f1d1197f70000000098b12eacd55084fe2807a33dc2c89e6fc25f382aba3c6b4c7ee2f74a6999a6bbfbca4a4dffff001d03ba1eb8 +010000009c0730eadadb66fa486e40dbddd14b635ec7939f788fdfd999e043f2000000006b0a86109aea3d5e4b7dc320da600dff19d81dce7b77dab9596204c20c6125b06eca4a4dffff001d01c88a1c +01000000410f77945e68dba04d76fc931b26ccb571159c9bad438df5f8fc4d190000000004d902fedee34fce75de583a54e1fe7ef26a7946b48b93b9b0834dbf19a0b47f3dcb4a4dffff001d016618bc +0100000025ec91d09a451f18667262004e0cebb2ad66262407ed0a8f4ddbff580000000092f8e1be93272aae6b36766d721df3d80444582edec0143174c5fafad4cef50c79cd4a4dffff001d031ee0cf +01000000d8d0cea1249f726362ee78259d97d98efa17ab0efeb1b9d0a0cf0d880000000035c3dc39e544b855457fb0711697a24123fc3ba6c439123714b193b9d1d9ec7514cd4a4dffff001d0526c906 +010000003e824b7a8cb7bea1dddcaee7bbc4720e8c5d2f53c9c28dbaea4662a90000000061ef7a1ca4f6f61cc594d040490d3cb2f11054639a0a964c67cebc6dbc079e36cacc4a4dffff001d03af40bf +01000000da9201093da940fb6badc420406540a979cd5c06fc2f47db3215130b0000000069577de9d33e6351fb0ac1be26158f94c217d2522c35e9f813e47bf9811fad72dacd4a4dffff001d02f70faa +010000003dca51f6b97f7c05a44f329c1c9f4d7cf03eeb3b16d94bd56e34b50600000000092122ea597797ea0de7841945f09c221578800d0c44de4961cad7ecfb93c47323ce4a4dffff001d03bd9635 +01000000a560b2ead8a3f50df84385c1d5a51654c22a321268e077dfcb9ef90c0000000091db9cfec3e6c44a790d6295a2effcb8dc94f9f1c0fc4f6d867e90e140c1e9f325cf4a4dffff001d03a86875 +0100000056e6a9a6b93f9ad3ad847ee845ee9c366dc9376489516c31d892ed5e00000000bf2f3b6867855fe1058092fec7c5ea89c15cf8a70d0c565c26362d10129221af47cf4a4dffff001d027a9b69 +01000000d123e40889dc9dd8039943b5b1a0de53026e9ce92cfc69d5f8482fbc0000000022b49f0319707490d002d5b2c04adfc7a6433c2931ed04545e9c33f37a12b5bb924bbe4fffff001d00b7fffa +0100000065aa216d118366b22580747fb4d876d80ae3be145e318563984e0b040000000000c2b33efaf934623c6bea64570558d6551524e2f45d022b0fe935387279e6a37b51be4fffff001d0511e593 +01000000eced0b25fadf8818fabbf65b9c16f39874336d7faca51b74698ad55500000000ee49643ded3202eb46b451c55a652c6cf38d82da93de97a2f2bc0e8a28c1be7ba951be4fffff001d01f82bc0 +01000000bc9c266783225bfa273dbba2342f54809dcc2b263e1e6bf3269bda300000000001aa244355f8827bdc7eaaa0defe1a928816cf7d919890781c1fe3ee58d985684d53be4fffff001d01a443c5 +010000001b7467dcebcf2baa4814161dd6c7ab966ba082d670a69eaacc1785910000000050098a241f8df957a6bbea8ecc39473091636a4e55d673ae723edb6a3f50b9835853be4fffff001d013788f6 +01000000f5023ad6b3432b6b2d5afe707cfbfa0a30e94000799c8430a97da151000000007a53e7dbfb43a28ee9d5956be8da4190e5be6f16da4a5636f6139742754ec0cedc54be4fffff001d04474930 +010000002077ea8e53ba9a132d83b91e40fb1f4c724217b8197c4533a5bee9e90000000096a376627bd7b42278fb33609713b83665f195d100316b9e1651638cd4d7d47cfd84bf4fffff001d027c40c5 +010000001936a4ef861f194c15f171e843866f33ec3b3cf09394bc323753beb8000000002e3288941ec156d0fdc90d314d85a71a418d272704e12f2a99576fe3201e2d1b3887bf4fffff001d027aefaf +010000006e5c2e805629d476b92e0a8d74c3ee0dea0c24e9f35ab4afc6ac77b400000000d26cbed9a7c1e51fe657c6b3cdf202926e0b6e371ebf41ccdcbc0909470d56256e88bf4fffff001d05a2db35 +01000000e5f26b5abf56353520e1f9baa35e9cdeb57f257f59ff6a4ee42c3795000000007becb215d446a70b351fdb14659d105b3c258802fa4314b536c4e5249adaab2a9c88bf4fffff001d04e36b51 +010000003cf3550994f31a27c040b1340c7ae9ccd36d554a1bfa797cf46504c00000000005f56011e8d4bf55bca3dd8c9799e0d83304e43ae8b66ee58c688bb9d7f7fefeb088bf4fffff001d0314c7c0 +01000000ad48a4d4922e3834375f8c50e8cb1842688829c8952204f66b89a7e9000000004579dc3ff13abb920007843862e9091b0e142564cd0a75f5fc3272982b816d4ee988bf4fffff001d00becf18 +0100000098eff668afac04d3d8531d9bc31aafcd48f7ba68b080b40641dfddc200000000a91184d97bec523016c15dfc3473e7407278b70638f4fd6c9dc1ba210454f7fb1189bf4fffff001d042a1349 +01000000ed2b2b04575642de5fc665cb8e30f3e39dea66299592405204bf6b5d00000000f3af0d684b2c16f307b6ddf9a02b58b9079e858dd2d093aa29288856b83b82183789bf4fffff001d002cfc38 +01000000a398a8e978de6f1e7b9b84523d45e5f2021feb29f6d39890a2f48afd00000000f4a78ab4703ca95ed478e78e526f1a8198c3cbfb8a9ee6455338d517aa18173f3e89bf4fffff001d008b40b4 +0100000036bef7eedabe3b05e1e049efcf66823190285d64d11ccfd2eccb5e3200000000243f2ed308bd48cd3dd25d4cc649dea0d0518d6e12612e03c8a0888aa284bf2d6189bf4fffff001d0498f47c +010000008d84b4fadc45563b60c0f50797ffaf71046f287482ab9fe629f64e4e000000003ae5c2515afb4117d032de958e0ba535e484575b80ff6d6e2e0a23e6dc95e0ea7889bf4fffff001d0518a2da +0100000020ddc91a15f8c9ec5161bf1709ad7f37c33ecce9809b353dfd3df2a400000000f2b6d372af2c7164d36dc1c369090f4f9179b0870676ab6ec2641b0922b1fd179289bf4fffff001d04bbff73 +010000009701e67638ec4e8b47c547ac3125ac6c1892a98d77a580ab88f069d5000000007c740055656afd61883f9c1c102fec710315e6e4d58904accd184cba85c9f958cd8bbf4fffff001d03f8f01b +01000000fa9f56e3e4fe1352905b3a1f12567caa3f9f4251f0bda94fb07f1684000000009ff7fbae47f6afb47a1723662d196155b5f4bc1c62e882a93f54ba2d9a10913b508cbf4fffff001d02d1c97b +0100000002ae1c66166259ef43d290c6a60cf8eb5783e6c91293a3566946673a00000000846d597237ea76576bc49381badb59bd509cfda3095c2c1ce5c75788b96aa144718cbf4fffff001d0139c1f5 +010000001ed0a06499985c4cfb05371eec681819d8e193b66011f57b66792565000000008980b714aeb09568fd34128f4c7ff84d3e09223dcb2b290c5948816cc636d69fa58cbf4fffff001d0122c5f4 +010000008c045f9e0e200a8eea9c0957b12e045e927dc768a8a5fc2d8fd8d9ad000000009925a3f617c025b95f6329247379b7ef7bd081e52838676671c7360eec07255a098dbf4fffff001d0099c40f +01000000a6adbd81b12cff9948911af583db14484c4c0d30e91b1af2e7fd4d4f00000000a70e4b2057854c71170f8468b799f03d0344905213872216f4210f7bfcf76980198dbf4fffff001d002b7c3a +0100000059e1e5ae6727e02c2acf95bc5f24d8274e42495c790125ba52a581170000000043412a5a9f1749688460eee46b62851641fd9b17f9ed5131b3f50e4f160eadc45cb3bf4fffff001d03a966ba +01000000c704e90a0c4cb992d6973d636a7094ef2790a60231db345a926b6cb6000000006f98c89001a2e89ea35aaf00b85d965af5598b61bc48d9d9847609331995383effb4bf4fffff001d02ee5af9 +010000005468467a64f255415f3ad770f0d5393275f2813fd0e1e915a2b8c28b000000000566f29ed6b8cdf85a4b197dcbb0135dfb7b671f83ca67970bffea903309ab7445b5bf4fffff001d032ac068 +0100000021ad75b801d48a30fa5b4e6b1e43106a294a5e2763307a992fbd5282000000002c20851e58dc1a2ceb7c7771822ed41c7d0994731d01829628c3e938f4155a276eb5bf4fffff001d01a1420f +0100000002954bf32d68d54adac42cb5190efc06f18ead35208dc14a8face6f1000000009002c5c2dfd0259960507e69399c688f0d74bbcd52c810cd2d2507a829133f4941bdbf4fffff001d01724be9 +01000000add5df18f427437ace8b40064b3806583217a3f724672cf72cf8c18300000000bc652800c84513b2de38e0e97be92a582a628e1e49a9fe8d49aea5623251eda54abdbf4fffff001d045ec8ea +010000003b0c953fed585c1ac952df84e4c3953ba551da457c700035f6f3fc0c00000000831c01723b0fa955a6fbd51d9ddc5c77964814868f96f52ea3eaf3b66098e6df9dbdbf4fffff001d00cb77db +01000000f6667e8c4557c28f88b86a6892d03509a5afc9a89088a5bb8638eafa00000000ff61d87fbc86dac7f80962702153a2a6d9a84dac13c265106b883ed7ef68fc91cebdbf4fffff001d00e07f63 +010000002cb99b76cd981a422d02d61684f76bacea92669298e552e412ce41df000000005f2a9977b14cf34078500f956be0cc154291ce5a0e34c00a3dbbae97bed5e930f5bdbf4fffff001d008fd760 diff --git a/test/functional/data/wallets/high_minversion/.walletlock b/test/functional/data/wallets/high_minversion/.walletlock new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/data/wallets/high_minversion/.walletlock diff --git a/test/functional/data/wallets/high_minversion/db.log b/test/functional/data/wallets/high_minversion/db.log new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/data/wallets/high_minversion/db.log diff --git a/test/functional/data/wallets/high_minversion/wallet.dat b/test/functional/data/wallets/high_minversion/wallet.dat Binary files differnew file mode 100644 index 0000000000..99ab809263 --- /dev/null +++ b/test/functional/data/wallets/high_minversion/wallet.dat diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py index 8bb7e02695..e6ff21ee9c 100755 --- a/test/functional/feature_logging.py +++ b/test/functional/feature_logging.py @@ -36,7 +36,7 @@ class LoggingTest(BitcoinTestFramework): invdir = self.relative_log_path("foo") invalidname = os.path.join("foo", "foo.log") self.stop_node(0) - exp_stderr = "Error: Could not open debug log file \S+$" + exp_stderr = r"Error: Could not open debug log file \S+$" self.nodes[0].assert_start_raises_init_error(["-debuglogfile=%s" % (invalidname)], exp_stderr, match=ErrorMatch.FULL_REGEX) assert not os.path.isfile(os.path.join(invdir, "foo.log")) diff --git a/test/functional/feature_uacomment.py b/test/functional/feature_uacomment.py index fb4ad21359..85c250173f 100755 --- a/test/functional/feature_uacomment.py +++ b/test/functional/feature_uacomment.py @@ -27,12 +27,12 @@ class UacommentTest(BitcoinTestFramework): self.log.info("test -uacomment max length") self.stop_node(0) - expected = "Error: Total length of network version string \([0-9]+\) exceeds maximum length \(256\). Reduce the number or size of uacomments." + expected = r"Error: Total length of network version string \([0-9]+\) exceeds maximum length \(256\). Reduce the number or size of uacomments." self.nodes[0].assert_start_raises_init_error(["-uacomment=" + 'a' * 256], expected, match=ErrorMatch.FULL_REGEX) self.log.info("test -uacomment unsafe characters") for unsafe_char in ['/', ':', '(', ')', '₿', '🏃']: - expected = "Error: User Agent comment \(" + re.escape(unsafe_char) + "\) contains unsafe characters." + expected = r"Error: User Agent comment \(" + re.escape(unsafe_char) + r"\) contains unsafe characters." self.nodes[0].assert_start_raises_init_error(["-uacomment=" + unsafe_char], expected, match=ErrorMatch.FULL_REGEX) diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py index 30f851fb8e..0739d7e29b 100755 --- a/test/functional/mempool_package_onemore.py +++ b/test/functional/mempool_package_onemore.py @@ -33,7 +33,7 @@ class MempoolPackagesTest(BitcoinTestFramework): outputs = {} for i in range(num_outputs): outputs[node.getnewaddress()] = send_value - rawtx = node.createrawtransaction(inputs, outputs) + rawtx = node.createrawtransaction(inputs, outputs, 0, True) signedtx = node.signrawtransactionwithwallet(rawtx) txid = node.sendrawtransaction(signedtx['hex']) fulltx = node.getrawtransaction(txid, 1) @@ -75,10 +75,16 @@ class MempoolPackagesTest(BitcoinTestFramework): # ...especially if its > 40k weight assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) # But not if it chains directly off the first transaction - self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) + (replacable_txid, replacable_orig_value) = self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) # and the second chain should work just fine self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) + # Make sure we can RBF the chain which used our carve-out rule + second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['addresses'][0]: replacable_orig_value - (Decimal(1) / Decimal(100))} + second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs) + signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx) + self.nodes[0].sendrawtransaction(signed_second_tx['hex']) + # Finally, check that we added two transactions assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 3) diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index 12cb06a407..3258a38e3c 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -19,7 +19,7 @@ class P2PBlocksOnly(BitcoinTestFramework): def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) - self.log.info('Check that txs from p2p are rejected') + self.log.info('Check that txs from p2p are rejected and result in disconnect') prevtx = self.nodes[0].getblock(self.nodes[0].getblockhash(1), 2)['tx'][0] rawtx = self.nodes[0].createrawtransaction( inputs=[{ @@ -42,13 +42,17 @@ class P2PBlocksOnly(BitcoinTestFramework): assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False) with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']): self.nodes[0].p2p.send_message(msg_tx(FromHex(CTransaction(), sigtx))) - self.nodes[0].p2p.sync_with_ping() + self.nodes[0].p2p.wait_for_disconnect() assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) + # Remove the disconnected peer and add a new one. + del self.nodes[0].p2ps[0] + self.nodes[0].add_p2p_connection(P2PInterface()) + self.log.info('Check that txs from rpc are not rejected and relayed to other peers') assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True) txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid'] - with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=0'.format(txid)]): + with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=1'.format(txid)]): self.nodes[0].sendrawtransaction(sigtx) self.nodes[0].p2p.wait_for_tx(txid) assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py new file mode 100755 index 0000000000..7d386c47f6 --- /dev/null +++ b/test/functional/p2p_dos_header_tree.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat""" + +from test_framework.messages import ( + CBlockHeader, + FromHex, +) +from test_framework.mininode import ( + P2PInterface, + msg_headers, +) +from test_framework.test_framework import BitcoinTestFramework + +import os + + +class RejectLowDifficultyHeadersTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint + self.num_nodes = 2 + + def add_options(self, parser): + parser.add_argument( + '--datafile', + default='data/blockheader_testnet3.hex', + help='Test data file (default: %(default)s)', + ) + + def run_test(self): + self.log.info("Read headers data") + self.headers_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.options.datafile) + with open(self.headers_file_path, encoding='utf-8') as headers_data: + h_lines = [l.strip() for l in headers_data.readlines()] + + # The headers data is taken from testnet3 for early blocks from genesis until the first checkpoint. There are + # two headers with valid POW at height 1 and 2, forking off from genesis. They are indicated by the FORK_PREFIX. + FORK_PREFIX = 'fork:' + self.headers = [l for l in h_lines if not l.startswith(FORK_PREFIX)] + self.headers_fork = [l[len(FORK_PREFIX):] for l in h_lines if l.startswith(FORK_PREFIX)] + + self.headers = [FromHex(CBlockHeader(), h) for h in self.headers] + self.headers_fork = [FromHex(CBlockHeader(), h) for h in self.headers_fork] + + self.log.info("Feed all non-fork headers, including and up to the first checkpoint") + self.nodes[0].add_p2p_connection(P2PInterface()) + self.nodes[0].p2p.send_message(msg_headers(self.headers)) + self.nodes[0].p2p.sync_with_ping() + assert { + 'height': 546, + 'hash': '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70', + 'branchlen': 546, + 'status': 'headers-only', + } in self.nodes[0].getchaintips() + + self.log.info("Feed all fork headers (fails due to checkpoint)") + with self.nodes[0].assert_debug_log(['bad-fork-prior-to-checkpoint (code 67)']): + self.nodes[0].p2p.send_message(msg_headers(self.headers_fork)) + self.nodes[0].p2p.wait_for_disconnect() + + self.log.info("Feed all fork headers (succeeds without checkpoint)") + # On node 0 it succeeds because checkpoints are disabled + self.restart_node(0, extra_args=['-nocheckpoints']) + self.nodes[0].add_p2p_connection(P2PInterface()) + self.nodes[0].p2p.send_message(msg_headers(self.headers_fork)) + self.nodes[0].p2p.sync_with_ping() + assert { + "height": 2, + "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", + "branchlen": 2, + "status": "headers-only", + } in self.nodes[0].getchaintips() + + # On node 1 it succeeds because no checkpoint has been reached yet by a chain tip + self.nodes[1].add_p2p_connection(P2PInterface()) + self.nodes[1].p2p.send_message(msg_headers(self.headers_fork)) + self.nodes[1].p2p.sync_with_ping() + assert { + "height": 2, + "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", + "branchlen": 2, + "status": "headers-only", + } in self.nodes[1].getchaintips() + + +if __name__ == '__main__': + RejectLowDifficultyHeadersTest().main() diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 6c30e05084..266a0d6cd2 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -186,6 +186,7 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(chaintxstats['time'], b200['time']) assert_equal(chaintxstats['txcount'], 201) assert_equal(chaintxstats['window_final_block_hash'], b200_hash) + assert_equal(chaintxstats['window_final_block_height'], 200) assert_equal(chaintxstats['window_block_count'], 199) assert_equal(chaintxstats['window_tx_count'], 199) assert_equal(chaintxstats['window_interval'], time_diff) @@ -195,6 +196,7 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(chaintxstats['time'], b1['time']) assert_equal(chaintxstats['txcount'], 2) assert_equal(chaintxstats['window_final_block_hash'], b1_hash) + assert_equal(chaintxstats['window_final_block_height'], 1) assert_equal(chaintxstats['window_block_count'], 0) assert 'window_tx_count' not in chaintxstats assert 'window_interval' not in chaintxstats diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 62f3843756..056e193d55 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -134,6 +134,27 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Missing redeemScript/witnessScript", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + # if witnessScript specified, all ok + prevtx_err["witnessScript"] = prevtxs[0]["redeemScript"] + node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + + # both specified, also ok + prevtx_err["redeemScript"] = prevtxs[0]["redeemScript"] + node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + + # redeemScript mismatch to witnessScript + prevtx_err["redeemScript"] = "6a" # OP_RETURN + assert_raises_rpc_error(-8, "redeemScript does not correspond to witnessScript", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + + # redeemScript does not match scriptPubKey + del prevtx_err["witnessScript"] + assert_raises_rpc_error(-8, "redeemScript/witnessScript does not match scriptPubKey", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + + # witnessScript does not match scriptPubKey + prevtx_err["witnessScript"] = prevtx_err["redeemScript"] + del prevtx_err["redeemScript"] + assert_raises_rpc_error(-8, "redeemScript/witnessScript does not match scriptPubKey", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs) rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs) diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index b12eb1d9ec..104c66aaa7 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -20,7 +20,32 @@ from test_framework.util import ( wait_until, ) from test_framework.mininode import P2PInterface -from test_framework.messages import CAddress, msg_addr, NODE_NETWORK, NODE_WITNESS +from test_framework.messages import ( + CAddress, + msg_addr, + NODE_NETWORK, + NODE_WITNESS, + NODE_GETUTXO,NODE_BLOOM, + NODE_NETWORK_LIMITED, +) + +def assert_net_servicesnames(servicesflag, servicenames): + """Utility that checks if all flags are correctly decoded in + `getpeerinfo` and `getnetworkinfo`. + + :param servicesflag: The services as an integer. + :param servicesnames: The list of decoded services names, as strings. + """ + if servicesflag & NODE_NETWORK: + assert "NETWORK" in servicenames + if servicesflag & NODE_GETUTXO: + assert "GETUTXO" in servicenames + if servicesflag & NODE_BLOOM: + assert "BLOOM" in servicenames + if servicesflag & NODE_WITNESS: + assert "WITNESS" in servicenames + if servicesflag & NODE_NETWORK_LIMITED: + assert "NETWORK_LIMITED" in servicenames class NetTest(BitcoinTestFramework): def set_test_params(self): @@ -31,7 +56,7 @@ class NetTest(BitcoinTestFramework): def run_test(self): self._test_connection_count() self._test_getnettotals() - self._test_getnetworkinginfo() + self._test_getnetworkinfo() self._test_getaddednodeinfo() self._test_getpeerinfo() self._test_getnodeaddresses() @@ -70,7 +95,7 @@ class NetTest(BitcoinTestFramework): assert_greater_than_or_equal(after['bytesrecv_per_msg'].get('pong', 0), before['bytesrecv_per_msg'].get('pong', 0) + 32) assert_greater_than_or_equal(after['bytessent_per_msg'].get('ping', 0), before['bytessent_per_msg'].get('ping', 0) + 32) - def _test_getnetworkinginfo(self): + def _test_getnetworkinfo(self): assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) @@ -84,6 +109,11 @@ class NetTest(BitcoinTestFramework): assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) + # check the `servicesnames` field + network_info = [node.getnetworkinfo() for node in self.nodes] + for info in network_info: + assert_net_servicesnames(int(info["localservices"]), info["localservicesnames"]) + def _test_getaddednodeinfo(self): assert_equal(self.nodes[0].getaddednodeinfo(), []) # add a node (node2) to node0 @@ -104,6 +134,9 @@ class NetTest(BitcoinTestFramework): assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr']) assert_equal(peer_info[0][0]['minfeefilter'], Decimal("0.00000500")) assert_equal(peer_info[1][0]['minfeefilter'], Decimal("0.00001000")) + # check the `servicesnames` field + for info in peer_info: + assert_net_servicesnames(int(info[0]["services"]), info[0]["servicesnames"]) def _test_getnodeaddresses(self): self.nodes[0].add_p2p_connection(P2PInterface()) diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index a1cd33ad54..9f94d11a93 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -58,6 +58,13 @@ class ScantxoutsetTest(BitcoinTestFramework): self.start_node(0) self.nodes[0].generate(110) + scan = self.nodes[0].scantxoutset("start", []) + info = self.nodes[0].gettxoutsetinfo() + assert_equal(scan['success'], True) + assert_equal(scan['height'], info['height']) + assert_equal(scan['txouts'], info['txouts']) + assert_equal(scan['bestblock'], info['bestblock']) + self.restart_node(0, ['-nowallet']) self.log.info("Test if we have found the non HD unspent outputs.") assert_equal(self.nodes[0].scantxoutset("start", [ "pkh(" + pubk1 + ")", "pkh(" + pubk2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.002")) diff --git a/test/functional/rpc_setban.py b/test/functional/rpc_setban.py index 423741fd27..b1d2b6f431 100755 --- a/test/functional/rpc_setban.py +++ b/test/functional/rpc_setban.py @@ -26,7 +26,7 @@ class SetBanTests(BitcoinTestFramework): self.nodes[1].setban("127.0.0.1", "add") # Node 0 should not be able to reconnect - with self.nodes[1].assert_debug_log(expected_msgs=['dropped (banned)\n'],timeout=5): + with self.nodes[1].assert_debug_log(expected_msgs=['dropped (banned)\n'], timeout=5): self.restart_node(1, []) self.nodes[0].addnode("127.0.0.1:" + str(p2p_port(1)), "onetry") diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 89a5a65e64..3cb5f56bee 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -43,8 +43,8 @@ COIN = 100000000 # 1 btc in satoshis BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out NODE_NETWORK = (1 << 0) -# NODE_GETUTXO = (1 << 1) -# NODE_BLOOM = (1 << 2) +NODE_GETUTXO = (1 << 1) +NODE_BLOOM = (1 << 2) NODE_WITNESS = (1 << 3) NODE_NETWORK_LIMITED = (1 << 10) @@ -803,7 +803,9 @@ class HeaderAndShortIDs: return [ key0, key1 ] # Version 2 compact blocks use wtxid in shortids (rather than txid) - def initialize_from_block(self, block, nonce=0, prefill_list = [0], use_witness = False): + def initialize_from_block(self, block, nonce=0, prefill_list=None, use_witness=False): + if prefill_list is None: + prefill_list = [0] self.header = CBlockHeader(block) self.nonce = nonce self.prefilled_txn = [ PrefilledTransaction(i, block.vtx[i]) for i in prefill_list ] diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 779863df79..166438cf70 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -111,7 +111,7 @@ class P2PConnection(asyncio.Protocol): def is_connected(self): return self._transport is not None - def peer_connect(self, dstaddr, dstport, net="regtest"): + def peer_connect(self, dstaddr, dstport, *, net): assert not self.is_connected self.dstaddr = dstaddr self.dstport = dstport diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 9667cf4ea4..d8bfdfd040 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -313,24 +313,24 @@ class TestNode(): with open(debug_log, encoding='utf-8') as dl: dl.seek(0, 2) prev_size = dl.tell() - try: - yield - finally: - while True: - found = True - with open(debug_log, encoding='utf-8') as dl: - dl.seek(prev_size) - log = dl.read() - print_log = " - " + "\n - ".join(log.splitlines()) - for expected_msg in expected_msgs: - if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: - found = False - if found: - return - if time.time() >= time_end: - break - time.sleep(0.05) - self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) + + yield + + while True: + found = True + with open(debug_log, encoding='utf-8') as dl: + dl.seek(prev_size) + log = dl.read() + print_log = " - " + "\n - ".join(log.splitlines()) + for expected_msg in expected_msgs: + if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: + found = False + if found: + return + if time.time() >= time_end: + break + time.sleep(0.05) + self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) @contextlib.contextmanager def assert_memory_usage_stable(self, *, increase_allowed=0.03): @@ -489,7 +489,7 @@ class TestNode(): if 'dstaddr' not in kwargs: kwargs['dstaddr'] = '127.0.0.1' - p2p_conn.peer_connect(**kwargs)() + p2p_conn.peer_connect(**kwargs, net=self.chain)() self.p2ps.append(p2p_conn) if wait_for_verack: p2p_conn.wait_for_verack() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 821e1cd3c5..f9f5fe553e 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -56,7 +56,9 @@ def assert_raises_message(exc, message, fun, *args, **kwds): raise AssertionError("Use assert_raises_rpc_error() to test RPC failures") except exc as e: if message is not None and message not in e.error['message']: - raise AssertionError("Expected substring not found:" + e.error['message']) + raise AssertionError( + "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( + message, e.error['message'])) except Exception as e: raise AssertionError("Unexpected exception raised: " + type(e).__name__) else: @@ -116,7 +118,9 @@ def try_rpc(code, message, fun, *args, **kwds): if (code is not None) and (code != e.error["code"]): raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"]) if (message is not None) and (message not in e.error['message']): - raise AssertionError("Expected substring not found:" + e.error['message']) + raise AssertionError( + "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( + message, e.error['message'])) return True except Exception as e: raise AssertionError("Unexpected exception raised: " + type(e).__name__) @@ -283,14 +287,22 @@ def initialize_datadir(dirname, n, chain): datadir = get_datadir_path(dirname, n) if not os.path.isdir(datadir): os.makedirs(datadir) + # Translate chain name to config name + if chain == 'testnet3': + chain_name_conf_arg = 'testnet' + chain_name_conf_section = 'test' + else: + chain_name_conf_arg = chain + chain_name_conf_section = chain with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f: - f.write("{}=1\n".format(chain)) - f.write("[{}]\n".format(chain)) + f.write("{}=1\n".format(chain_name_conf_arg)) + f.write("[{}]\n".format(chain_name_conf_section)) f.write("port=" + str(p2p_port(n)) + "\n") f.write("rpcport=" + str(rpc_port(n)) + "\n") f.write("server=1\n") f.write("keypool=1\n") f.write("discover=0\n") + f.write("dnsseed=0\n") f.write("listenonion=0\n") f.write("printtoconsole=0\n") f.write("upnp=0\n") diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ad5673e03a..9c92091f1d 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -131,6 +131,7 @@ BASE_SCRIPTS = [ 'wallet_createwallet.py --usecli', 'wallet_watchonly.py', 'wallet_watchonly.py --usecli', + 'wallet_reorgsrestore.py', 'interface_http.py', 'interface_rpc.py', 'rpc_psbt.py', @@ -196,6 +197,7 @@ BASE_SCRIPTS = [ 'feature_uacomment.py', 'wallet_coinbase_category.py', 'feature_filelock.py', + 'p2p_dos_header_tree.py', 'p2p_unrequested_blocks.py', 'feature_includeconf.py', 'rpc_deriveaddresses.py', diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 34e84fcf55..ce04110240 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -499,6 +499,36 @@ class WalletTest(BitcoinTestFramework): self.nodes[0].setlabel(change, 'foobar') assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False) + # Test gettransaction response with different arguments. + self.log.info("Testing gettransaction response with different arguments...") + self.nodes[0].setlabel(change, 'baz') + baz = self.nodes[0].listtransactions(label="baz", count=1)[0] + expected_receive_vout = {"label": "baz", + "address": baz["address"], + "amount": baz["amount"], + "category": baz["category"], + "vout": baz["vout"]} + expected_fields = frozenset({'amount', 'bip125-replaceable', 'confirmations', 'details', 'fee', + 'hex', 'time', 'timereceived', 'trusted', 'txid', 'walletconflicts'}) + verbose_field = "decoded" + expected_verbose_fields = expected_fields | {verbose_field} + + self.log.debug("Testing gettransaction response without verbose") + tx = self.nodes[0].gettransaction(txid=txid) + assert_equal(set([*tx]), expected_fields) + assert_array_result(tx["details"], {"category": "receive"}, expected_receive_vout) + + self.log.debug("Testing gettransaction response with verbose set to False") + tx = self.nodes[0].gettransaction(txid=txid, verbose=False) + assert_equal(set([*tx]), expected_fields) + assert_array_result(tx["details"], {"category": "receive"}, expected_receive_vout) + + self.log.debug("Testing gettransaction response with verbose set to True") + tx = self.nodes[0].gettransaction(txid=txid, verbose=True) + assert_equal(set([*tx]), expected_verbose_fields) + assert_array_result(tx["details"], {"category": "receive"}, expected_receive_vout) + assert_equal(tx[verbose_field], self.nodes[0].decoderawtransaction(tx["hex"])) + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index e4a4ab1f35..23748e5dd7 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -44,8 +44,10 @@ class ImportMultiTest(BitcoinTestFramework): def setup_network(self): self.setup_nodes() - def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=[]): + def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=None): """Run importmulti and assert success""" + if warnings is None: + warnings = [] result = self.nodes[1].importmulti([req]) observed_warnings = [] if 'warnings' in result[0]: diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 984ffab5a4..68bc45f986 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -17,6 +17,8 @@ from test_framework.util import ( assert_raises_rpc_error, ) +FEATURE_LATEST = 169900 + class MultiWalletTest(BitcoinTestFramework): def set_test_params(self): @@ -27,6 +29,13 @@ class MultiWalletTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def add_options(self, parser): + parser.add_argument( + '--data_wallets_dir', + default=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/wallets/'), + help='Test data with wallet directories (default: %(default)s)', + ) + def run_test(self): node = self.nodes[0] @@ -93,12 +102,12 @@ class MultiWalletTest(BitcoinTestFramework): # should not initialize if one wallet is a copy of another shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy')) - exp_stderr = "BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" + exp_stderr = r"BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) # should not initialize if wallet file is a symlink os.symlink('w8', wallet_dir('w8_symlink')) - self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) + self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], r'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) # should not initialize if the specified walletdir does not exist self.nodes[0].assert_start_raises_init_error(['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') @@ -139,7 +148,7 @@ class MultiWalletTest(BitcoinTestFramework): competing_wallet_dir = os.path.join(self.options.tmpdir, 'competing_walletdir') os.mkdir(competing_wallet_dir) self.restart_node(0, ['-walletdir=' + competing_wallet_dir]) - exp_stderr = "Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" + exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" self.nodes[1].assert_start_raises_init_error(['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) self.restart_node(0, extra_args) @@ -323,6 +332,22 @@ class MultiWalletTest(BitcoinTestFramework): self.nodes[0].unloadwallet(wallet) self.nodes[1].loadwallet(wallet) + # Fail to load if wallet is downgraded + shutil.copytree(os.path.join(self.options.data_wallets_dir, 'high_minversion'), wallet_dir('high_minversion')) + self.restart_node(0, extra_args=['-upgradewallet={}'.format(FEATURE_LATEST)]) + assert {'name': 'high_minversion'} in self.nodes[0].listwalletdir()['wallets'] + self.log.info("Fail -upgradewallet that results in downgrade") + assert_raises_rpc_error( + -4, + "Wallet loading failed.", + lambda: self.nodes[0].loadwallet(filename='high_minversion'), + ) + self.stop_node( + i=0, + expected_stderr='Error: Error loading {}: Wallet requires newer version of Bitcoin Core'.format( + wallet_dir('high_minversion', 'wallet.dat')), + ) + if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py new file mode 100755 index 0000000000..f48018e9fb --- /dev/null +++ b/test/functional/wallet_reorgsrestore.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test tx status in case of reorgs while wallet being shutdown. + +Wallet txn status rely on block connection/disconnection for its +accuracy. In case of reorgs happening while wallet being shutdown +block updates are not going to be received. At wallet loading, we +check against chain if confirmed txn are still in chain and change +their status if block in which they have been included has been +disconnected. +""" + +from decimal import Decimal +import os +import shutil + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, + disconnect_nodes, +) + +class ReorgsRestoreTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 3 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + # Send a tx from which to conflict outputs later + txid_conflict_from = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].generate(1) + self.sync_blocks() + + # Disconnect node1 from others to reorg its chain later + disconnect_nodes(self.nodes[0], 1) + disconnect_nodes(self.nodes[1], 2) + connect_nodes(self.nodes[0], 2) + + # Send a tx to be unconfirmed later + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + tx = self.nodes[0].gettransaction(txid) + self.nodes[0].generate(4) + tx_before_reorg = self.nodes[0].gettransaction(txid) + assert_equal(tx_before_reorg["confirmations"], 4) + + # Disconnect node0 from node2 to broadcast a conflict on their respective chains + disconnect_nodes(self.nodes[0], 2) + nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10")) + inputs = [] + inputs.append({"txid": txid_conflict_from, "vout": nA}) + outputs_1 = {} + outputs_2 = {} + + # Create a conflicted tx broadcast on node0 chain and conflicting tx broadcast on node1 chain. Both spend from txid_conflict_from + outputs_1[self.nodes[0].getnewaddress()] = Decimal("9.99998") + outputs_2[self.nodes[0].getnewaddress()] = Decimal("9.99998") + conflicted = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_1)) + conflicting = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_2)) + + conflicted_txid = self.nodes[0].sendrawtransaction(conflicted["hex"]) + self.nodes[0].generate(1) + conflicting_txid = self.nodes[2].sendrawtransaction(conflicting["hex"]) + self.nodes[2].generate(9) + + # Reconnect node0 and node2 and check that conflicted_txid is effectively conflicted + connect_nodes(self.nodes[0], 2) + self.sync_blocks([self.nodes[0], self.nodes[2]]) + conflicted = self.nodes[0].gettransaction(conflicted_txid) + conflicting = self.nodes[0].gettransaction(conflicting_txid) + assert_equal(conflicted["confirmations"], -9) + assert_equal(conflicted["walletconflicts"][0], conflicting["txid"]) + + # Node0 wallet is shutdown + self.stop_node(0) + self.start_node(0) + + # The block chain re-orgs and the tx is included in a different block + self.nodes[1].generate(9) + self.nodes[1].sendrawtransaction(tx["hex"]) + self.nodes[1].generate(1) + self.nodes[1].sendrawtransaction(conflicted["hex"]) + self.nodes[1].generate(1) + + # Node0 wallet file is loaded on longest sync'ed node1 + self.stop_node(1) + self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, 'wallet.bak')) + shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, 'regtest', 'wallet.dat')) + self.start_node(1) + tx_after_reorg = self.nodes[1].gettransaction(txid) + # Check that normal confirmed tx is confirmed again but with different blockhash + assert_equal(tx_after_reorg["confirmations"], 2) + assert(tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"]) + conflicted_after_reorg = self.nodes[1].gettransaction(conflicted_txid) + # Check that conflicted tx is confirmed again with blockhash different than previously conflicting tx + assert_equal(conflicted_after_reorg["confirmations"], 1) + assert(conflicting["blockhash"] != conflicted_after_reorg["blockhash"]) + +if __name__ == '__main__': + ReorgsRestoreTest().main() diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index 1d6122a13d..bd947d194c 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -15,8 +15,8 @@ import re FOLDER_GREP = 'src' FOLDER_TEST = 'src/test/' -REGEX_ARG = '(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"' -REGEX_DOC = 'AddArg\("(-[^"=]+?)(?:=|")' +REGEX_ARG = r'(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"' +REGEX_DOC = r'AddArg\("(-[^"=]+?)(?:=|")' CMD_ROOT_DIR = '$(git rev-parse --show-toplevel)/{}'.format(FOLDER_GREP) CMD_GREP_ARGS = r"git grep --perl-regexp '{}' -- {} ':(exclude){}'".format(REGEX_ARG, CMD_ROOT_DIR, FOLDER_TEST) CMD_GREP_WALLET_ARGS = r"git grep --function-context 'void WalletInit::AddWalletOptions' -- {} | grep AddArg".format(CMD_ROOT_DIR) diff --git a/test/lint/check-rpc-mappings.py b/test/lint/check-rpc-mappings.py index 137cc82b5d..a33ab17f3f 100755 --- a/test/lint/check-rpc-mappings.py +++ b/test/lint/check-rpc-mappings.py @@ -48,13 +48,13 @@ def process_commands(fname): for line in f: line = line.rstrip() if not in_rpcs: - if re.match("static const CRPCCommand .*\[\] =", line): + if re.match(r"static const CRPCCommand .*\[\] =", line): in_rpcs = True else: if line.startswith('};'): in_rpcs = False elif '{' in line and '"' in line: - m = re.search('{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line) + m = re.search(r'{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line) assert m, 'No match to table expression: %s' % line name = parse_string(m.group(2)) args_str = m.group(4).strip() @@ -80,7 +80,7 @@ def process_mapping(fname): if line.startswith('};'): in_rpcs = False elif '{' in line and '"' in line: - m = re.search('{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line) + m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line) assert m, 'No match to table expression: %s' % line name = parse_string(m.group(1)) idx = int(m.group(2)) diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 47ad896589..9f34d0f4dd 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -55,7 +55,7 @@ def normalize(s): assert type(s) is str s = s.replace("\n", " ") s = s.replace("\t", " ") - s = re.sub("/\*.*?\*/", " ", s) + s = re.sub(r"/\*.*?\*/", " ", s) s = re.sub(" {2,}", " ", s) return s.strip() diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index 4b9e2615b6..d27e45a23f 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -11,6 +11,9 @@ export LC_ALL=C IGNORE_REGEXP="/(leveldb|secp256k1|univalue)/" +# cd to root folder of git repo for git ls-files to work properly +cd "$(dirname $0)/../.." || exit 1 + filter_suffix() { git ls-files | grep -E "^src/.*\.${1}"'$' | grep -Ev "${IGNORE_REGEXP}" } diff --git a/test/lint/lint-python-mutable-default-parameters.sh b/test/lint/lint-python-mutable-default-parameters.sh new file mode 100755 index 0000000000..1f9f035d30 --- /dev/null +++ b/test/lint/lint-python-mutable-default-parameters.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Detect when a mutable list or dict is used as a default parameter value in a Python function. + +export LC_ALL=C +EXIT_CODE=0 +OUTPUT=$(git grep -E '^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)' -- "*.py") +if [[ ${OUTPUT} != "" ]]; then + echo "A mutable list or dict seems to be used as default parameter value:" + echo + echo "${OUTPUT}" + echo + cat << EXAMPLE +This is how mutable list and dict default parameter values behave: + +>>> def f(i, j=[], k={}): +... j.append(i) +... k[i] = True +... return j, k +... +>>> f(1) +([1], {1: True}) +>>> f(1) +([1, 1], {1: True}) +>>> f(2) +([1, 1, 2], {1: True, 2: True}) + +The intended behaviour was likely: + +>>> def f(i, j=None, k=None): +... if j is None: +... j = [] +... if k is None: +... k = {} +... j.append(i) +... k[i] = True +... return j, k +... +>>> f(1) +([1], {1: True}) +>>> f(1) +([1], {1: True}) +>>> f(2) +([2], {2: True}) +EXAMPLE + EXIT_CODE=1 +fi +exit ${EXIT_CODE} diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh index a76806003f..3c82ec19e3 100755 --- a/test/lint/lint-python.sh +++ b/test/lint/lint-python.sh @@ -73,7 +73,6 @@ enabled=( W291 # trailing whitespace W292 # no newline at end of file W293 # blank line contains whitespace - W504 # line break after binary operator W601 # .has_key() is deprecated, use "in" W602 # deprecated form of raising exception W603 # "<>" is deprecated, use "!=" diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh index 5d672698a7..e70b73e1cc 100755 --- a/test/lint/lint-spelling.sh +++ b/test/lint/lint-spelling.sh @@ -9,6 +9,11 @@ export LC_ALL=C +if ! command -v codespell > /dev/null; then + echo "Skipping spell check linting since codespell is not installed." + exit 0 +fi + IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/qt/locale/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/"); then echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}" diff --git a/test/util/data/txcreateoutpubkey1.json b/test/util/data/txcreateoutpubkey1.json index 32097b3ebe..42b519bb21 100644 --- a/test/util/data/txcreateoutpubkey1.json +++ b/test/util/data/txcreateoutpubkey1.json @@ -15,11 +15,7 @@ "scriptPubKey": { "asm": "02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397 OP_CHECKSIG", "hex": "2102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397ac", - "reqSigs": 1, - "type": "pubkey", - "addresses": [ - "1FoG2386FG2tAJS9acMuiDsKy67aGg9MKz" - ] + "type": "pubkey" } } ], |