From b18269e3d0a70341c56cba9071f91237611dabe9 Mon Sep 17 00:00:00 2001 From: Andy Oknen Date: Fri, 1 Apr 2022 12:19:09 +0300 Subject: [PATCH 01/99] Version change to 0.21.0 --- build_msvc/pocketcoin_config.h | 8 ++++---- src/checkpoints.cpp | 1 + src/pocketcoind.cpp | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build_msvc/pocketcoin_config.h b/build_msvc/pocketcoin_config.h index 898d86e87..57a880834 100644 --- a/build_msvc/pocketcoin_config.h +++ b/build_msvc/pocketcoin_config.h @@ -11,10 +11,10 @@ #define CLIENT_VERSION_MAJOR 0 /* Minor version */ -#define CLIENT_VERSION_MINOR 20 +#define CLIENT_VERSION_MINOR 21 /* Build revision */ -#define CLIENT_VERSION_REVISION 19 +#define CLIENT_VERSION_REVISION 0 /* Version Build */ #define CLIENT_VERSION_BUILD 0 @@ -346,7 +346,7 @@ #define PACKAGE_NAME "Pocketnet Core" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "Pocketnet Core 0.20.19" +#define PACKAGE_STRING "Pocketnet Core 0.21.0" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "pocketcoin" @@ -355,7 +355,7 @@ #define PACKAGE_URL "https://github.com/pocketnetteam" /* Define to the version of this package. */ -#define PACKAGE_VERSION "0.20.19" +#define PACKAGE_VERSION "0.21.0" /* Define to necessary symbol if this constant uses a non-standard name on your system. */ diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index 5ae0b0632..852cfe229 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -21,6 +21,7 @@ namespace Checkpoints { for (const MapCheckpoints::value_type& i : reverse_iterate(checkpoints)) { const uint256& hash = i.second; + LOCK(cs_main); CBlockIndex* pindex = LookupBlockIndex(hash); if (pindex) { return pindex; diff --git a/src/pocketcoind.cpp b/src/pocketcoind.cpp index 17f7d47a4..1e2ba276b 100644 --- a/src/pocketcoind.cpp +++ b/src/pocketcoind.cpp @@ -55,7 +55,7 @@ static bool AppInit(int argc, char* argv[]) // Process help and version before taking care about datadir if (HelpRequested(args) || args.IsArgSet("-version")) { - std::string strUsage = PACKAGE_NAME " version " + FormatFullVersion() + "\n"; + std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; if (args.IsArgSet("-version")) { strUsage += FormatParagraph(LicenseInfo()) + "\n"; From a17bad37b2e5b7701f2c97c258f4d55203b2f7cb Mon Sep 17 00:00:00 2001 From: Andy Oknen Date: Fri, 1 Apr 2022 16:54:10 +0300 Subject: [PATCH 02/99] Debug logging --- src/pos.cpp | 29 +---------------------------- src/validation.cpp | 2 +- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/src/pos.cpp b/src/pos.cpp index f097aae3a..7751ef917 100644 --- a/src/pos.cpp +++ b/src/pos.cpp @@ -179,6 +179,7 @@ bool CheckProofOfStake(CBlockIndex *pindexPrev, CTransactionRef const &tx, unsig // Kernel (input 0) must match the stake hash target per coin age (nBits) const CTxIn &txin = tx->vin[0]; + LogPrintf("DEBUG: txin.prevout.hash = %s\n", txin.prevout.hash.ToString()); auto txPrev = PocketDb::TransRepoInst.Get(txin.prevout.hash.ToString(), false, false, true); if (!txPrev) { @@ -329,40 +330,12 @@ bool CheckStakeKernelHash(CBlockIndex *pindexPrev, unsigned int nBits, CBlockInd hashProofOfStakeSource = ss; hashProofOfStake = UintToArith256(Hash(ss)); - if (fPrintProofOfStake) - { - LogPrint(BCLog::WALLET, "CheckStakeKernelHash() : using modifier 0x%016x at height=%d timestamp=%s for block from timestamp=%s\n", - nStakeModifier, nStakeModifierHeight, - FormatISO8601DateTime(nStakeModifierTime), - FormatISO8601DateTime(nTimeBlockFrom)); - - LogPrint(BCLog::WALLET, "CheckStakeKernelHash() : check modifier=0x%016x nTimeBlockFrom=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s bnTarget=%s nBits=%08x nValueIn=%d bnWeight=%s\n", - nStakeModifier, - nTimeBlockFrom, *txPrev.GetTime(), prevout.n, nTimeTx, - hashProofOfStake.ToString(), bnTarget.ToString(), nBits, nValueIn, bnWeight.ToString()); - } - // Now check if proof-of-stake hash meets target protocol if (hashProofOfStake > bnTarget) { return false; } - if (!fPrintProofOfStake) - { - LogPrint(BCLog::WALLET, - "CheckStakeKernelHash() : using modifier 0x%016x at height=%d timestamp=%s for block from timestamp=%s\n", - nStakeModifier, nStakeModifierHeight, - FormatISO8601DateTime(nStakeModifierTime), - FormatISO8601DateTime(nTimeBlockFrom)); - - LogPrint(BCLog::WALLET, - "CheckStakeKernelHash() : pass modifier=0x%016x nTimeBlockFrom=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n", - nStakeModifier, - nTimeBlockFrom, txPrev.GetTime(), prevout.n, nTimeTx, - hashProofOfStake.ToString()); - } - return true; } diff --git a/src/validation.cpp b/src/validation.cpp index 893edf538..8ed1ddfb2 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4298,7 +4298,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector& } 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); + LogPrint(BCLog::SYNC, "Synchronizing blockheaders, height: %d (~%.2f%%)\n", (*ppindex)->nHeight, 100.0/((*ppindex)->nHeight+(GetAdjustedTime() - (*ppindex)->GetBlockTime()) / Params().GetConsensus().nPowTargetSpacing) * (*ppindex)->nHeight); } } return true; From 6a89e36c5c7657d523a9ce7f835440791667d81d Mon Sep 17 00:00:00 2001 From: Andy Oknen Date: Fri, 1 Apr 2022 17:04:30 +0300 Subject: [PATCH 03/99] Rollback logging debug --- src/pos.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pos.cpp b/src/pos.cpp index 7751ef917..6e386012d 100644 --- a/src/pos.cpp +++ b/src/pos.cpp @@ -179,7 +179,6 @@ bool CheckProofOfStake(CBlockIndex *pindexPrev, CTransactionRef const &tx, unsig // Kernel (input 0) must match the stake hash target per coin age (nBits) const CTxIn &txin = tx->vin[0]; - LogPrintf("DEBUG: txin.prevout.hash = %s\n", txin.prevout.hash.ToString()); auto txPrev = PocketDb::TransRepoInst.Get(txin.prevout.hash.ToString(), false, false, true); if (!txPrev) { From 5873c8a8ac582e26d99db20dee3c8f794a4abd26 Mon Sep 17 00:00:00 2001 From: lostystyg Date: Fri, 1 Apr 2022 17:30:07 +0300 Subject: [PATCH 04/99] Fixed leveldb forcing compile all with -Werror and -Wthread-safety using clang --- src/leveldb/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leveldb/CMakeLists.txt b/src/leveldb/CMakeLists.txt index 2f75780ae..26cbaa9bc 100644 --- a/src/leveldb/CMakeLists.txt +++ b/src/leveldb/CMakeLists.txt @@ -256,7 +256,7 @@ endif(BUILD_SHARED_LIBS) if(HAVE_CLANG_THREAD_SAFETY) target_compile_options(leveldb - PUBLIC + PRIVATE -Werror -Wthread-safety) endif(HAVE_CLANG_THREAD_SAFETY) From 7c47c41f6835d0f2d579570c4f498e2cff468ab1 Mon Sep 17 00:00:00 2001 From: Andy Oknen Date: Fri, 1 Apr 2022 17:36:20 +0300 Subject: [PATCH 05/99] Squashed commit of the following: commit 9cb7dd4355cc694fc63c063da50293f79c68a940 Merge: dcd1ed1a b660f9f3 Author: Andy Oknen Date: Fri Apr 1 17:35:36 2022 +0300 Merge pull request #242 from tawmaz/bitcoin_unittests Fix unittests and clean transactions in sqlite database commit b660f9f3986f3c1783f75fc890b700e317458953 Author: tawmaz Date: Mon Mar 21 16:21:23 2022 -0700 Remove in-progress SQL database transaction validation functions - Remove GetTransactionHashes, verifydb, and CleanExpiredTransactions until those functions can be fully developed. commit 5fefc67cd7654c27ae101fd2afb8506993dd5ead Author: tawmaz Date: Thu Mar 10 22:17:40 2022 -0800 Fix nested sqlite transaction in CleanExpiredTransactions commit 0b3a87cca6723038cc8aab8d886cc512efb08f6e Author: tawmaz Date: Mon Feb 28 16:00:02 2022 -0800 New user account creation in pocketnet_social_tests and code formating commit 0221ddc28440a93cefd64b3de1deb518c2ad76b4 Author: tawmaz Date: Thu Feb 10 17:21:23 2022 -0800 Fix orphan transactions in SQLite database and add unitttest - Clean expired transactions from SQLite Payload and Transactions db - Fix error in SQLite query for orphaned transactions - Enable mempool_tests - Add verifydb function to validate sqlite transactions are present on the blockchain - Additional debugging and check mempool for existance of transactions - Re-arrange locks in LoadMemPool to be consistent with lock usage through rest of code - Refactor CleanSQLite function and print error if transaction to delete is present in blocks or mempool - Verify DB deletes orphan transactions and add removal reason to CleanSQLite logging - Use GetTransaction call to verify TX is valid - Fix segmentation fault caused by accessing mempool TX which has been removed - Enable unittests txvalidation_tests, txvalidationcache_tests, and validation_block_tests - Remove unused parameter from ProcessNewBlock - Add test pocketnet_block_tests - pocketnet_block_tests: test InvalidateBlock and adding block to chain - Add transactions to block from mempool - Fix execution of unittests and disable Segwit on regtest for now - Fix pocketnet_block_tests error by setting up mocktime for tests - Intermittent test failures were occuring because blocks were generated with same timestamp and same hash value due to one second time stamp granularity. Now timestamp is incremented for each block. - Enable wallet tests. --- src/Makefile.test.include | 28 ++-- src/bench/checkblock.cpp | 2 +- src/chainparams.cpp | 9 +- src/init.cpp | 4 +- src/pocketdb/SQLiteDatabase.cpp | 14 +- src/pocketdb/consensus/social/Post.hpp | 3 +- src/pocketdb/repositories/ChainRepository.cpp | 1 + src/pocketdb/web/PocketTransactionRpc.cpp | 2 +- src/test/blockencodings_tests.cpp | 2 + src/test/pocketnet_block_tests.cpp | 152 ++++++++++++++++++ src/test/pocketnet_social_tests.cpp | 109 +++++++++++++ src/test/transaction_tests.cpp | 12 +- src/test/txvalidationcache_tests.cpp | 11 +- src/test/util/mining.cpp | 1 + src/test/util/setup_common.cpp | 69 +++++--- src/test/util_tests.cpp | 4 + src/test/validation_block_tests.cpp | 12 +- src/test/validation_tests.cpp | 3 +- src/txmempool.cpp | 71 ++++---- src/txmempool.h | 2 +- src/validation.cpp | 19 ++- src/validation.h | 2 +- src/wallet/test/psbt_wallet_tests.cpp | 2 + src/wallet/test/wallet_tests.cpp | 2 + 24 files changed, 439 insertions(+), 97 deletions(-) create mode 100644 src/test/pocketnet_block_tests.cpp create mode 100644 src/test/pocketnet_social_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ffa87297b..eaa5f0191 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -214,7 +214,7 @@ FUZZ_SUITE_LD_COMMON = \ # test/hash_tests.cpp # test/key_io_tests.cpp # test/key_tests.cpp -# test/mempool_tests.cpp +# test/main_tests.cpp # test/merkle_tests.cpp # test/merkleblock_tests.cpp # test/miner_tests.cpp @@ -223,15 +223,14 @@ FUZZ_SUITE_LD_COMMON = \ # test/rpc_tests.cpp # test/sighash_tests.cpp # test/txindex_tests.cpp -# test/txvalidation_tests.cpp # test/txrequest_tests.cpp \ -# test/txvalidationcache_tests.cpp \ # test/validation_block_tests.cpp \ # test/validation_chainstate_tests.cpp \ # test/validation_chainstatemanager_tests.cpp \ # test/validation_flush_tests.cpp \ # test/validationinterface_tests.cpp \ # test/versionbits_tests.cpp +# test/validation_block_tests.cpp # Intermittant failure in validation_block_tests POCKETCOIN_TESTS =\ @@ -264,9 +263,12 @@ POCKETCOIN_TESTS =\ test/logging_tests.cpp \ test/dbwrapper_tests.cpp \ test/validation_tests.cpp \ + test/mempool_tests.cpp \ test/multisig_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ + test/pocketnet_block_tests.cpp \ + test/pocketnet_social_tests.cpp \ test/pmt_tests.cpp \ test/policy_fee_tests.cpp \ test/policyestimator_tests.cpp \ @@ -291,19 +293,21 @@ POCKETCOIN_TESTS =\ test/timedata_tests.cpp \ test/torcontrol_tests.cpp \ test/transaction_tests.cpp \ + test/txvalidation_tests.cpp \ + test/txvalidationcache_tests.cpp \ test/uint256_tests.cpp \ test/util_tests.cpp if ENABLE_WALLET -# POCKETCOIN_TESTS += \ -# wallet/test/db_tests.cpp \ -# wallet/test/psbt_wallet_tests.cpp \ -# wallet/test/wallet_tests.cpp \ -# wallet/test/wallet_crypto_tests.cpp \ -# wallet/test/coinselector_tests.cpp \ -# wallet/test/init_tests.cpp \ -# wallet/test/ismine_tests.cpp \ -# wallet/test/scriptpubkeyman_tests.cpp +POCKETCOIN_TESTS += \ + wallet/test/db_tests.cpp \ + wallet/test/psbt_wallet_tests.cpp \ + wallet/test/wallet_tests.cpp \ + wallet/test/wallet_crypto_tests.cpp \ + wallet/test/coinselector_tests.cpp \ + wallet/test/init_tests.cpp \ + wallet/test/ismine_tests.cpp \ + wallet/test/scriptpubkeyman_tests.cpp POCKETCOIN_TEST_SUITE += \ wallet/test/wallet_test_fixture.cpp \ diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 8bf9bb0ef..9cc309422 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -16,7 +16,7 @@ static void DeserializeBlockTest(benchmark::Bench& bench) { - CDataStream stream(benchmark::data::block1533073, SER_NETWORK, PROTOCOL_VERSION); + CDataStream stream(benchmark::data::block1533073, SER_NETWORK, PROTOCOL_VERSION); char a = '\0'; stream.write(&a, 1); // Prevent compaction diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a42f20ac5..fba6970dc 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -415,12 +415,12 @@ class CRegTestParams : public CChainParams consensus.signet_challenge.clear(); // TODO (losty): may be change? consensus.nSubsidyHalvingInterval = 150; consensus.BIP16Exception = uint256(); - consensus.BIP34Height = 100000000; // BIP34 has not activated on regtest (far in the future so block v1 are not rejected in tests) + consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests) consensus.BIP34Hash = uint256(); consensus.BIP65Height = 1351; // BIP65 activated on regtest (Used in functional tests) consensus.BIP66Height = 1251; // BIP66 activated on regtest (Used in functional tests) consensus.CSVHeight = 432; // CSV activated on regtest (Used in rpc activation tests) - consensus.SegwitHeight = 0; // SEGWIT is always activated on regtest unless overridden + consensus.SegwitHeight = std::numeric_limits::max(); // SEGWIT is disabled on regtest consensus.MinBIP9WarningHeight = 0; consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 3.5 * 24 * 60 * 60; // two weeks @@ -431,11 +431,16 @@ class CRegTestParams : public CChainParams consensus.nMinerConfirmationWindow = 100; consensus.nPosFirstBlock = 200; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay + consensus.nMinerConfirmationWindow = 100; + consensus.nPosFirstBlock = 1020; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay + consensus.nModifierInterval = 10 * 60; // time to elapse before new modifier is computed + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; diff --git a/src/init.cpp b/src/init.cpp index 0d53476b0..12b993a78 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1619,9 +1619,9 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA RandAddPeriodic(); }, std::chrono::minutes{1}); - // ********************************************************* Step 4b: Start PocketDB + // ********************************************************* Step 4b: Start PocketDB uiInterface.InitMessage(_("Loading Pocket DB...").translated); - + PocketDb::InitSQLite(GetDataDir() / "pocketdb"); PocketDb::InitSQLiteCheckpoints(GetDataDir() / "checkpoints"); diff --git a/src/pocketdb/SQLiteDatabase.cpp b/src/pocketdb/SQLiteDatabase.cpp index 3cf866b6e..43644fae6 100644 --- a/src/pocketdb/SQLiteDatabase.cpp +++ b/src/pocketdb/SQLiteDatabase.cpp @@ -73,9 +73,16 @@ namespace PocketDb void InitSQLiteCheckpoints(fs::path path) { - // Intialize Checkpoints DB auto checkpointDbName = Params().NetworkIDString(); - if (!fs::exists((path / (checkpointDbName + ".sqlite3")).string())) + int i = 0; + + // Look for checkpoints directory in current folder or up to two directories up. + while (i < 3 && !fs::exists((path / "checkpoints" / (checkpointDbName + ".sqlite3")).string())) + { + path = path / ".."; + i++; + } + if (!fs::exists((path / "checkpoints" / (checkpointDbName + ".sqlite3")).string())) { LogPrintf("Checkpoint DB %s not found!\nDownload actual DB file from %s and place to %s directory.\n", (path / (checkpointDbName + ".sqlite3")).string(), @@ -85,8 +92,7 @@ namespace PocketDb throw std::runtime_error(_("Unable to start server. Checkpoints DB not found. See debug log for details.").translated); } - SQLiteDbCheckpointInst.Init(path.string(), checkpointDbName); - + SQLiteDbCheckpointInst.Init((path / "checkpoints").string(), checkpointDbName); } SQLiteDatabase::SQLiteDatabase(bool readOnly) : isReadOnlyConnect(readOnly) diff --git a/src/pocketdb/consensus/social/Post.hpp b/src/pocketdb/consensus/social/Post.hpp index 0459ee43e..ae39afeae 100644 --- a/src/pocketdb/consensus/social/Post.hpp +++ b/src/pocketdb/consensus/social/Post.hpp @@ -60,7 +60,8 @@ namespace PocketConsensus return {false, baseCheckCode}; // Check required fields - if (IsEmpty(ptx->GetAddress())) return {false, SocialConsensusResult_Failed}; + if (IsEmpty(ptx->GetAddress())) + return {false, SocialConsensusResult_Failed}; return Success; } diff --git a/src/pocketdb/repositories/ChainRepository.cpp b/src/pocketdb/repositories/ChainRepository.cpp index ff7356bf4..1a0a3dd29 100644 --- a/src/pocketdb/repositories/ChainRepository.cpp +++ b/src/pocketdb/repositories/ChainRepository.cpp @@ -94,6 +94,7 @@ namespace PocketDb } } + tuple ChainRepository::ExistsBlock(const string& blockHash, int height) { bool exists = false; diff --git a/src/pocketdb/web/PocketTransactionRpc.cpp b/src/pocketdb/web/PocketTransactionRpc.cpp index e86839bbf..c02d10a82 100644 --- a/src/pocketdb/web/PocketTransactionRpc.cpp +++ b/src/pocketdb/web/PocketTransactionRpc.cpp @@ -335,7 +335,7 @@ namespace PocketWeb::PocketWebRpc "\nCreate new pocketnet address.\n" ); - if (Params().NetworkIDString() != CBaseChainParams::TESTNET) + if (Params().NetworkIDString() != CBaseChainParams::TESTNET && Params().NetworkIDString() != CBaseChainParams::REGTEST) throw runtime_error("Only for testnet\n"); std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 2051f0849..60e02ee09 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -373,6 +373,7 @@ BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationMaxTest) { BOOST_CHECK_EQUAL(req0.indexes[0], req1.indexes[0]); } +#ifdef DISABLE_TEST BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationOverflowTest) { // Any set of index deltas that starts with N values that sum to (0x10000 - N) // causes the edge-case overflow that was originally not checked for. Such @@ -404,4 +405,5 @@ BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationOverflowTest) { } } +#endif /* DISABLE_TEST */ BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/pocketnet_block_tests.cpp b/src/test/pocketnet_block_tests.cpp new file mode 100644 index 000000000..38261e428 --- /dev/null +++ b/src/test/pocketnet_block_tests.cpp @@ -0,0 +1,152 @@ +// Copyright (c) 2022 The Pocketcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include