diff --git a/README.md b/README.md index 8a6288df..d5ba81d7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # The PHPCoin (PHP) cryptocurrency node. +For detailed information on installation, usage, and development, please see the [PHPCoin Documentation](./docs/). + +-- + Name: PHPCoin Symbol: PHP @@ -75,14 +79,6 @@ https://m1.phpcoin.net/ https://m2.phpcoin.net/ - - - - ## Install -Check [instructions](https://github.com/phpcoinn/node/wiki/Node-installation) - - - - +Please see the [Install Guide](./docs/getting-started/) for detailed instructions. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..b138c661 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,25 @@ +Welcome to the official documentation for PHPCoin. This documentation provides a comprehensive guide to installing, using, and developing with PHPCoin. + +* **[Introduction](./introduction/)** - A high-level overview of PHPCoin, its features, and its purpose. + * **[What is PHPCoin?](./introduction/what-is-phpcoin.md)** - A detailed explanation of the PHPCoin project. + * **[Features](./introduction/features.md)** - A list of the key features of PHPCoin. +* **[White Paper](./white-paper/README.md)** - The official PHPCoin whitepaper. +* **[Getting Started](./getting-started/)** - Step-by-step guides for installing and running a PHPCoin node. + * **[Installation](./getting-started/installation.md)** - A guide on how to install a PHPCoin node. + * **[Running a Node](./getting-started/running-a-node.md)** - Instructions on how to run a PHPCoin node. +* **[Wallet](./wallet/)** - Instructions on how to use the command-line and web-based wallets. + * **[Using the Wallet](./wallet/using-the-wallet.md)** - A detailed guide on how to use the PHPCoin wallet. +* **[Mining](./mining/)** - Information on how to mine PHPCoin using different methods. + * **[How to Mine](./mining/how-to-mine.md)** - A guide on how to get started with mining. + * **[Miner Workflow](./mining/miner-workflow.md)** - A detailed, technical step-by-step breakdown of the different mining processes. +* **[ePoW (Elapsed Proof of Work)](./epow/)** - A detailed explanation of PHPCoin's unique consensus algorithm. +* **[Staking](./staking/)** - A guide to staking your PHPCoin holdings to earn rewards. + * **[How to Stake](./staking/how-to-stake.md)** - A guide on how to get started with staking. +* **[Masternodes](./masternodes/)** - Instructions for setting up and running a masternode. + * **[Setting up a Masternode](./masternodes/setting-up-a-masternode.md)** - A guide on how to set up a masternode. +* **[Smart Contracts](./smart-contracts/)** - A comprehensive guide to developing smart contracts on the PHPCoin platform. + * **[Smart Contract Builder's Guide](./smart-contracts/builders-guide.md)** - A detailed guide on how to build smart contracts. +* **[dApps](./dapps/)** - Information on how to build decentralized applications (Dapps) on PHPCoin. + * **[Developing dApps](./dapps/developing-dapps.md)** - A guide on how to develop dApps. +* **[API](./api/)** - The complete API reference for a PHPCoin node. + * **[API Reference](./api/api-reference.md)** - A detailed reference for the PHPCoin API. diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 00000000..53d5cffd --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,10 @@ +[PHPCoin Docs](../) > API + + +--- + +# API Documentation + +This section contains the API reference for PHPCoin. + +* [API Reference](./api-reference.md) diff --git a/docs/api/api-reference.md b/docs/api/api-reference.md new file mode 100644 index 00000000..f1c4f2af --- /dev/null +++ b/docs/api/api-reference.md @@ -0,0 +1,297 @@ +[PHPCoin Docs](../) > [API](./) > API Reference + + +--- + +# API Reference + +The PHPcoin API provides a set of endpoints for interacting with the blockchain. The API is accessible via HTTP and returns data in JSON format. + +## Accounts + +### getAddress + +Converts a public key to a PHPcoin address. + +* **URL:** `/api.php?q=getAddress` +* **Method:** `GET` +* **Parameters:** + * `public_key` (string, required): The public key to convert. +* **Example:** + ``` + /api.php?q=getAddress&public_key=... + ``` + +### getBalance + +Returns the balance of a specific address. + +* **URL:** `/api.php?q=getBalance` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address to check. +* **Example:** + ``` + /api.php?q=getBalance&address=... + ``` + +### getPendingBalance + +Returns the pending balance of a specific address, which includes unconfirmed transactions. + +* **URL:** `/api.php?q=getPendingBalance` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address to check. +* **Example:** + ``` + /api.php?q=getPendingBalance&address=... + ``` + +### getPublicKey + +Returns the public key of a specific address. + +* **URL:** `/api.php?q=getPublicKey` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address to check. +* **Example:** + ``` + /api.php?q=getPublicKey&address=... + ``` + +### generateAccount + +Generates a new PHPcoin account. + +* **URL:** `/api.php?q=generateAccount` +* **Method:** `GET` +* **Example:** + ``` + /api.php?q=generateAccount + ``` + +## Transactions + +### getTransactions + +Returns the latest transactions for a specific address. + +* **URL:** `/api.php?q=getTransactions` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address to check. + * `limit` (integer, optional): The maximum number of transactions to return. + * `offset` (integer, optional): The offset to start from. +* **Example:** + ``` + /api.php?q=getTransactions&address=...&limit=10 + ``` + +### getTransaction + +Returns a specific transaction by its ID. + +* **URL:** `/api.php?q=getTransaction` +* **Method:** `GET` +* **Parameters:** + * `transaction` (string, required): The ID of the transaction. +* **Example:** + ``` + /api.php?q=getTransaction&transaction=... + ``` + +### send + +Sends a transaction to the network. + +* **URL:** `/api.php?q=send` +* **Method:** `POST` +* **Parameters:** + * `val` (float, required): The amount to send. + * `dst` (string, required): The destination address. + * `public_key` (string, required): The sender's public key. + * `signature` (string, required): The transaction signature. + * `date` (integer, required): The transaction date (Unix timestamp). + * `message` (string, optional): A message to include with the transaction. +* **Example:** + ```json + { + "val": 10.0, + "dst": "...", + "public_key": "...", + "signature": "...", + "date": 1678886400, + "message": "Hello, world!" + } + ``` + +## Blocks + +### currentBlock + +Returns the current block. + +* **URL:** `/api.php?q=currentBlock` +* **Method:** `GET` +* **Example:** + ``` + /api.php?q=currentBlock + ``` + +### getBlock + +Returns a specific block by its height. + +* **URL:** `/api.php?q=getBlock` +* **Method:** `GET` +* **Parameters:** + * `height` (integer, required): The height of the block. +* **Example:** + ``` + /api.php?q=getBlock&height=12345 + ``` + +### getBlockTransactions + +Returns the transactions of a specific block. + +* **URL:** `/api.php?q=getBlockTransactions` +* **Method:** `GET` +* **Parameters:** + * `height` (integer, required): The height of the block. +* **Example:** + ``` + /api.php?q=getBlockTransactions&height=12345 + ``` + +## Node + +### version + +Returns the version of the node. + +* **URL:** `/api.php?q=version` +* **Method:** `GET` +* **Example:** + ``` + /api.php?q=version + ``` + +### mempoolSize + +Returns the number of transactions in the mempool. + +* **URL:** `/api.php?q=mempoolSize` +* **Method:** `GET` +* **Example:** + ``` + /api.php?q=mempoolSize + ``` + +### nodeInfo + +Returns information about the node. + +* **URL:** `/api.php?q=nodeInfo` +* **Method:** `GET` +* **Example:** + ``` + /api.php?q=nodeInfo + ``` + +### getPeers + +Returns a list of the node's peers. + +* **URL:** `/api.php?q=getPeers` +* **Method:** `GET` +* **Example:** + ``` + /api.php?q=getPeers + ``` + +## Masternodes + +### getMasternodes + +Returns a list of all masternodes. + +* **URL:** `/api.php?q=getMasternodes` +* **Method:** `GET` +* **Example:** + ``` + /api.php?q=getMasternodes + ``` + +### getMasternode + +Returns a specific masternode by its address. + +* **URL:** `/api.php?q=getMasternode` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address of the masternode. +* **Example:** + ``` + /api.php?q=getMasternode&address=... + ``` + +## Smart Contracts + +### getSmartContract + +Returns a specific smart contract by its address. + +* **URL:** `/api.php?q=getSmartContract` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address of the smart contract. +* **Example:** + ``` + /api.php?q=getSmartContract&address=... + ``` + +### getSmartContractProperty + +Reads a property of a smart contract. + +* **URL:** `/api.php?q=getSmartContractProperty` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address of the smart contract. + * `property` (string, required): The name of the property to read. + * `key` (string, optional): The key of the property, if it's a map. +* **Example:** + ``` + /api.php?q=getSmartContractProperty&address=...&property=myProperty&key=myKey + ``` + +### getSmartContractInterface + +Returns the interface of a smart contract. + +* **URL:** `/api.php?q=getSmartContractInterface` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address of the smart contract. +* **Example:** + ``` + /api.php?q=getSmartContractInterface&address=... + ``` + +### getSmartContractView + +Executes a view method of a smart contract. + +* **URL:** `/api.php?q=getSmartContractView` +* **Method:** `GET` +* **Parameters:** + * `address` (string, required): The address of the smart contract. + * `method` (string, required): The name of the view method to execute. + * `params` (string, optional): The parameters for the method, as a base64-encoded JSON string. +* **Example:** + ``` + /api.php?q=getSmartContractView&address=...&method=myMethod¶ms=... + ``` diff --git a/docs/dapps/README.md b/docs/dapps/README.md new file mode 100644 index 00000000..7d1ae249 --- /dev/null +++ b/docs/dapps/README.md @@ -0,0 +1,10 @@ +[PHPCoin Docs](../) > dApps + + +--- + +# Dapps Documentation + +This section explains how to develop decentralized applications (Dapps) on the PHPCoin platform. + +* [Developing Dapps](./developing-dapps.md) diff --git a/docs/dapps/developing-dapps.md b/docs/dapps/developing-dapps.md new file mode 100644 index 00000000..4731b5dc --- /dev/null +++ b/docs/dapps/developing-dapps.md @@ -0,0 +1,26 @@ +[PHPCoin Docs](../) > [dApps](./) > Developing Dapps + + +--- + +# Developing Dapps + +A Decentralized Application (Dapp) on PHPcoin is a web application that interacts with the PHPcoin blockchain. Dapps can be built using standard web technologies like PHP, HTML, and JavaScript. + +## Dapp Structure + +A Dapp is typically structured as a web page that is served by a web server. The Dapp's frontend is built with HTML and JavaScript, and it interacts with a PHPcoin node's API to get information from the blockchain and to send transactions. + +The backend of a Dapp can be written in any language, but PHP is a natural choice for PHPcoin Dapps. The backend can be used to perform more complex operations, such as interacting with a database or calling external APIs. + +## Interacting with the Blockchain + +Dapps interact with the PHPcoin blockchain by making calls to a node's API. The API provides a set of endpoints for getting information about the blockchain, sending transactions, and interacting with smart contracts. + +You can use any HTTP client to make calls to the API. In PHP, you can use the `file_get_contents()` function or the cURL library. In JavaScript, you can use the `fetch()` API or a library like Axios. + +## Example Dapp + +The `dapps/demo` directory contains a simple example Dapp that visualizes the PHPcoin network topology. This Dapp is a good starting point for learning how to develop Dapps on PHPcoin. + +The Dapp's frontend is built with HTML and JavaScript, and it uses the `vivagraph.js` and `go.js` libraries to visualize the network graph. The Dapp's backend is written in PHP, and it uses the `file_get_contents()` function to call the node's API and get information about the network. diff --git a/docs/epow/README.md b/docs/epow/README.md new file mode 100644 index 00000000..b334a414 --- /dev/null +++ b/docs/epow/README.md @@ -0,0 +1,9 @@ +[PHPCoin Docs](../) > ePoW + +--- + +# ePoW (Elapsed Proof of Work) + +This section provides a detailed explanation of PHPCoin's unique consensus algorithm, ePoW (Elapsed Proof of Work). + +* [Technical Explanation](./technical-explanation.md) diff --git a/docs/epow/technical-explanation.md b/docs/epow/technical-explanation.md new file mode 100644 index 00000000..e168a7fb --- /dev/null +++ b/docs/epow/technical-explanation.md @@ -0,0 +1,234 @@ +[PHPCoin Docs](../) > [ePoW](./) + + +--- + +# ePoW (Elapsed Proof of Work) + +## Table of Contents +- [A Simple Explanation](#a-simple-explanation) +- [Technical Workflow](#technical-workflow) +- [Code Examples and Source Files](#code-examples-and-source-files) + +--- + +## A Simple Explanation + +ePoW, or Elapsed Proof of Work, is the consensus mechanism that keeps the phpcoin network secure and fair. If you're familiar with Bitcoin's Proof of Work (PoW), you'll find ePoW has some interesting differences. + +In a traditional PoW system, miners are given a single puzzle for each new block, and its difficulty is static while they work on it. + +The defining feature of ePoW is completely different: the puzzle gets easier to solve with every passing second. + +**Here's the core idea:** + +To use an analogy: +* **Traditional PoW** gives miners one single, very hard puzzle to solve. +* **phpcoin's ePoW** gives miners a new puzzle every second, with each new puzzle being a little bit easier than the last. + +This unique mechanism is driven by the "elapsed time" since the last block was found. As miners work, this elapsed time increases, and for each second that passes, the mining target becomes easier to hit. This means the longer a block goes unsolved, the easier it becomes to solve. + +This has two key effects: +* **Stabilizes Block Time:** It ensures that blocks are found, on average, every 60 seconds. If a block isn't found quickly, the constantly decreasing difficulty makes it more and more likely to be found as time goes on. +* **Fairness:** It reduces the advantage of large mining pools. The difficulty adjusts so rapidly that it creates a more level playing field for all miners on the network. + +In short, ePoW is a more responsive version of PoW that uses the "elapsed time" between blocks to regulate the network's block time and difficulty. This helps to maintain a fair and stable environment for all participants in the phpcoin network. + + + +--- + +## Technical Workflow + +The ePoW consensus mechanism is a process that involves miners and the node working together to create and validate new blocks. Here's a step-by-step breakdown of the technical workflow. + +### 1. Block Creation and Mining Information + +When a miner is ready to mine a new block, they first request mining information from a phpcoin node. The node provides the following critical data: + +* The `height` of the new block. +* The `difficulty` for the current block. +* The `date` (timestamp) of the previous block. + +### 2. The Role of "Elapsed Time" + +The core of ePoW is the concept of "elapsed time." This is the time, in seconds, that has passed since the previous block was mined. The miner calculates this value continuously as they are working on a new block. + +**Pseudo-code:** +``` +previous_block_date = node.get_latest_block().date +current_timestamp = get_current_time() +elapsed_time = current_timestamp - previous_block_date +``` + +The `elapsed_time` is crucial because it directly influences the mining `target`. + +### 3. Calculating the Mining Target + +In phpcoin, a **higher** `hit` value is better. To find a valid block, a miner's `hit` must be greater than the `target`. The `target` is dynamically calculated using the `difficulty` and the `elapsed` time. + +**Pseudo-code:** +``` +target = (difficulty * BLOCK_TIME) / elapsed_time +``` + +* `BLOCK_TIME`: A constant, which is 60 seconds in phpcoin. +* `difficulty`: The difficulty of the current block. +* `elapsed_time`: The time passed since the last block. + +This formula means that as `elapsed_time` increases, the `target` decreases, making it easier to find a valid block. Conversely, if a block is found quickly, `elapsed_time` is small, the `target` is high, and it's harder to find a valid block. + +### 4. Hashing and Finding a Valid Block + +Unlike traditional Proof of Work where miners search for a random `nonce`, in ePoW, the mining process is an iteration over the **`elapsed` time**. The `nonce` is calculated deterministically, not found. + +Here is the mining loop: + +1. For each second that passes, the miner increments the `elapsed_time` value. +2. A new **Argon2id hash** is calculated using the `previous_block_date` and the new `elapsed_time`. Argon2id is a memory-hard hashing algorithm, making it resistant to ASIC miners. +3. A deterministic **`nonce`** is then calculated using a SHA-256 hash of the miner's address, the `elapsed_time`, and the new Argon2 hash. There is no randomness here; for the same inputs, the output `nonce` is always the same. +4. The miner calculates the `hit` value. +5. The miner recalculates the `target`, which changes with each increment of `elapsed_time`. +6. If `hit > target`, a valid block has been found. The miner can then submit the block with the successful `elapsed_time`, the calculated `argon` hash, and the deterministic `nonce`. + +**Pseudo-code:** +``` +previous_block_date = node.get_latest_block().date + +while (true): + current_timestamp = get_current_time() + elapsed_time = current_timestamp - previous_block_date + + // Recalculate target for the current elapsed_time + target = (difficulty * BLOCK_TIME) / elapsed_time + + // Calculate hashes deterministically + argon_hash = argon2("previous_block_date-elapsed_time") + nonce = sha256("miner_address-previous_block_date-elapsed_time-argon_hash") + + hit = calculate_hit(miner_address, nonce, block_height, difficulty) + + if (hit > target): + // Block found! + submit_block(nonce, argon_hash, elapsed_time) + break + + // Wait for the next second to increment elapsed_time + wait(1_second) +``` + +### 5. Submitting a Block + +Once a miner finds a valid `nonce` and `argon` hash, they submit them along with the `elapsed` time and their address to the node. This is typically done via an API call to `mine.php`. + +### 6. Block Validation by the Node + +The node receives the submitted block data and performs a series of checks to validate it: + +1. **Verify Elapsed Time**: The node checks if the `elapsed` time is valid (i.e., greater than zero). +2. **Verify Argon Hash**: The node recalculates the Argon2 hash using the previous block's date and the submitted `elapsed` time. It then compares this to the `argon` hash submitted by the miner. +3. **Verify Nonce**: The node recalculates the `nonce` using the miner's address, the previous block's date, the `elapsed` time, and the submitted `argon` hash. It checks if this matches the `nonce` sent by the miner. +4. **Verify Hit and Target**: The node calculates the `hit` using the submitted data and recalculates the `target` using the `elapsed` time. It then verifies that `hit > target`. + +If all of these checks pass, the node considers the block valid, adds it to the blockchain, and propagates it to other peers on the network. + +--- + +## Code Examples and Source Files + +Here are some relevant code snippets from the phpcoin source code that illustrate the ePoW mechanism. + +### Calculating the Target + +This function from `include/class/Block.php` shows how the target is calculated based on the `elapsed` time and `difficulty`. + +```php +function calculateTarget($elapsed) { + global $_config; + if($elapsed == 0) { + return 0; + } + $target = gmp_div(gmp_mul($this->difficulty , BLOCK_TIME), $elapsed); + if($target == 0 && DEVELOPMENT) { + $target = 1; + } + if($target > 100 && DEVELOPMENT) { + $target = 100; + } + return $target; +} +``` +[Link to `include/class/Block.php`](../../include/class/Block.php) + +### Calculating the Nonce and Argon Hash + +These functions from `include/class/Block.php` are used to calculate and verify the `nonce` and `argon` hash. + +```php +function calculateNonce($prev_block_date, $elapsed, $chain_id = CHAIN_ID) { + $nonceBase = "{$chain_id}{$this->miner}-{$prev_block_date}-{$elapsed}-{$this->argon}"; + $calcNonce = hash("sha256", $nonceBase); + _log("calculateNonce nonceBase=$nonceBase argon={$this->argon} calcNonce=$calcNonce", 5); + return $calcNonce; +} + +function calculateArgonHash($prev_block_date, $elapsed) { + $base = "{$prev_block_date}-{$elapsed}"; + $options = self::hashingOptions($this->height); + if($this->height < UPDATE_3_ARGON_HARD) { + $options['salt']=substr($this->miner, 0, 16); + } + $argon = @password_hash( + $base, + HASHING_ALGO, + $options + ); + return $argon; +} +``` +[Link to `include/class/Block.php`](../../include/class/Block.php) + +### Block Submission + +When a miner submits a block, the `web/mine.php` file handles the request. This snippet shows how the submitted data is received and used to create a new `Block` object. + +```php +$nonce = san($_POST['nonce']); +$version = Block::versionCode($height); +$address = san($_POST['address']); +$elapsed = intval($_POST['elapsed']); +$difficulty = san($_POST['difficulty']); +$argon = $_POST['argon']; + +// ... + +$block = new Block($generator, $address, $height, $date, $nonce, $data, $difficulty, $version, $argon, $prev_block['id']); +``` +[Link to `web/mine.php`](../../web/mine.php) + +### Block Validation (Mine Check) + +The `mine()` function in `include/class/Block.php` is where the core validation logic for a new block resides. This snippet shows the checks for the `argon` hash, `nonce`, `hit`, and `target`. + +```php +public function mine(&$err=null) +{ + // ... + if(!$this->verifyArgon($prev_date, $elapsed)) { + throw new Exception("Invalid argon={$this->argon}"); + } + + $calcNonce = $this->calculateNonce($prev_date, $elapsed); + // ... (nonce comparison logic) + + $hit = $this->calculateHit(); + $target = $this->calculateTarget($elapsed); + $res = $this->checkHit($hit, $target, $this->height); + if(!$res && $this->height > UPDATE_3_ARGON_HARD) { + throw new Exception("invalid hit or target"); + } + + return true; +} +``` +[Link to `include/class/Block.php`](../../include/class/Block.php) diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md new file mode 100644 index 00000000..b1d9405f --- /dev/null +++ b/docs/getting-started/README.md @@ -0,0 +1,12 @@ +[PHPCoin Docs](../) > Getting Started + + +--- + +# Getting Started + +This section provides guides to help you get started with PHPCoin. + +* [Quick Installation](./quick-installation.md) +* [Manual Installation](./manual-installation.md) +* [Running a Node](./running-a-node.md) diff --git a/docs/getting-started/manual-installation.md b/docs/getting-started/manual-installation.md new file mode 100644 index 00000000..741bd920 --- /dev/null +++ b/docs/getting-started/manual-installation.md @@ -0,0 +1,208 @@ +[PHPCoin Docs](../) > [Getting Started](./) > Manual Installation + +--- + +# Manual Installation Guide + +This guide provides step-by-step instructions for manually installing a PHPCoin node on a Debian-based system (e.g., Ubuntu). This process is intended for advanced users who require more control over the installation. + +## Prerequisites + +* A server running a Debian-based Linux distribution (e.g., Ubuntu 20.04, 22.04). +* Root or `sudo` privileges. +* Familiarity with the Linux command line. + +--- + +## Step 1: Update System and Install Dependencies + +First, update your system's package list and install the required software packages, including Nginx, PHP, and MariaDB. + +```bash +# Update package list +sudo apt update + +# Install dependencies +sudo apt install -y curl wget git sed net-tools unzip bc nginx php-fpm php-mysql php-gmp php-bcmath php-curl php-mbstring mariadb-server +``` + +--- + +## Step 2: Set Up the Database + +Next, create a new MariaDB database and user for the PHPCoin node. + +```bash +# Start the MariaDB service +sudo service mariadb start + +# Choose a network (mainnet or testnet) +export NETWORK="mainnet" # Or "testnet" + +# Set database credentials +export DB_NAME="phpcoin${NETWORK}" +export DB_USER="phpcoin" +export DB_PASS="phpcoin" # It is recommended to use a more secure password + +# Create the database and user +sudo mysql -e "CREATE DATABASE ${DB_NAME};" +sudo mysql -e "CREATE USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';" +sudo mysql -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';" +``` + +--- + +## Step 3: Download the PHPCoin Node + +Clone the PHPCoin source code from the official Git repository into the `/var/www` directory. + +```bash +# Set the installation directory +export NODE_DIR="/var/www/phpcoin-${NETWORK}" + +# Create the directory and clone the source code +sudo mkdir -p ${NODE_DIR} +cd ${NODE_DIR} + +if [ "${NETWORK}" = "mainnet" ]; then + sudo git clone https://git.phpcoin.net/node . +else + sudo git clone https://git.phpcoin.net/node --branch test . +fi +``` + +--- + +## Step 4: Configure the Web Server (Nginx) + +Configure Nginx to serve the PHPCoin web interface. + +```bash +# Get your server's public IP address +export IP=$(curl -s http://whatismyip.akamai.com/) + +# Set the port and hostname +if [ "${NETWORK}" = "mainnet" ]; then + export PORT="80" + export HOSTNAME="http://${IP}" +else + export PORT="81" + export HOSTNAME="http://${IP}:${PORT}" +fi + +# Create the Nginx configuration file +sudo tee /etc/nginx/sites-available/phpcoin-${NETWORK} > /dev/null < /dev/null 2>&1 +sleep 5 + +# Set the hostname in the database +sudo mysql ${DB_NAME} -e "UPDATE config SET val='${HOSTNAME}' WHERE cfg='hostname';" + +# Clear the temporary directory +sudo rm -rf ${NODE_DIR}/tmp/* + +# Restart Nginx +sudo service nginx start + +echo "Installation complete!" +echo "You can now access your node at: ${HOSTNAME}" + + + +--- + +## Troubleshooting + +If you encounter any issues during the installation, here are some common problems and their solutions: + +* **Port Conflict:** If another service is using port 80 (for mainnet) or 81 (for testnet), the Nginx service may fail to start. You can check for port conflicts using the `sudo netstat -tulpn | grep LISTEN` command. +* **Database Connection Issues:** Verify that the MariaDB service is running and that the database credentials in `config/config.inc.php` are correct. +* **Firewall Issues:** If you are unable to access the web interface, ensure that your server's firewall is configured to allow traffic on the appropriate port (80 or 81). +* **PHP-FPM Not Found:** The installation script assumes PHP 8.1. If you are using a different version, you will need to adjust the `fastcgi_pass` directive in the Nginx configuration file. diff --git a/docs/getting-started/quick-installation.md b/docs/getting-started/quick-installation.md new file mode 100644 index 00000000..8ee100a1 --- /dev/null +++ b/docs/getting-started/quick-installation.md @@ -0,0 +1,56 @@ +[PHPCoin Docs](../) > [Getting Started](./) > Installation + + +--- + +# Installation + +This guide will walk you through the process of installing a PHPcoin node on a Debian-based system (e.g., Ubuntu). The easiest way to install PHPcoin is by using the provided installation script, which automates the entire process. + +## Prerequisites + +Before you begin, you will need: + +* A server running a Debian-based Linux distribution (e.g., Ubuntu 20.04, 22.04). +* Root or `sudo` privileges on the server. + +## Quick Installation + +The following one-liner command will download and execute the installation script, setting up a PHPcoin node for the **mainnet**: + +```bash +curl -s https://phpcoin.net/scripts/install_node.sh | bash +``` + +To install a node for the **testnet**, use the following command: + +```bash +curl -s https://phpcoin.net/scripts/install_node.sh | bash -s -- --network testnet +``` + +## What the Script Does + +The installation script performs the following steps: + +1. **Updates System:** Updates the package manager and installs essential packages, including Nginx, PHP, and MariaDB. +2. **Creates Database:** Sets up a new database and user for the PHPcoin node. +3. **Downloads Node:** Clones the latest version of the PHPcoin source code from the official Git repository. +4. **Configures Web Server:** Configures Nginx to serve the PHPcoin web interface. +5. **Initializes Configuration:** Creates the necessary configuration files for the node. +6. **Imports Blockchain:** Downloads and imports a recent snapshot of the blockchain to accelerate the initial synchronization process. +7. **Starts Services:** Starts the Nginx and MariaDB services. + +Once the installation is complete, you can access your PHPcoin node by opening the provided URL in your web browser. + + + +--- + +## Troubleshooting + +If you encounter any issues during the installation, here are some common problems and their solutions: + +* **Script Fails to Run:** Ensure that you have `curl` installed and that you are running the command with `sudo` or as the root user. +* **Port Conflict:** If another service is using port 80 (for mainnet) or 81 (for testnet), the Nginx service may fail to start. You can check for port conflicts using the `sudo netstat -tulpn | grep LISTEN` command. +* **Database Connection Issues:** Verify that the MariaDB service is running and that the database credentials in `config/config.inc.php` are correct. +* **Firewall Issues:** If you are unable to access the web interface, ensure that your server's firewall is configured to allow traffic on the appropriate port (80 or 81). diff --git a/docs/getting-started/running-a-node.md b/docs/getting-started/running-a-node.md new file mode 100644 index 00000000..710df748 --- /dev/null +++ b/docs/getting-started/running-a-node.md @@ -0,0 +1,46 @@ +[PHPCoin Docs](../) > [Getting Started](./) > Running a Node + + +--- + +# Running a Node + +Once you have installed your PHPcoin node, it will be running as a web application. You can access the web interface by navigating to the URL provided at the end of the installation process. + +## Web Interface + +The web interface provides a user-friendly way to interact with your node. It includes the following features: + +* **Block Explorer:** Allows you to browse the blockchain, view blocks, and inspect transactions. +* **Wallet:** Provides a simple and secure way to manage your PHPcoin wallet, send and receive coins, and view your transaction history. +* **Network Information:** Displays information about the current state of the network, including the number of connected peers and the current block height. + +## Command-Line Interface (CLI) + +In addition to the web interface, PHPcoin provides a command-line interface (CLI) for more advanced users. The CLI scripts are located in the `cli/` directory and can be used to perform various tasks, such as: + +* **Running the P2P Server:** The `server.php` script is used to run the node's peer-to-peer (P2P) server, which is responsible for communicating with other nodes on the network. To run the server, execute the following command from the root of your PHPcoin installation: + + ```bash + php cli/server.php + ``` + +* **Synchronizing the Blockchain:** The `sync.php` script is used to manually trigger the synchronization of the blockchain. This is useful if your node has been offline for a while and needs to catch up with the rest of the network. + + ```bash + php cli/sync.php + ``` + +* **Running Cron Jobs:** The `cron.php` script is designed to be run periodically to perform maintenance tasks, such as cleaning up old data and updating the list of peers. + + ```bash + php cli/cron.php + ``` + +* **Mining:** The `miner.php` script can be used to mine PHPcoin from the command line. + + ```bash + php cli/miner.php + ``` + +For most users, the web interface will be sufficient for interacting with the PHPcoin network. However, the CLI provides a powerful set of tools for those who want more control over their node. diff --git a/docs/introduction/README.md b/docs/introduction/README.md new file mode 100644 index 00000000..060796bd --- /dev/null +++ b/docs/introduction/README.md @@ -0,0 +1,11 @@ +[PHPCoin Docs](../) > Introduction + + +--- + +# Introduction + +This section provides a general introduction to PHPCoin. + +* [What is PHPCoin?](./what-is-phpcoin.md) +* [Features](./features.md) diff --git a/docs/introduction/features.md b/docs/introduction/features.md new file mode 100644 index 00000000..9daeb601 --- /dev/null +++ b/docs/introduction/features.md @@ -0,0 +1,22 @@ +[PHPCoin Docs](../) > [Introduction](./) > Features + + +--- + +# Features + +PHPcoin comes with a wide range of features that make it a powerful and flexible cryptocurrency platform. Here are some of its key features: + +* **Proof-of-Work Consensus:** PHPcoin uses a Proof-of-Work (PoW) consensus algorithm to secure its network. This is the same algorithm used by Bitcoin and other major cryptocurrencies. + +* **Web-Based and Command-Line Mining:** You can mine PHPcoin using either a web-based interface or a command-line miner, providing flexibility for different types of users. + +* **Masternodes:** PHPcoin supports masternodes, which are special nodes that provide additional services to the network and earn rewards for their owners. + +* **Decentralized Applications (Dapps):** The platform is designed to support Dapps, allowing developers to build and deploy their own decentralized applications on the PHPcoin blockchain. + +* **Web Interface:** PHPcoin includes a user-friendly web interface that provides access to a block explorer, wallet, and other tools. + +* **API:** A comprehensive API is available for developers who want to build applications that interact with the PHPcoin blockchain. + +* **Open Source:** PHPcoin is an open-source project, which means its code is publicly available for anyone to review, use, or contribute to. diff --git a/docs/introduction/what-is-phpcoin.md b/docs/introduction/what-is-phpcoin.md new file mode 100644 index 00000000..5ee0ecb3 --- /dev/null +++ b/docs/introduction/what-is-phpcoin.md @@ -0,0 +1,12 @@ +[PHPCoin Docs](../) > [Introduction](./) > What is PHPCoin? + + +--- + +# What is PHPcoin? + +PHPcoin is a cryptocurrency built with PHP, designed to be lightweight, easy to use, and accessible to a wide range of users. It is a full-featured blockchain implementation that can be used for various purposes, from simple transactions to more complex decentralized applications (Dapps). + +The project's primary goal is to provide a simple and stable cryptocurrency that can be easily understood, modified, and deployed. This makes it an excellent choice for developers who want to learn about blockchain technology or build their own custom blockchain solutions. + +PHPcoin is an open-source project, and its code is publicly available for anyone to review, use, or contribute to. diff --git a/docs/masternodes/README.md b/docs/masternodes/README.md new file mode 100644 index 00000000..baec467f --- /dev/null +++ b/docs/masternodes/README.md @@ -0,0 +1,10 @@ +[PHPCoin Docs](../) > Masternodes + + +--- + +# Masternodes + +This section provides information on setting up and running a PHPCoin masternode. + +* [Setting up a Masternode](./setting-up-a-masternode.md) diff --git a/docs/masternodes/setting-up-a-masternode.md b/docs/masternodes/setting-up-a-masternode.md new file mode 100644 index 00000000..da1d8987 --- /dev/null +++ b/docs/masternodes/setting-up-a-masternode.md @@ -0,0 +1,82 @@ +[PHPCoin Docs](../) > [Masternodes](./) > Setting up a Masternode + + +--- + +# Setting up a Masternode + +A masternode is a special type of node that provides additional services to the PHPcoin network. In return for these services, masternode owners receive rewards in the form of PHPcoin. + +## Requirements + +To set up a masternode, you will need: + +* A running PHPcoin node. +* Enough PHPcoin to cover the masternode collateral. You can check the current collateral amount by calling the `/api.php?q=getCollateral` API endpoint. +* A separate PHPcoin address for the masternode. This address will be used to identify the masternode on the network. +* Optionally, a separate PHPcoin address to receive the masternode rewards. This is known as a "cold" masternode setup. + +## Setting up a Masternode + +To set up a masternode, you will use the command-line wallet. + +### 1. Create a Masternode Address + +First, you need to create a new PHPcoin address that will be used for your masternode. You can do this by running the `wallet.php` script from the `utils/` directory: + +```bash +php utils/wallet.php +``` + +This will create a new `phpcoin.dat` file in the current directory. Make sure to back up this file in a safe place. + +### 2. Fund the Masternode Address + +Next, you need to send the exact collateral amount to the masternode address you just created. You can do this from another wallet or an exchange. + +### 3. Create the Masternode + +Once the collateral transaction has been confirmed, you can create the masternode by running the `masternode-create` command from your main wallet (not the masternode wallet): + +```bash +php utils/wallet.php masternode-create [reward_address] +``` + +* ``: The address of your masternode. +* `[reward_address]` (optional): The address where you want to receive the masternode rewards. If you don't specify a reward address, the rewards will be sent to the masternode address. + +This command will create and send a special transaction to the network that will register your masternode. + +### 4. Verifying Your Masternode + +You can verify that your masternode is running correctly by calling the `/api.php?q=getMasternode` API endpoint: + +``` +/api.php?q=getMasternode&address= +``` + +If your masternode is set up correctly, this will return information about your masternode. + +--- + +## Advanced Masternode Setup + +### Cold Masternode + +A "cold" masternode setup allows you to keep the wallet containing your masternode collateral offline and secure, while the masternode itself runs on a separate server. This is the recommended setup for most users, as it significantly improves security. + +The `masternode-create` command shown above is designed for a cold setup. The wallet that runs this command is the "control" wallet, and it can be kept offline after the masternode is created. The masternode itself runs on a separate server, and its wallet does not need to contain any funds. + +### Security Best Practices + +* **Firewall:** Use a firewall to restrict access to your masternode server. Only allow incoming connections on the necessary ports (e.g., the P2P port and the web interface port). +* **SSH Keys:** Use SSH keys instead of passwords to access your server. This is much more secure than using a password, which can be vulnerable to brute-force attacks. +* **Regular Updates:** Keep your server's operating system and all software up to date. This will ensure that you have the latest security patches and bug fixes. + +### Monitoring Your Masternode + +It is important to monitor your masternode to ensure that it is running correctly and that you are receiving rewards. Here are some ways to monitor your masternode: + +* **API Endpoint:** Use the `/api.php?q=getMasternode` API endpoint to check the status of your masternode. +* **Block Explorer:** Use a block explorer to check that your masternode is receiving rewards. You can find your masternode's reward address in the output of the `getMasternode` API call. +* **Third-Party Monitoring Services:** There are several third-party services that can monitor your masternode for you and send you an alert if it goes offline. diff --git a/docs/mining/README.md b/docs/mining/README.md new file mode 100644 index 00000000..3c378353 --- /dev/null +++ b/docs/mining/README.md @@ -0,0 +1,13 @@ +[PHPCoin Docs](../) > Mining + + +--- + +# Mining Documentation + +This section contains documentation related to mining PHPCoin. + +## Documents + +* [**How to Mine**](./how-to-mine.md) - A guide on how to get started with mining. +* [**Mining Workflows**](./miner-workflow.md) - A detailed, technical step-by-step breakdown of the different mining processes. diff --git a/docs/mining/how-to-mine.md b/docs/mining/how-to-mine.md new file mode 100644 index 00000000..7561dbde --- /dev/null +++ b/docs/mining/how-to-mine.md @@ -0,0 +1,53 @@ +[PHPCoin Docs](../) > [Mining](./) > How to Mine + + +--- + +# How to Mine + +PHPcoin uses a Proof-of-Work (PoW) consensus algorithm, which means that new coins are created by solving complex mathematical problems. This process is called mining. + +There are two ways to mine PHPcoin: + +* **Command-Line Mining:** Using the command-line interface (CLI) miner. +* **Web-Based Mining:** Using a web-based miner that connects to a PHPcoin node. + +## Command-Line Mining + +The command-line miner is ideal for users who are comfortable with the terminal and want to have more control over the mining process. + +### Configuration + +Before you can start mining, you need to configure your miner by editing the `config/config.inc.php` file. You will need to set the following options: + +* `miner`: Set this to `true` to enable the command-line miner. +* `miner_public_key`: Your PHPcoin public key. This is where the mining rewards will be sent. +* `miner_private_key`: Your PHPcoin private key. This is used to sign the blocks you mine. +* `miner_cpu`: The percentage of CPU you want to dedicate to mining. For example, `25` for 25% CPU usage. + +### Running the Miner + +To start the command-line miner, run the following command from the root of your PHPcoin installation: + +```bash +php cli/miner.php +``` + +The miner will then start hashing and searching for new blocks. + +## Web-Based Mining + +The web-based miner is a more user-friendly option that allows you to mine PHPcoin directly from your web browser. + +### Configuration + +To enable the web-based miner, you need to configure your node by editing the `config/config.inc.php` file. You will need to set the following options: + +* `generator`: Set this to `true` to enable the web-based miner. +* `generator_public_key`: The public key of the node. +* `generator_private_key`: The private key of the node. +* `allowed_hosts`: A list of IP addresses that are allowed to connect to the web miner. You can set this to `['*']` to allow all hosts. + +### Running the Miner + +Once you have configured your node, you can use a web-based miner to connect to it and start mining. You can find a list of compatible web miners on the PHPcoin website or community forums. diff --git a/docs/mining/miner-workflow.md b/docs/mining/miner-workflow.md new file mode 100644 index 00000000..39f350b9 --- /dev/null +++ b/docs/mining/miner-workflow.md @@ -0,0 +1,116 @@ +[PHPCoin Docs](../) > [Mining](./) > Mining Workflows + + +--- + +# Mining Workflows + +This document outlines the complete step-by-step workflows for the different mining processes in PHPCoin. + +## CLI Miner Workflow + +This section describes the process for the standalone command-line miner, executed via `utils/miner.php`. + + + +### Initialization + +1. **Parse Command-Line Arguments & Config:** The script launches and reads settings from `miner.conf` and command-line arguments, with arguments taking precedence. + * **File:** [`utils/miner.php`](https://github.com/phpcoinn/node/tree/main/utils/miner.php) + +2. **Start Miner Instance(s):** Based on the `threads` setting, it uses the `Forker` class to create one or more child processes, each with its own `Miner` instance. + * **File:** [`utils/miner.php`](https://github.com/phpcoinn/node/tree/main/utils/miner.php) + +### Main Mining Loop + +1. **Fetch Alternative Mining Nodes:** Before the loop, it gets a list of other nodes for fallback purposes. + * **File:** [`include/class/Miner.php`](https://github.com/phpcoinn/node/tree/main/include/class/Miner.php) + * **Function:** `getMiningNodes()` + +2. **Get Mining Info:** At the start of each loop, it requests the current `height`, `difficulty`, and last `block` ID from a node. + * **File:** [`include/class/Miner.php`](https://github.com/phpcoinn/node/tree/main/include/class/Miner.php) + * **Function:** `getMiningInfo()` + +3. **Enter Hashing Sub-Loop:** It enters a nested loop, where each iteration is a single hashing attempt. + +4. **Calculate Elapsed Time:** It calculates the seconds passed since the last block's timestamp, adjusted for clock offset. + +5. **Calculate Argon2id Hash:** It computes an Argon2id hash using `password_hash()` with specific memory/time costs depending on block height. + * **File:** [`include/class/Block.php`](https://github.com/phpcoinn/node/tree/main/include/class/Block.php) + * **Function:** `calculateArgonHash()` + +6. **Calculate Nonce:** It derives a nonce from a `sha256` hash of the chain ID, miner address, previous block date, elapsed time, and the Argon2id hash. + * **File:** [`include/class/Block.php`](https://github.com/phpcoinn/node/tree/main/include/class/Block.php) + * **Function:** `calculateNonce()` + +7. **Calculate Hit & Target:** It calculates the numerical `hit` and the dynamic `target` for the attempt. The `hit` calculation is scaled by `BLOCK_TARGET_MUL` (a constant set to `1000`) for precision. + * **File:** [`include/class/Block.php`](https://github.com/phpcoinn/node/tree/main/include/class/Block.php) + * **Functions:** `calculateHit()`, `calculateTarget()` + +8. **Check for Solution:** It checks if `hit > target`. + +9. **Check for New Network Block:** Every 10 seconds of `elapsed` time, it re-checks the mining info. If the network's block ID has changed, it aborts the current attempt and starts the main loop over. + +### Block Submission + +1. **Submit to Node(s):** Once a block is found, it's submitted to the primary node's API. If that fails, it attempts to submit to the fallback nodes. + * **File:** [`include/class/Miner.php`](https://github.com/phpcoinn/node/tree/main/include/class/Miner.php) + * **Function:** `sendHash()` + +--- + +## Node Miner Workflow + +The integrated Node Miner follows the same core hashing loop but with key operational differences. + +* **File:** [`include/class/NodeMiner.php`](https://github.com/phpcoinn/node/tree/main/include/class/NodeMiner.php) + +### Key Differences + +1. **Configuration:** It uses the `miner_public_key` and `miner_private_key` from the node's main config file. +2. **Mempool Integration:** It gathers pending transactions from the node's local mempool to include in the block. +3. **Reward Generation:** It creates and signs all reward transactions (miner, generator, masternode, etc.) for the new block. +4. **Local Block Submission:** It adds a found block directly to its own local blockchain database. + * **Function:** `Block->add()` +5. **Direct Propagation:** It immediately propagates the new block to all connected peers. + * **Function:** `Propagate::blockToAll('current')` +6. **Pre-Mining Checks:** It verifies the node is not syncing, has enough peers, and has a sufficient node score before starting. + +--- + +## Node Workflow: Verifying and Processing Blocks + +This section describes how a node processes, verifies, and propagates a block, whether received from a CLI miner via the API or an internal Node Miner. + +### 1. Initial Receipt and Pre-Validation +* **CLI Miner:** A block is received at the `/mine.php?q=submitHash` endpoint. The node performs initial checks: miner version, generator status, node health, peer count, and if the submitted block height is exactly one greater than the current blockchain height. + * **File:** [`web/mine.php`](https://github.com/phpcoinn/node/tree/main/web/mine.php) +* **Node Miner:** The block is generated internally; these checks are performed before mining even begins. + +### 2. Block Construction and Reward Generation +* **CLI Miner:** The node takes the `argon`, `nonce`, and other data from the API request. It then gathers transactions from its own mempool and generates all the necessary reward transactions (miner, generator, masternode, etc.). It signs the complete block with its own generator private key. + * **File:** [`web/mine.php`](https://github.com/phpcoinn/node/tree/main/web/mine.php) +* **Node Miner:** This is done *before* the hashing process begins. + +### 3. Core Mining Validation +The node calls the `mine()` method on the newly constructed block object. This is a critical verification step that re-calculates the `nonce`, `hit`, and `target` based on the submitted data to ensure the proof-of-work is valid. +* **File:** [`include/class/Block.php`](https://github.com/phpcoinn/node/tree/main/include/class/Block.php) +* **Function:** `mine()` + +### 4. Database Insertion and Transaction Processing +If the `mine()` validation succeeds, the node calls the `add()` method. This function: +1. Performs final validation checks (e.g., block signature, version code). +2. Starts a database transaction. +3. Inserts the block record into the `blocks` table. +4. Calls `parse_block()` to process and insert every transaction from the block data into the `transactions` table, updating account balances accordingly. +5. Commits the database transaction. +* **File:** [`include/class/Block.php`](https://github.com/phpcoinn/node/tree/main/include/class/Block.php) +* **Functions:** `add()`, `parse_block()` + +### 5. Propagation +Once the block is successfully added to the local database, the node immediately propagates it to its peers. +1. The `Propagate::blockToAll()` method is called. +2. This method executes a command-line script (`php cli/propagate.php`) in a new background process. +3. The `propagate.php` script fetches all active peers and sends the new block to each one. +* **File:** [`include/class/Propagate.php`](https://github.com/phpcoinn/node/tree/main/include/class/Propagate.php) +* **Function:** `blockToAll()` diff --git a/docs/smart-contracts/README.md b/docs/smart-contracts/README.md new file mode 100644 index 00000000..30a3ef87 --- /dev/null +++ b/docs/smart-contracts/README.md @@ -0,0 +1,10 @@ +[PHPCoin Docs](../) > Smart Contracts + + +--- + +# Smart Contracts + +This section provides a guide to developing smart contracts on the PHPCoin platform. + +* [Builder's Guide](./builders-guide.md) diff --git a/docs/smart-contracts/builders-guide.md b/docs/smart-contracts/builders-guide.md new file mode 100644 index 00000000..1248f712 --- /dev/null +++ b/docs/smart-contracts/builders-guide.md @@ -0,0 +1,464 @@ +[PHPCoin Docs](../) > [Smart Contracts](./) > Smart Contract Builder's Guide + + +--- + +# 🛠️ PHPCoin Smart Contract Builder's Guide + +This guide outlines the essential structural and utility rules for creating a functional smart contract in the PHPCoin environment. + +## ⚙️ Structural Rules + +Every PHPCoin smart contract is a PHP class with specific structural requirements. These rules ensure that the contract can be correctly deployed and executed by the blockchain. + +| Element | Requirement | Example (Minimal) | +|---|---|---| +| **Class Definition** | Must extend `SmartContractBase`. | `class YourContract extends SmartContractBase { ... }` | +| **Class Name Constant** | A `const SC_CLASS_NAME` must be defined, holding the name of the class. | `const SC_CLASS_NAME = "YourContract";` | +| **Deployment Method** | The `deploy()` method, annotated with `@SmartContractDeploy`, is executed once upon deployment to set the contract's initial state. | `/** @SmartContractDeploy */ public function deploy($param) { ... }` | + +## 💾 State Management + +PHPCoin smart contracts manage persistent state through class properties annotated with specific docblock tags. These properties are automatically backed by the blockchain's state database. + +| Data Type | PHP Annotation/Definition | Usage | +|---|---|---| +| **Single Value** | `/** @SmartContractVar */ public $owner;` | Used for scalar values like addresses, numbers, or strings. | +| **Map/Array** | `/** @SmartContractMap */ public SmartContractMap $balances;` | Used for associative arrays, mapping keys to values (e.g., user balances). | + +### Reading and Writing State + +Interaction with state variables is designed to be intuitive, using standard PHP syntax. + +| Operation | Example | Description | +|---|---|---| +| **Write (Single)** | `$this->owner = $this->src;` | Assign a value to a `@SmartContractVar`. | +| **Read (Single)** | `$currentOwner = $this->owner;` | Retrieve the value of a `@SmartContractVar`. | +| **Write (Map)** | `$this->balances[$userAddress] = 100;` | Set a value for a key in a `SmartContractMap`. | +| **Read (Map)** | `$userBalance = $this->balances[$userAddress];` | Get the value for a key from a `SmartContractMap`. | + +## 🏷️ Annotations + +PHPCoin uses docblock annotations to define the type and behavior of smart contract methods and properties. These annotations are essential for the PHPCoin runtime to correctly interpret and execute the contract's code. + +| Annotation | Purpose | Example | +|---|---|---| +| **`@SmartContractDeploy`** | Marks the method that is executed only once when the contract is deployed. This is where you should set the initial state of the contract. | `/** @SmartContractDeploy */ public function deploy() { ... }` | +| **`@SmartContractView`** | Designates a read-only method. View methods can be called without creating a transaction and cannot modify the contract's state. | `/** @SmartContractView */ public function getOwner() { return $this->owner; }` | +| **`@SmartContractTransact`** | Marks a method that modifies the contract's state. Executing a transact method requires sending a transaction to the network. | `/** @SmartContractTransact */ public function setOwner($newOwner) { ... }` | +| **`@SmartContractVar`** | Declares a public property as a persistent, single-value state variable. | `/** @SmartContractVar */ public $owner;` | +| **`@SmartContractMap`** | Declares a public property of type `SmartContractMap` as a persistent key-value store. | `/** @SmartContractMap */ public SmartContractMap $balances;` | + +## 🛡️ Core Transaction Templates + +All methods marked with `@SmartContractTransact` should include checks to ensure the integrity and security of the contract. The following templates cover the most common requirements. + +| Security Rule | Code Requirement / Example | Rationale | +|---|---|---| +| **Access Control** | `if ($this->src != $this->owner) { $this->error("UNAUTHORIZED"); }` | Verifies that the sender (`$this->src`) has permission to execute the function. | +| **Input Validation** | `if (!Account::valid($to)) { $this->error("INVALID_ADDRESS"); }` | Ensures that provided data, such as addresses, are in the correct format before processing. | +| **Fee Check** | `if ($this->value < MINIMUM_FEE) { $this->error("INSUFFICIENT_FEE"); }` | Prevents Denial-of-Service or griefing attacks by requiring a minimum fee. | +| **Data Size Limit** | `if (strlen($input) > MAX_INPUT_SIZE) { $this->error("INPUT_TOO_LONG"); }` | Enforces storage and processing limits on user-provided data. | +| **Non-Overwrite** | `if ($this->records[$id] !== null) { $this->error("RECORD_EXISTS"); }` | Prevents the accidental or malicious overwriting of existing state. | + +## 🔩 Built-In Utilities + +The `SmartContractBase` class provides a set of properties and methods for accessing transaction data, controlling execution, and interacting with the blockchain environment. + +| Category | Method / Property | Purpose | +|---|---|---| +| **Transaction Data** | `string $this->id` | The hash of the current transaction. | +| **Transaction Data** | `string $this->src` | The address of the sender. | +| **Transaction Data** | `float $this->value` | The amount of PHPCoin sent with the transaction. | +| **Transaction Data** | `int $this->height` | The current block height. | +| **Transaction Data** | `string $this->address` | The contract's own address. | +| **State Reversion** | `void $this->error(string $msg)` | Halts execution, reverts all state changes, and returns the error message. | +| **Inter-Contract** | `mixed $this->callSmartContract(string $contract, string $method, array $params)` | Performs a read-only call to a method on another smart contract. | +| **Inter-Contract** | `void $this->execSmartContract(string $contract, string $method, array $params)` | Executes a state-changing transaction on another smart contract. | +| **Outgoing TX** | `Transaction::send(string $to, float $amount)` | Executes an outgoing transaction from the contract's address to another address. | + +## 🪙 ERC20 Token Templates + +The repository includes a set of pre-built ERC20 token templates that you can extend to create your own tokens. These templates are located in the `include/templates/tokens` directory. + +| Template | Description | +|---|---| +| `erc_20_token.php` | A standard ERC20 token with basic transfer and allowance functionality. | +| `erc_20_token_burnable.php` | An ERC20 token that can be "burned" or destroyed, reducing the total supply. | +| `erc_20_token_mintable.php` | An ERC20 token that allows for the creation of new tokens, increasing the total supply. | +| `erc_20_token_burnable_mintable.php` | An ERC20 token that is both burnable and mintable. | + +## 📦 Deployment + +This section covers the tools and procedures for deploying your smart contracts. + +### Compiling the Contract + +The `utils/sc_compile.php` script is used to package your smart contract's source code into a `.phar` file. + +**Usage:** + +```bash +php utils/sc_compile.php [contract_address] [source_file.php] [output_file.phar] +``` + +- `[contract_address]`: The address where the contract will be deployed. +- `[source_file.php]`: The path to your smart contract's PHP source file. +- `[output_file.phar]`: The path where the compiled `.phar` file will be saved. + +### Deploying the Contract + +After compiling the contract, you need to create a deployment transaction. This is done by creating a separate PHP script that uses the `SCUtil` class. + +### Deployment Cost + +Deploying a smart contract to the PHPCoin network incurs a fixed fee. This fee prevents spam and compensates the network for storing the contract's code. + +- **Mainnet:** 1000 PHPCoin +- **Testnet:** 100 PHPCoin + +This value is defined in the `getSmartContractCreateFee()` method within the `include/class/Blockchain.php` file. + +**Example Deployment Script (`deploy.php`):** + +```php +message = $initialMessage; + } + + /** + * @SmartContractTransact + * Updates the contract's main message. + * @param string $newMessage The new message. + */ + public function setMessage($newMessage) + { + $this->message = $newMessage; + } + + /** + * @SmartContractTransact + * Allows any user to store a personal record. + * @param string $key The key for the record. + * @param string $value The value to store. + */ + public function setRecord($key, $value) + { + $this->records[$key] = $value; + } + + /** + * @SmartContractView + * Returns the main message. + * @return string The current message. + */ + public function getMessage() + { + return $this->message; + } + + /** + * @SmartContractView + * Retrieves a specific record by its key. + * @param string $key The key of the record. + * @return string|null The record's value, or null if not found. + */ + public function getRecord($key) + { + return $this->records[$key]; + } +} +``` + +### Example 3: Full Feature App (Advanced Poll) + +This example builds on the previous concepts to create a more robust polling contract. It introduces owner-only actions, a fee requirement for voting, and a lifecycle (the poll can be opened and closed). + +```php +owner = $this->src; + $this->question = $question; + $this->isOpen = true; + + // Initialize options and vote counts + foreach ($options as $option) { + if (!empty($option)) { + $this->options[$option] = true; + $this->votes[$option] = 0; + } + } + } + + /** + * @SmartContractTransact + * Allows any user to cast a vote, provided they pay the fee. + * @param string $option The option to vote for. + */ + public function vote($option) + { + if (!$this->isOpen) { + $this->error("POLL_CLOSED"); + } + if ($this->voters[$this->src] === true) { + $this->error("ALREADY_VOTED"); + } + if ($this->options[$option] !== true) { + $this->error("INVALID_OPTION"); + } + if ($this->value < self::VOTE_FEE) { + $this->error("INSUFFICIENT_FEE"); + } + + $this->voters[$this->src] = true; + $this->votes[$option] = (int)$this->votes[$option] + 1; + } + + /** + * @SmartContractTransact + * Allows the owner to close the poll. + */ + public function closePoll() + { + if ($this->src !== $this->owner) { + $this->error("UNAUTHORIZED"); + } + $this->isOpen = false; + } + + /** + * @SmartContractTransact + * Allows the owner to withdraw the collected fees. + */ + public function withdrawFees() + { + if ($this->src !== $this->owner) { + $this->error("UNAUTHORIZED"); + } + + $balance = Account::getBalance($this->address); + if ($balance > 0) { + Transaction::send($this->owner, $balance); + } + } + + /** + * @SmartContractView + * Returns the current status of the poll. + * @return array The poll question and its open status. + */ + public function getStatus() + { + return [ + 'question' => $this->question, + 'isOpen' => $this->isOpen + ]; + } + + /** + * @SmartContractView + * Returns the results of the poll. + * @return array A map of options to their vote counts. + */ + public function getResults() + { + $results = []; + $options = $this->options->keys(); + foreach ($options as $option) { + $results[$option] = (int)$this->votes[$option]; + } + return $results; + } +} +``` + +### Example 4: Wrapped PHPCoin (WPHP) + +This contract creates a token that is pegged 1:1 with PHPCoin. Users can send PHPCoin to the contract to "wrap" it into a token, and burn tokens to "unwrap" them back into PHPCoin. This allows PHPCoin to be used in token-based applications. + +```php +value; + if ($amount <= 0) { + $this->error("AMOUNT_TOO_LOW"); + } + + $this->balances[$this->src] = bcadd($this->balances[$this->src], $this->amountToInt($amount)); + $this->totalSupply = bcadd($this->totalSupply, $this->amountToInt($amount)); + } + + /** + * @SmartContractTransact + * Unwraps WPHP tokens back into PHPCoin. + * @param float $amount The amount of WPHP to unwrap. + */ + public function unwrap($amount) + { + $value = $this->amountToInt($amount); + if ($this->balances[$this->src] < $value) { + $this->error("INSUFFICIENT_BALANCE"); + } + + $this->balances[$this->src] = bcsub($this->balances[$this->src], $value); + $this->totalSupply = bcsub($this->totalSupply, $value); + + Transaction::send($this->src, $amount); + } +} +``` diff --git a/docs/staking/README.md b/docs/staking/README.md new file mode 100644 index 00000000..1d427032 --- /dev/null +++ b/docs/staking/README.md @@ -0,0 +1,9 @@ +[PHPCoin Docs](../) > Staking + +--- + +# Staking + +This section explains how to stake your PHPCoin holdings to earn rewards and support the network. + +* [How to Stake](./how-to-stake.md) - A guide on how to start staking your PHPCoin, including technical details about the Proof-of-Stake algorithm, rewards, and requirements. diff --git a/docs/staking/how-to-stake.md b/docs/staking/how-to-stake.md new file mode 100644 index 00000000..5915da87 --- /dev/null +++ b/docs/staking/how-to-stake.md @@ -0,0 +1,76 @@ +[PHPCoin Docs](../) > [Staking](./) > How to Stake + +--- + +# How to Stake + +## Introduction + +Staking is the process of holding PHPCoin in your wallet to support the blockchain network. In return for holding coins, you receive rewards in the form of new PHPCoin. + +## How Staking Works + +PHPcoin uses a Proof-of-Stake (PoS) system to reward coin holders. Unlike miners or block generators, **stakers do not create or validate blocks**. Instead, for each block created, the network automatically chooses a "stake winner" from all eligible participants to receive a reward. + +To be eligible for staking rewards, you must meet the network's minimum balance and coin maturity requirements. + +## How to Start Staking + +To become eligible for staking rewards, you must first send a special "stake" transaction. This is a one-time action that flags your address as a staking participant. + +1. **Open a terminal or command prompt.** +2. **Navigate to the directory where your PHPcoin wallet is located.** +3. **Run the following command:** + + ```bash + php cli/wallet.php send 0 "stake" + ``` + + Replace `` with your own wallet address. The `0` is the amount to send (this special transaction is free), and `"stake"` is the message that activates your address for staking. + +## Staking Requirements + +### Current Requirements (Block 1,000,001+) + +For the current mainnet (at over 1,250,000 blocks), the requirements are simple and fixed: + +* **Coin Maturity:** **60 blocks**. Your coins must be held for at least 60 blocks before they are eligible. +* **Minimum Balance:** **160,000 PHPCoin**. You must hold at least this amount to be eligible. + +### Historical Requirements + +The staking requirements have changed during the blockchain's history. + +* **Before block 290,000:** + * Coin Maturity: 600 blocks + * Minimum Balance: 100 PHPCoin +* **Block 290,001 - 1,000,000:** + * Coin Maturity: 60 blocks + * Minimum Balance: Increased in stages, from 30,000 to 140,000 PHPCoin. + +--- + +## Technical Details + +### 1. Activating an Address for Staking + +An address is recognized as a staking address after it has been the destination of a transaction with the message `"stake"`. + +* **Code Reference:** The `getAddressTypes` function in `include/class/Block.php`. + +### 2. Code References for Requirements + +* **Maturity:** The `getStakingMaturity()` function in `include/class/Blockchain.php` controls this value. The change from 600 to 60 blocks is triggered by the `UPDATE_11_STAKING_MATURITY_REDUCE` constant (value: `290000`) in `include/coinspec.inc.php`. +* **Minimum Balance:** The `getStakingMinBalance()` function in `include/class/Blockchain.php` controls this value. The change from a fixed 100 PHPCoin to a dynamic value is triggered by the `UPDATE_12_STAKING_DYNAMIC_THRESHOLD` constant (value: `290000`) in `include/coinspec.inc.php`. The specific amounts are derived from the `REWARD_SCHEME` constant in `include/rewards.inc.php`. + +### 3. Stake Winner Selection + +For each block, a single stake winner is selected from all eligible accounts based on a `weight`. + +* **Code Reference:** The `getStakeWinner` function in `include/class/Account.php` calculates this `weight` using the formula: `(current_block_height - last_transaction_height) * account_balance`. The account with the highest weight wins. + +### 4. Staking Rewards + +The reward amount is determined by the block height. + +* **Code Reference:** The `reward` function in `include/class/Block.php` reads the reward structure from the `REWARD_SCHEME` constant in `include/rewards.inc.php`. diff --git a/docs/wallet/README.md b/docs/wallet/README.md new file mode 100644 index 00000000..ab3f6765 --- /dev/null +++ b/docs/wallet/README.md @@ -0,0 +1,10 @@ +[PHPCoin Docs](../) > Wallet + + +--- + +# Wallet + +This section provides instructions on how to use the PHPCoin wallet. + +* [Using the Wallet](./using-the-wallet.md) diff --git a/docs/wallet/using-the-wallet.md b/docs/wallet/using-the-wallet.md new file mode 100644 index 00000000..77c7ceb1 --- /dev/null +++ b/docs/wallet/using-the-wallet.md @@ -0,0 +1,56 @@ +[PHPCoin Docs](../) > [Wallet](./) > Using the Wallet + + +--- + +# Using the Wallet + +PHPcoin provides two ways to manage your wallet: + +* **Command-Line Wallet:** A command-line interface (CLI) for advanced users. +* **Web-Based Wallet:** A user-friendly web interface for managing your wallet. + +## Command-Line Wallet + +The command-line wallet is a powerful tool that allows you to perform all wallet-related operations from the terminal. + +### Creating a Wallet + +To create a new wallet, simply run the `wallet.php` script from the `utils/` directory: + +```bash +php utils/wallet.php +``` + +If no wallet file is found, a new one will be created. You will be prompted to encrypt the wallet with a password. + +### Wallet Commands + +The command-line wallet supports a variety of commands: + +* `balance [address]`: Check the balance of your wallet or a specific address. +* `export`: Display your wallet's public and private keys. +* `block`: Get information about the current block. +* `encrypt`: Encrypt your wallet with a password. +* `decrypt`: Decrypt your wallet. +* `transactions`: List the latest transactions for your wallet. +* `transaction `: Get information about a specific transaction. +* `send
[message]`: Send coins to another address. +* `masternode-create
`: Create a masternode. +* `masternode-remove [address]`: Remove a masternode. +* `sign `: Sign a message with your wallet's private key. +* `smart-contract-create
`: Create a smart contract. +* `smart-contract-exec
`: Execute a smart contract. +* `smart-contract-send
`: Send coins from a smart contract. + +To get a list of all available commands, run: + +```bash +php utils/wallet.php help +``` + +## Web-Based Wallet + +The web-based wallet is part of the PHPcoin web interface. It provides a user-friendly way to manage your wallet, send and receive coins, and view your transaction history. + +To access the web-based wallet, open your PHPcoin node's URL in your web browser and navigate to the "Wallet" section. diff --git a/docs/white-paper/README.md b/docs/white-paper/README.md new file mode 100644 index 00000000..c1002fff --- /dev/null +++ b/docs/white-paper/README.md @@ -0,0 +1,4 @@ +# PHPCoin White Paper + +* [PHPCoin White Paper (Markdown)](./white-paper.md) +* [PHPCoin White Paper (PDF)](./white-paper.pdf) diff --git a/docs/white-paper/image-000.png b/docs/white-paper/image-000.png new file mode 100644 index 00000000..62d35e63 Binary files /dev/null and b/docs/white-paper/image-000.png differ diff --git a/docs/white-paper/image-002.png b/docs/white-paper/image-002.png new file mode 100644 index 00000000..852fc221 Binary files /dev/null and b/docs/white-paper/image-002.png differ diff --git a/docs/white-paper/image-003.png b/docs/white-paper/image-003.png new file mode 100644 index 00000000..ce9bdc41 Binary files /dev/null and b/docs/white-paper/image-003.png differ diff --git a/docs/white-paper/image-004.png b/docs/white-paper/image-004.png new file mode 100644 index 00000000..aadbb385 Binary files /dev/null and b/docs/white-paper/image-004.png differ diff --git a/docs/white-paper/image-005.png b/docs/white-paper/image-005.png new file mode 100644 index 00000000..edf3ed8a Binary files /dev/null and b/docs/white-paper/image-005.png differ diff --git a/docs/white-paper/white-paper.md b/docs/white-paper/white-paper.md new file mode 100644 index 00000000..e9ed2468 --- /dev/null +++ b/docs/white-paper/white-paper.md @@ -0,0 +1,393 @@ +[PHPCoin Docs](../) > [White Paper](./) > PHPCoin White Paper + +[Up](README.md) | [Table of Contents](../README.md) + +--- + +# PHPCoin Whitepaper + +**Version 1.6** + +**Date: 2023-02-08** + +**http://phpcoin.net** + +## Changes + +| Date | Version | Changes | +|------------|---------|-------------------------------------------------------------| +| 07.05.2021 | 1.0 | - Initial version | +| 22.09.2021 | 1.1 | - Added new images | +| 27.09.2021 | 1.2 | - Updated images | +| 23.02.2022 | 1.3 | - Added masternode concept | +| 04.11.2022 | 1.4 | - Added sections with decentralized apps and Smart Contracts | +| 22.11.2022 | 1.5 | - Update image for miner (added Chain ID) | +| 08.02.2023 | 1.6 | - Corrected some points, added staking and swap sections | + +## Introduction + +In the world of many different crypto coins which are mostly copy-paste (fork) there are a small number of them written in different languages than c++. With evolving blockchain technologies it has become easier to understand the philosophy behind it and adapt to other programming languages. That's how phpcoin was born. + +Decision to implement blockchain in php was chosen because it is still one of the most popular and easily understandable programming languages in the world but not so much established in the crypto world. + +Idea of phpcoin is to present basic crypto philosophy to a broader audience of developers for further improvements and focusing on utilizing blockchain technology for web services, rather than just earning some cryptocurrency. + +Also the goal of phpcoin is to solve some of the proven and known cons of existing blockchains and cryptocurrencies. + +### Disclaimer + +Phpcoin is not completely written from scratch. Its basic idea comes from the Arionum coin built and established in 2018, their experience and code which gives an easy understable concept of blockchain philosophy. + +Php is not just a fork of Arionum, it is built on their base, modified and adapted to meet php basic blockchain philosophy, and add more unique features to it. + +Phpcoin will stay open source, encouraging other developers to continue evolving on top of its code, in such a way promoting php as a new blockchain underlying technology. + +### Why another coin? + +While many coins are just designed to build, gain interest, give fast profit to owners and exit as potential scam, phpcoin will tend to develop through a large developer community who can contribute to the project. + +Also phpcoin is designed as some kind of "green" coin which is tend to solve issue of heavy mining algorithms by utilizing its own and unique elapsed proof of work (EPOW) + +Phpcoin is built on the web based platform which is easily understandable to a wider number of developers. It explains basic blockchain principles and allows people to understand its full purpose not just for earning profit. + +In that way phpcoin will implement decentralized apps and databases and smart contracts written fully in well known and popular language php. + +## Blockchain + +### Technology and structure + +#### Node + +Blockchain is organized through a distributed network of peers. Each peer is recognized as a node. + +Node is the central unit in the network. It needs to be deployed on a server and available online. It is started in order to support the network and provide services. Node has a built in miner to mine coins in the background and earn revenue to its owners. Blockchain is stored in a database which is replicated on all nodes using propagation of block transactions. + +By utilizing transactions different actions in the network can be distributed. + +Node mining process is light and easy in order to not affect the operation of the node. If excessive mining occurs and a node becomes inoperative it can be blacklisted from other peers on the network. + +Node can have many different functions which can be configured: + +* **Explorer** - node hosts full blockchain explorer +* **Dapps** - special service of node who checks, propagates and pushes decentralized apps +* **Miner** - node can be a standalone miner who mines new blocks in the background. Complete block reward goes to the node. +* **Generator** - node can be configured as a service which accepts hashes from miners, validates them and adds blocks to blockchain. +* **Admin** - if enabled allows web administration of node to owner +* **Masternode** - if configured and activated receive part of block rewards + +![Miner](image-000.png) + +#### Miner + +Miners are used to move blockchain. + +They can be standalone, web or node miners. + +* **Standalone and web miners** are simple miners which allow users to easily earn coins with mining. In both cases users need to provide an address to which they will receive a reward for the mined block. Miner is connected to an associated node and block reward is split 90-10 % between miner and node. +* **Node miner** is a service process on deployed nodes which mines and adds blocks. Complete block reward goes to it. + +#### Wallet + +Wallet is a service which allows users to manage their account. As opposed to other cryptocurrencies, PHPCoin each wallet holds only one address with private key and public key. + +Wallet will serve as a mailbox for users where they can receive messages and notifications from future apps. + +Also wallets can be standalone or web. + +A standalone wallet is an application (cli or gui) which is installed on a user computer and connects to an associated node to perform wallet functions. + +Wallet has its own account which can be encrypted to improve security. + +Gui wallet also has an integrated standalone miner. + +#### Web wallet + +Every node on the network holds a full web wallet as a Decentralized app. + +Users register and login with their private key and can access wallet and other services. + +### Account + +An account is a basic point of blockchain. It is used to identify objects (person, node) who will participate in blockchain functions. + +Account is consist of three pieces of information which are securely connected and unique. + +Account will be independent from the chain where it is used. + +#### Private key + +Private key is a unique generated key which represents ownership of an account. It is known only to the owner, and the owner's responsibility is to store it and keep it safe. Without a private key account holder can not access its funds or use blockchain functions. In case of loss of private key there is no possibility to restore the account. + +Phpcoin web services and its apps will never try to read, know, store or use private keys and any deviation from that will be considered a security threat and will be highly discouraged. + +Private key is only used to sign a message on the owner's side before sending further. + +#### Public key + +Public key is derived from private key, in an irreversible process. It is used to identify parts in blockchain transactions, to verify that they are really sent from intended account owners. Public key is free to show and share. If lost can be derived from a private key. + +#### Address + +Address is derived from the public key and is used to visually represent part in the process. It is shorter than a public key and also can be freely shown and shared. + +#### Securing account + +Each account is permanently stored in a blockchain database once a transaction is executed over them. Actually only address and public key is stored, while private key is only known to account holders. + +When the address is first time generated, it is generated with empty balance so in order to use that address for login or for mining it needs to be secured, i.e. it must execute one outgoing transaction, in order to store its public key. + +For that purpose some nodes in the network will provide faucet service which can donate some initial amount to an unsecured address by request. + +This will help users to start mining, but on the other hand prevent malicious and excessive usage of accounts. + +#### Faucet + +Faucet is a service which will provide new users to get into the network and start mining. + +Faucet will send some small amount of coin (0.01) to the entered address. In order to receive that reward address must not exist in the blockchain. + +By receiving a reward address is recorded, but is insecure (without public key) until it does not perform one outgoing transaction of arbitrary amount. That transaction can be paid back to the faucet address as an example. + +Faucet is served from a node which is configured for that purpose. Faucet obviously needs to have funds in its account in order to operate. + +As an advanced version of faucet, a new Dapp named Verifier is implemented to allow faster and more automatic verification of an account. + +### Blockchain consensys + +Phpcoin blockchain tries to solve actual issues with traditional proof of work consensus (POW). As POW is often criticized as a very energy waste, PHP Coin utilizes its own lightweight version of POW, called EPOW - Elapsed Proof Of Work. + +Basic principle of this algorithm is that valid hash is calculated for each miner in such a way that the more time elapsed from target block time it will be easier to mine the block. + +#### Mining process + +Miners generate a new block on the network. + +In order to do that, miners get work from a connected node. That work contains the latest block info. + +Then the miner goes into a loop and calculates his elapsed times from the last block. + +To prove his work, the miner hashes the base string with argon and its address. + +Based on its address it calculates hit value which is changed every second elapsed from last block time. + +With a given difficulty for the next block which is calculated from node, miner calculates the target and when its hit value becomes higher than target it mines a block. + +![Mining Process](image-002.png) + +Target is not fixed, it is calculated from difficulty and is lower as time passes from the previous block. It is centered to be optimal to mine blocks for given block time. + +After finding the hash, the miner sends it to the associated node. Node checks miner hash, and if it is validated, node adds a new block to blockchain. For that block beside mempool transactions node adds more reward transactions. + +First one is a reward to the miner who submitted a targeted hash and second is reward to the node who added a block to blockchain. Third reward goes to the node on the network which wins that block as a masternode. + +Another reward which is called stake goes to the account (address) which is the winner in that mechanism. + +Amount for rewards are calculated from actual block reward, they are depending on block height, and are defined with a global rewards scheme. + +#### Difficulty adjustment + +Based on a simple formula that calculates the last 10 blocks, difficulty is calculated and adjusted for every block. If a block is found faster than block target time, difficulty is increased by 5%, otherwise it is decreased by 5%. + +#### Transaction sign and validation + +Every transaction which transfers some value between addresses needs to be signed by the sender private key. It can be a transaction signed by a miner or transaction signed within a wallet. In both cases a transaction is signed with a private key which is held only in possession of the owner. + +#### Block sign and validation + +Block which is generated in the process is signed with the generator's private key. When such a block is received on a network of peers it is validated with the generator's public key which is stored in block information. If a signature is valid, a block is added to the blockchain. + +## Masternodes + +### Masternode concept + +Masternode is a special kind of node whose purpose is to make the network stable and provide different services. For its role will be rewarded in rounds for each block. By design and defined rewards scheme masternode rewards will be slowly increased as time passes by and hence reducing miners' role. Actions for masternode will be available in wallet and in gui wallet. + +### Create masternode + +To create a masternode user needs to create another wallet and address (masternode address). Then he needs to send collateral from his wallet to that address with a special transaction type (create masternode). Source wallet will be reduced by collateral amount which will be then locked on masternode address. Processing this transaction, a new entry will be created in the masternode table and propagated over the network. Validation of this transaction must check that the destination address is not already in the masternode list. When a new masternode is added to the list it will be first in order to receive the next reward. + +If a user wants to spend collateral he needs to remove masternode. Only allowed transfer from masternode is the amount earned over collateral. + +![Masternode](image-003.png) + +Collateral will be dynamic, i.e. its amount will depend og blockchain height. After collateral changes which are defined in the global rewards scheme, the owner must remove the masternode with old collateral and create new one with new collateral. + +### Masternode work - Proof of service + +Masternode must be running constantly in order to receive reward. It is performed in that way that masternode must create its own signature for every next block. If a signature is verified from other nodes on the network , the masternode is rewarded when it is his turn. If masternode is offline, not running, not synced or in any other way prevented from signing future blocks, it will not receive a reward. When masternode calculates its signature it propagates it to other nodes on the network. Other nodes validate signature and update entries in the masternode table. + +### Masternode rewards + +Masternodes are rewarded in rounds by principle first come first served. In each block will be calculated the masternode winner as the node which is oldest paid in the list with verified signature. In the masternode list there will be column win height i.e. block at which mn is last time paid. It is calculated from the block when the masternode receives the last reward and can be verified. Because only one masternode can be paid in one block, the win height will be unique. Reward transaction is created as a transaction to the masternode winner address which is in the masternode list active and with minimal win height and verified signature. Every other node will then verify this transaction. This transaction is created and signed by a node who adds a block ( generator or nodeminer). On other nodes reward transactions are processed, and the winner's masternode will be updated in the list with a new win height. Each masternode in each block will check his entry in the masternode list and calculate the signature for the next block. Then it will propagate his entry across the network. As an addition to the block, there is a new column masternode which will hold the masternode address of the winner in that block. Also there will be a masternode signature for verification. These columns will be also added to the block signature base. Other nodes when verifying the block will check the masternode address and signature and it will verify it. + +Base for signature is public key and current block height + +### Blacklisting masternode + +There will be no need to blacklist it, because if masternode is not working, or in any way prevented from service, it will lose its reward. As soon as it becomes operative, masternode will start receiving rewards again. + +### Delete masternode + +By deleting masternode, the user removes his masternode from the list and releases collateral. Delete of masternode will be possible only if a certain number of blocks are passed from masternode creation or after collateral change. In that way it will ensure that each masternode must run some defined time in order to prevent abuse. Users need to send a collateral amount from the masternode address to another address. This is a new type of transaction. Source masternode wallet will be then reduced by collateral. Processing this transaction will cause masternode to be deleted from the list and propagated. By verifying transactions will be checked if source address is in the masternode list and check if passed a defined number of blocks from the collateral transaction. With this action collateral will be unlocked. + +## Staking + +Beside earning coins by mining, hosting node or masternode, each account on blockchain which meets some requirements can also earn part of block reward. Staking reward is depending on block height and is defined in a global rewards scheme. + +The main principle for staking is that any address that has a minimum balance and has not made any transactions in some time (maturity) can win a stake reward. The winner in each block will be decided by highest weight which is calculated by multiplying address balance with last transaction height. + +The more the user has on balance and holds longer it will have more chance to earn reward. + +## Decentralized apps + +Because it is built on PHP technology the main purpose of each node will be to host decentralized apps. This will be the main goal and offer to blockchain developers and which give true value to the project. + +Each future app in nodes will be built through a decentralized platform. Each address can host one site with many pages. + +Node can be configured to be the source of its own decentralized apps. Then the node owner can write its apps code in its own folder and propagate it through the network. + +Address for node dapps must be verified on blockchain and then apps can be accessed on any node via url `node_ip/dapps/address` + +Source node of dapps will be anonymous. Source node need to calculate dapps hash and propagate it through the network. Then other peers will get a hash, verify it with the node address and download dapps file. + +If the requested dapps address does not exist, node will contact other peers to download it. + +## Smart contracts + +Smart contract feature will allow blockchain to execute programmable functions and store stare in the blockchain database and in that way extend blockchain usability. + +Advantage of this concept is that smart contracts are written and executed in the PHP language. + +Smart contracts are in general created and executed through blockchain transaction mechanism and this concept is mostly based on the very known Ethereum concept of smart contracts. + +### Create smart contract + +In order to create a smart contract, users need to create a new address. It will be a smart contract address, and does not need to be verified. + +Then a special type of transaction from a wallet to smart contract address will create a smart contract. + +Smart contract is written in php language using all available allowed functions. It can be built from a single file or from a folder with php files. + +However there are some rules that need to be obeyed. + +Smart Contract must have an entry file with a class which extends `SmartContractBase`. If compiled from a folder entry file name must be called `index.php` with a class which extends `SmartContractBase` class. + +For smart contracts to be read by Smart Contract Engine (SCE) relevant methods and properties in code need to be annotated. + +Smart contract file or folder is compiled to phar file. Phar file is a single php executable file packed with all related files. Phar file can be also compressed to reduce size of final smart contract code. + +There is cli utility to compile phar file from source. + +Compiled Smart contract file will be read and its content will be encoded with base64. Together with other smart contract metadata encoded content is signed with the smart contract private key and signature is saved in the transaction message. Encoded metadata is saved in a data column and then propagated through the network with standard transaction mechanism. + +For creating (deploying) smart contract defined fees need to be paid. + +Smart contract is executed in Smart Contract Engine (SME). This engine is completely separated and isolated from the underlying system. It allows only a limited set of functions to be executed and ensures security for node owners. + +By creating a smart contract class deploy method from class will be executed and the initial state will be populated. Deploy function also can transfer some value from deploy wallet and can supply parameters to deploy function. + +Smart contract state will be saved in its own table and calculated with every smart contract code execution after each transaction. + +### Execution of smart contracts + +There are three ways for interacting with smart contract: + +1. **Deploy** - function that is called only once on creation of a smart contract. It requires a transaction and deployment fee. It is called from the deployer's wallet. It can have amount to transfer and parameters to pass to function +2. **Update** - function to update code of smart contract from specific height (Not implemented yet) +3. **Exec** - function that is called and changes state of contract. It requires a transaction and execution fee. It is called from any wallet. It can have an amount to transfer and parameters to pass to the calling function. +4. **Send** - function that is called from a contract wallet and changes state of contract. It requires a transaction and execution fee. It must have an amount to send and receiver's address. Also can supply parameters to calling method +5. **View** - function that is called immediately on smart contract, reads state, makes calculations, but does not write new state. It does not require a transaction. It is called via api or cli functions. +6. **Getter** - function which immediately returns the state of smart contract. It does not require transaction and it can be called via api or cli functions + +Smart contract is executed in simple process: + +1. Read smart contract code from database +2. Read smart contract state from database for current height +3. Execute called method +4. Store new state to database for next height + +State for smart contracts is saved as property => value pairs in the database. + +There are also special constructions such as arrays/maps which are stored as `property [key] => value`. + +All values are stored in the database as strings with max length of 1000. + +Smart contracts will highly extend functionality of basic blockchain and allow many developers to easily write fully decentralized apps. As an example there are some of future apps that will be based on smart contract concept: + +* Custom tokens +* Named addresses +* Airdrops - lottery +* Delayed payments +* Decentralized exchanges + +![Smart Contracts](image-004.png) + +## Distribution + +Phpcoin will go through few phrases in its existence in the crypto world. Each phase will have its specific rules and reward scheme. + +### Genesis + +Starts and finishes with the first block on the blockchain which is hardcoded in software. Genesis block is ownership of project developers and gives initial reward for start of project. This genesis will hold an amount that was swapped from the development stage of the project and will be later distributed to users. + +### Launch + +A few days after the start of blockchain reward is fixed low in order to allow, setup, configuration and smooth operation of initial nodes on the network. This time is used for project announcement and introduction to the wider auditorium. + +### Mining phase + +Period when the main source for earning coins and contributing to the project is mining. Reward is set to the highest value and then periodically decreased until the next phase. This allows participants who early enter the whole project to earn more. + +### Masternode phase + +Phase where masternodes are introduced on the network. In that time some significant amount of coins will be in circulation and new reward protocols will be started. Nodes which lock specific amounts of coins become masternodes and will host upcoming decentralized apps, and earn reward from that. At the same time developers funds from genesis are unlocked and offered on integrated exchange for potential investors and buyers. + +In this phase reward will be reduced periodically in favor of masternodes in order to slowly squeeze out miners from workflow, focusing on providing services. + +### Deflation + +In this phase mining will not earn any reward to miners and will be completely switched to masternodes. + +Masternode reward will be periodically reduced to limit the total number of coins in supply, hence more building and securing its value on market. + +During all these phases there will be no fees for core transactions on blockchain. + +Next phase where no new coins will be created will be true usage of services where fees will be introduced and it will go to nodes who mine further blocks. + +All funds received after selling of coins will be used for further development and promotion of the project. + +## Swap + +The project was first started in the development stage, so in order to migrate to the mainnet swap process is introduced. + +In some moment during the test phase (called mainnet alpha) total supply is calculated and locked and that value defines the initial supply of PHPCoin token which was created on Waves blockchain. + +Test phase will continue running to test and try out all new features for future development, but total supply will not be changed. + +Users can still use, mine and earn coins on test until the end of that blockchain. Also the test network can still run after launch of the main net for further development, and coins will be able to swap even then. + +Future mainnet will have a different chain ID in order to prevent collision with test blockchain. It is possible to use the same account (private key and address) on both networks. + +For swap coins to token there is a special swap dapp. That app can be installed on the main node or will be implemented as a smart contract (it will be decided). Coins will be swapped to token by burning and hence reducing php coin total supply. + +The Genesis amount is burned first. It is converted to token and offered on integrated Waves exchange for minimal price. Funds collected by selling genesis will be then used for further development and future marketing and promotion. + +Users can start to trade tokens on Waves network, even before relaunch begins. It can give value to coin and the start advantage to users who are from the beginning in the project. + +At the start of the next phase, when announced, total token supply can be then swapped again for a new chain. + +Total token supply will go into new genesis and can be then swapped back to the new chain. + +For that there will also be a new swap app. + +![Swap](image-005.png) + +## The team + +Similar to the creator of bitcoin, Satoshi Nakamoto, the team behind this project will not be revealed in the beginning. + +Team members would like to stay anonymous and independent as is the true nature of blockchain and purpose of its services. + +Team behind phpcoin is a devoted and well experienced team of developers who are disappointed and scanned in many previous crypto projects and decided to start their own vision of how the crypto world should look like. + +Anyone who shares phpcoin ideas, vision and goals, is welcome to join the team. diff --git a/docs/white-paper/white-paper.pdf b/docs/white-paper/white-paper.pdf new file mode 100644 index 00000000..fe0b0f56 Binary files /dev/null and b/docs/white-paper/white-paper.pdf differ diff --git a/include/checkpoints.php b/include/checkpoints.php index 4a4b8a2b..0352172a 100644 --- a/include/checkpoints.php +++ b/include/checkpoints.php @@ -7,16 +7,254 @@ } $checkpoints = [ - 1 => "2ucwGhYszGUTZwmiT5YMsw3tn9nfhdTciaaKMMTX77Zw", - 1000 => "6seZYqyH83Us7NhX3N9TgB8jqgkFrCc9etpQsiUL3coz", - 5000 => "BLLhY8L5kr61jmwjF252qNBuBaL1Sxhz8TurgJ1MZVbR", - 10000 => "5FCNEW8bRgb6pv7YXub755ijSU2DQJ15EqSdd4USMj98", - 15000 => "CmFpsmEew6kCUgwFRPyvbYKGmiXZ4Hd1Gedoa6q98TJV", - 20000 => "3LYBUKS1UFyPnY6ne3vz8zaYAWA36p75jvJzX1LZAJCi", - 25000 => "29oP8cKUKawXvQgfV5prURUPytZt58iFqUug3cQd9ndf", - 30000 => "AyuYpXTxSimipWXw9kaXg5A3Cu9gX7VcoFh5k2HhXMTp", - 35000 => "wnV5doKG5L5do9X7ZK9ceTsEY38n4ktYgdZatpuyM1L", - 40000 => "BBC84NpVjBtcaxdSPjCzHZm1J9XZ8hdoC38kPwLouatj", - 45000 => "8fFgPVrVjWZQNWUkvWB4VUngdS9XFhVUEyZXnQFLaxuR", - 50000 => "2RmopeRpxuFp6VM64v2Czyph7sTL3MXxJcPSGLBod2up", + 1 => "2ucwGhYszGUTZwmiT5YMsw3tn9nfhdTciaaKMMTX77Zw", // 2023-04-01 12:00:00 + 1000 => "6seZYqyH83Us7NhX3N9TgB8jqgkFrCc9etpQsiUL3coz", // 2023-04-02 06:32:45 + 5000 => "BLLhY8L5kr61jmwjF252qNBuBaL1Sxhz8TurgJ1MZVbR", // 2023-04-05 08:51:32 + 10000 => "5FCNEW8bRgb6pv7YXub755ijSU2DQJ15EqSdd4USMj98", // 2023-04-09 08:02:27 + 15000 => "CmFpsmEew6kCUgwFRPyvbYKGmiXZ4Hd1Gedoa6q98TJV", // 2023-04-13 04:38:58 + 20000 => "3LYBUKS1UFyPnY6ne3vz8zaYAWA36p75jvJzX1LZAJCi", // 2023-04-17 01:17:58 + 25000 => "29oP8cKUKawXvQgfV5prURUPytZt58iFqUug3cQd9ndf", // 2023-04-21 09:51:44 + 30000 => "AyuYpXTxSimipWXw9kaXg5A3Cu9gX7VcoFh5k2HhXMTp", // 2023-04-25 06:36:46 + 35000 => "wnV5doKG5L5do9X7ZK9ceTsEY38n4ktYgdZatpuyM1L", // 2023-04-29 03:35:22 + 40000 => "BBC84NpVjBtcaxdSPjCzHZm1J9XZ8hdoC38kPwLouatj", // 2023-05-03 00:16:23 + 45000 => "8fFgPVrVjWZQNWUkvWB4VUngdS9XFhVUEyZXnQFLaxuR", // 2023-05-06 21:41:47 + 50000 => "2RmopeRpxuFp6VM64v2Czyph7sTL3MXxJcPSGLBod2up", // 2023-05-10 17:54:22 + 55000 => "HXH4ts6j4eiXUHcg7G4KRJDsMgvckxXWzuwfV5FWbFvd", // 2023-05-14 14:40:19 + 60000 => "BHoaS3rkuP5LAJEm29YRhrUvm3edYjT87hhdXSzk9ks5", // 2023-05-18 10:50:49 + 65000 => "2KErUePq6U1Y8PjMzY9bMqS1GVWJzhKZUZjLpy7d9Jo9", // 2023-05-22 07:38:45 + 70000 => "DQbeBLfmtWob728NbrSTQER85mKgReXoENEjpa82r4Tp", // 2023-05-26 04:19:49 + 75000 => "4ZQfMnbzxQwnWpaXP1qzBaBoEwHiGxEHz4GxWPCVdMNx", // 2023-05-30 01:02:00 + 80000 => "NgHvZG4FKAXsmpDJg3xKr4MMFd7QFwZfixgvBMmBwAo", // 2023-06-02 21:31:42 + 85000 => "CEbViLj1VV49uE59JHLH3bMiK8mRQrYNejUHdzmVxbqB", // 2023-06-06 18:20:47 + 90000 => "9krGDi3N5xFWCCisEfG8igN9KgitUXGKhfwkqDZUdL9u", // 2023-06-10 15:39:39 + 95000 => "8MK419JyWhw4LVDm9bKZLmNjJo7FxckdKQszr5GV2BB5", // 2023-06-14 12:17:28 + 100000 => "6HTPwZc1u61FwUiBr779LM2nbDz5PuBFWeJJ3Gj88XQH", // 2023-06-18 09:23:09 + 105000 => "FzfkJLKmFYgoACAfwGScpKwDT5xn2xQiwrTpHCjyjCtL", // 2023-06-22 12:53:07 + 110000 => "co7GUpcXfGhzXB4mA6SEnDG96aZnQvRbrTvoZjvDYK3", // 2023-06-26 10:02:22 + 115000 => "BaRmc2zGGfkkvpdiB5XFn9T1NT5gzjxUb15xhtsecguW", // 2023-06-30 06:55:45 + 120000 => "LGvaKxz7waNciZJmwvnDAbkVdD1tW9UtJUUrkNoosQ5", // 2023-07-04 03:29:19 + 125000 => "zRNyPzprDx3X6VC4VPEtBGwcJcrtdRZMPx1maKMea8g", // 2023-07-07 23:56:57 + 130000 => "8PxbARg5VXuPh3mht9GrjWG5KLofwUjThEqmE1jFFn4c", // 2023-07-11 20:36:01 + 135000 => "HGNCvLHPmjsAa6c6BKTeqBeVD8daMDkpVKcG2T9HqZmT", // 2023-07-15 17:13:59 + 140000 => "4MtFPyDkTKf9FG8oQrkYWdGUS2m1VywaqzATvkZ3v5Rb", // 2023-07-19 13:40:35 + 145000 => "DW2CXDrV7x1xseN6aLf6jqqRgGNAZrZtk8wdDBtPMUmn", // 2023-07-23 10:23:16 + 150000 => "GeLkDJiMAz2qmM1kshykXQ8GMY3uP1C7yWKnxaxNicKX", // 2023-07-27 06:48:31 + 155000 => "DRUmtpY5qLN1AGR4whFFNRG98ntuexsdNt2wE7eMK8rV", // 2023-07-31 03:30:09 + 160000 => "ANhmZm9Rxcfyx8XNrcmtW6TzVz5urSzVakHZ8WsFjqLn", // 2023-08-04 00:15:49 + 165000 => "4M5VaRNxCmN5jmLf4fT4eW93vike4aQ6daYsAqqmDsn6", // 2023-08-07 21:21:44 + 170000 => "FT9Fbj1H2NcHBNiwy3RBRmBj4X7mWfRFvhyzkQtbehyu", // 2023-08-11 17:52:02 + 175000 => "5791qWxYd9sZ4naiEN1Hugi4LUDNr7Z7ecUke9JPyJvc", // 2023-08-15 14:22:24 + 180000 => "AUEp97Miy7ydDv5UW9fdx9Mpu4ZaDjHc2UWHUeMnKBAx", // 2023-08-19 10:34:05 + 185000 => "9M4tR8Ns9h3BTL63nefqRC4p7iaRNVedBJRdFeTHvFC1", // 2023-08-23 07:56:55 + 190000 => "CsF4UAXs3QJjhGfwF72sPuRPC9fbPP8zjLNRFEaV5ARC", // 2023-08-27 04:47:29 + 195000 => "EufiD5sSvcT8snK4UfRJQQGZSTLykSaafxy52NX5YusQ", // 2023-08-31 01:55:08 + 200000 => "4DLgryKvXKFhCNhdu3MEHya683sQ38sXnfRPGa6QBE2K", // 2023-09-03 23:12:58 + 205000 => "GpEwQMBybTXo8MDUacT5HQNdfLVx2nnx5jiQiPw54D2o", // 2023-09-07 19:52:22 + 210000 => "F4SZsmwdeff6NzeVRxVM7kUnUdqqgyAyopn6jGe5gmt3", // 2023-09-11 16:20:12 + 215000 => "APn8nnKJgpYzhEx42k6w5Zmb7H8j1EfA5M9vfhdWhDqd", // 2023-09-15 13:28:51 + 220000 => "CKDvEWgBxfE6ex8XQvVEmy5vQGVTfq13ANx5cZzzALoD", // 2023-09-19 10:14:19 + 225000 => "8U3ArGTHSsX7hPYtvpYLeGtQGHxWi78LShjSC23R9N8", // 2023-09-23 06:59:46 + 230000 => "GS2NEkDe4atZdT7n5Jk8ZT16eEKLNU3VqZS7yWT3KauA", // 2023-09-27 03:31:53 + 235000 => "DKwboNHf48i7JK1skZFoYxwfsRBLbuUYm6c68ZGrcL4C", // 2023-10-01 00:05:31 + 240000 => "6ghugWoXVVkGKo5qFs5LpV5cfvLXvZXruTuwfvZA3PxC", // 2023-10-04 21:06:02 + 245000 => "6GX5cijXxymBBEFiA418uQXd42BkPZ6mVKqiv5CvL4tB", // 2023-10-08 17:23:03 + 250000 => "BfM45EWHKi3vYEjAKh4dzeoV8cud2XKbuoh59Tnfiwu3", // 2023-10-12 14:03:43 + 255000 => "3U4pvPzMEpouVUSBpfNfk5czQ6PqcSQTRj3dnmnwArDz", // 2023-10-16 10:43:28 + 260000 => "GdgKNrEGEDMwudBspHt7rCFN6c3cgfwdEAAqrSkCsBDP", // 2023-10-20 07:47:47 + 265000 => "3ySM9bkqApnTzAKcVDeZdsAxShJBmsdjQtd3XgBwe4nJ", // 2023-10-24 04:08:32 + 270000 => "4FF1dvmzkerDZXZYhJ81jqeSfan135kFDhA3s54ExT3a", // 2023-10-28 00:42:15 + 275000 => "AMx9xv4DVw9tTyDXTx96TDBZoUj4ofuPj5ZqVYqiWVCB", // 2023-10-31 21:42:56 + 280000 => "B88eRpXsDNUv73gWEA5bq3qgG8zWCfs73bSPMPFjspS9", // 2023-11-04 18:26:38 + 285000 => "FWMYy9TzbPXHFJNsZ1Q2gx59VxRJg9g5oJdJEvbYUUsA", // 2023-11-08 15:04:40 + 290000 => "BhDiLZmS1FCRarPMqq51r1xRUTpcKq77u5DWnjgJnUvP", // 2023-11-12 11:17:01 + 295000 => "DbkUHFcrNSRdxi8Ri54M3Fu9LuE8SafbMJqk7JvYAGBv", // 2023-11-16 08:13:58 + 300000 => "3uLtfFanUqVPNkauBgq1sip7xaMMEB57WkooAnsgMtgg", // 2023-11-20 04:55:25 + 305000 => "9ZRML8zq2qqPjwx7qmTC6fnDwqzMNqhD8BoPkkXqSYf3", // 2023-11-24 09:39:26 + 310000 => "AFauVk939Q9SjqRT5kLWPChTxgarbBEgTPNsjjA9ZY7H", // 2023-11-28 06:28:57 + 315000 => "GfwAMvK8pKQ6QmMb5NnZoSCRoNKz8tpAjdXNsk4k9nbT", // 2023-12-02 02:50:33 + 320000 => "ARbDeH4vePdkVfnvj8mVGMS59uBqYyvWLxRRxV4fGFqa", // 2023-12-05 23:38:32 + 325000 => "7hay1mQWakr1kbtKyHRtEXjih8V6eBdCkXVhf1UPDys2", // 2023-12-09 20:20:48 + 330000 => "Nyj5UGDc7aW6EPzWoFSkb8GHjqavLrQ1QX3kekP6RWb", // 2023-12-13 17:00:34 + 335000 => "Gb1h1C6oU2rMZGnW5ZHpHLNHh7CZtpQNZXEh3gBBEQNW", // 2023-12-17 13:50:38 + 340000 => "6K7HjivvG4vEgTZEdegQPcsXvKjXxFPERq972sn3qT6p", // 2023-12-21 10:14:40 + 345000 => "9hqUXCrivX4NCMzGZrMRnaPrFeT3dRJZbi34Vq5QixNF", // 2023-12-25 07:11:20 + 350000 => "972rymKqb6QrpGf8rasCeKxNRhZHffKanSeCwKZh9LVJ", // 2023-12-29 03:54:09 + 355000 => "6biVWUFgZrGedarHTJD8jcf2bgvvW8zrGGBgNB8CVKmg", // 2024-01-02 00:11:22 + 360000 => "EaSvzW5btMXRzxBix26xgJGDrMwe7B4NGtRCivtQDLM8", // 2024-01-05 20:32:55 + 365000 => "CNnq8WKWXqFTyFywCDf7ypTdkDTPfiAxxorhQzuDcJpA", // 2024-01-09 16:47:50 + 370000 => "DDZtWLpjwiWTxq3v3phSAGEBciaczRW3UFfX8vhegF4Z", // 2024-01-13 13:19:30 + 375000 => "6Lq2x9hGPsnetHf2bd7TvYhjYLiHfVNHzGb7bu7rJ6yh", // 2024-01-17 09:51:20 + 380000 => "6k23TJ18HULJNEt7FH9d6xvU8WiAh4jFaukbuvm732oE", // 2024-01-21 06:20:08 + 385000 => "CfAKomeo362KJtgnKSSxGEgRx77SHrqPh4R53mZsr7iz", // 2024-01-25 03:13:09 + 390000 => "CwiEMsRY9oNdi9YpgQtT1GnorBvLv1NibswyQ3WbgufP", // 2024-01-29 00:08:22 + 395000 => "FQh8ysNsxueJ2xinfchiZJaNPizof89zk16fhGzCeb5N", // 2024-02-01 20:22:49 + 400000 => "ALB3X674Vn2Ff6CjM8KV97sYsY7JoRBic8Pzw6gCG4xc", // 2024-02-05 17:35:02 + 405000 => "3b1D2SUnyJQDfx5FqXPxiMs6aLhBCyNqNPk9nySJyHbY", // 2024-02-09 15:44:48 + 410000 => "4aXnVqs5H4d7RAj8dcjgrBmLiz3TSEyAC3h23Mp7HYjb", // 2024-02-13 12:31:49 + 415000 => "agUaqMpBcdV8hvEiAWREqxGdVJYY8jAS9TjHacnamPh", // 2024-02-17 08:52:08 + 420000 => "4Rn4ft5spMzJzYadMMwNFZXt3wz6bpEaTZgrs6EfL8Zj", // 2024-02-21 05:52:23 + 425000 => "7BhLvDTjyTXsDZn6WrLTFNm7daHSSRZ766ENtoaJ74hT", // 2024-02-25 02:24:15 + 430000 => "HumXU8fAiMBs1FFPg5RoHQUJZVG6ZzM5nsCR5JgCjpvv", // 2024-02-28 23:22:52 + 435000 => "DjdTPba5Dx6mLSZQKxd4o7FV2jJCYEthn5MUpZGauB6a", // 2024-03-03 20:27:03 + 440000 => "ALtzy3dz2A4VwmrC2G5zqMFvmH44pi3RUSNAsA3fgye3", // 2024-03-07 17:22:32 + 445000 => "BUP44kokcdKbpa6W9PYFyM21yKR3nZeSPvCkQJ81hk52", // 2024-03-11 14:19:20 + 450000 => "4irSmj8ZWBmxkLiCP8RhcjwmdAUfsJWXyzsEH9FYYyHH", // 2024-03-15 10:39:42 + 455000 => "7jtuqK6zhGSxyBpdxK9F5dEwQQN3ACvyMbHwt2gB75y6", // 2024-03-19 07:17:21 + 460000 => "Fox1HzTNoBJp9kK6GtQtJNUqucyEgYtpC473uPJB5RfQ", // 2024-03-23 03:39:15 + 465000 => "oELF6Kmp6szwum6FraYLPeLwArXRxq3vPWm8JVZjvxk", // 2024-03-27 00:02:32 + 470000 => "JDsLPPwNQBSM7fjMgyf3NzhuzN6vsbfYcT9AvGgv2LB6", // 2024-03-30 20:33:18 + 475000 => "6ty7t8xciD8VCcfk3ffESfDpeQ3RwC6BwEdyVTmuzPGB", // 2024-04-03 17:23:50 + 480000 => "79k6mGhZXwDy3yrnQufcf2K55U9cieLP9HmaGmeD5yKj", // 2024-04-07 14:07:45 + 485000 => "8MQXoNMkECtsTFuzdWMLmyUkWNVvvA5Q6yjFDQjYiEeN", // 2024-04-11 11:10:27 + 490000 => "GVEeZ16FRp9RWaAzeujyvcBbVzjcPD4waL6p4X6pTXwm", // 2024-04-15 07:55:42 + 495000 => "Gz1YfqfRjgWLd7yazmzn4tyBo4JH2SAPYHzLWeGFRdb8", // 2024-04-19 08:41:41 + 500000 => "3vVHbtw9Us6KZFEYR6oGNfE75QqCvdj5SvGACLk15RyQ", // 2024-04-23 05:51:38 + 505000 => "8viVhdLX3mhawrToPQfjE6GqSaKeuHmwEGDHtE66DDwe", // 2024-04-27 03:09:03 + 510000 => "5P9V3Zzr7euU9PbBgvHTEKhvp31ssLEeAWsUsNBRsE1W", // 2024-05-01 00:44:12 + 515000 => "CmaM6t7AA6WcEDJXtSs1C95PeeJy3FDBrE8cATtAeDGW", // 2024-05-04 21:38:51 + 520000 => "HGGchZdb1SPcqsft4nd19VRBaqtxabDnGB7k6NVvXPWb", // 2024-05-08 19:02:49 + 525000 => "HebNxkk64Z9ohdTNUj7k6FByZdricGbKHokzZLtXXPjf", // 2024-05-12 16:37:38 + 530000 => "HaDmHV13nEn5G13hjGN59hvuuZgvZ3Ws3B6bGYkG7fif", // 2024-05-16 13:43:28 + 535000 => "AUyFFULhqPYLvXYSY7t2mUZJ1CEzdpW9Z2snUxZmiV21", // 2024-05-20 10:35:47 + 540000 => "CsMCam3t6pPuZCGp7Rit2cmhV3t5AJym73t8tj7hD5vq", // 2024-05-24 07:59:04 + 545000 => "GAwvfPiFEVdHMS523TiZhvMcwwcZeenoBPDG5Hp11ith", // 2024-05-28 04:51:58 + 550000 => "A8igwUcabWpuxwDKfMLdC1xiFFcyofdAPL9yowozNJT2", // 2024-06-01 01:39:32 + 555000 => "DEPagpp1BawhAr2FhUE9YD8VCU2amNaKwNUv3qpUJcUn", // 2024-06-04 21:59:01 + 560000 => "BEiTrucHxH4LdU1SvHLp9GMy1P4QwUBVqf1iGFJnJRdd", // 2024-06-08 18:45:11 + 565000 => "FbXK5Q8srFGS9DFd8cT3yMvCBAJqB8qqP5W5JxvNHXE4", // 2024-06-12 15:25:43 + 570000 => "DaGBbwBhwEVjFmH3NjFdNwfsw4gdshgJfGMRcfr5AH2P", // 2024-06-16 12:04:17 + 575000 => "3eAWYfEopXoZnFC113kAPB8wuuBad1F7cvv7ewnBKZxL", // 2024-06-20 08:44:55 + 580000 => "9KMxWsSAPk2xzRVfZQumRfSwbTHjvbG1mcrRiMgFEHR5", // 2024-06-24 05:11:22 + 585000 => "9vCjXS1Fr7eXakLtfyYQXHfzAnCa42KnMDQkbnxsgcsd", // 2024-06-28 00:58:31 + 590000 => "5s7Ls8FbDUU5QUgGz24qKKrUsyS2e5pZ9A1NioVdFJRJ", // 2024-07-01 22:30:52 + 595000 => "65rMpGTP2s38SnbAFCz9zBXFL24XZqcTQwgToRGs9rZy", // 2024-07-05 19:16:12 + 600000 => "EYQFah3PKnRsk9BJJkkmSSHiBwAEDfue4tabzYSVVFWJ", // 2024-07-09 15:34:50 + 605000 => "P3fV4hDD2AGX2zerR56iNtoH5QXiFN9kZFFHfRFMbGs", // 2024-07-13 12:04:18 + 610000 => "511Y4Nym2wkZKwohgvpRT1AQk6yHu8QBFDkzNAsYmZEs", // 2024-07-17 08:12:07 + 615000 => "9vhQ6efWFehEBSmxsTdnxpMjKBQCorXtDSjfAK3K9Z4g", // 2024-07-21 04:45:22 + 620000 => "GQqVU8aTjmmjWW3Thepo6oRY5aspSTTYHQjwYWPYPfTv", // 2024-07-25 01:40:37 + 625000 => "6Vho2GcK89SDyjuXAjuA1JTWpNdJRkmzAz7CBHLEFjBx", // 2024-07-29 05:43:36 + 630000 => "7E6KkjZwYCPVsGNpnwN8sHCBn5aJFi6geSF1Bt42JiuP", // 2024-08-02 02:08:35 + 635000 => "3SVhx2YZAHZBvuJbwMQNoeysejykarxVhhkoezoNnrZn", // 2024-08-05 22:46:11 + 640000 => "7z7XMjEksvxnzgEECPfAv28FGno4R6Y3VqtFYgYWuRUm", // 2024-08-09 19:02:26 + 645000 => "4waPt22QUgLkKRFHhxorHN7vxYHy9h3fFMcjB3YJuBUj", // 2024-08-13 17:28:11 + 650000 => "CGLvdFCF6KFbEBTLwzgqBinKqik2Fv4t45UHTKBsSWAm", // 2024-08-17 15:11:30 + 655000 => "DLEoeqzR6iLnBKv5o4sxQ9YM27faULYigcoqK43nJHyx", // 2024-08-21 11:59:08 + 660000 => "B9xkadHFoaYo7suoUzRAerUPBX12zZDynek2rJChmiC4", // 2024-08-25 08:12:50 + 665000 => "4hc8prVQEZEqrwSgjNPkYTb3aSYZ11EJHpRDLUr6BvNQ", // 2024-08-29 04:34:56 + 670000 => "2NZNfZyJADfXhRkckQ65T73mAfuNSiWMpn4pXby1qk6z", // 2024-09-02 01:07:59 + 675000 => "AQKr5ZvYqu9GsYefJLKwfnrf6RcYA4YfEpALBnymQxmg", // 2024-09-05 21:41:51 + 680000 => "HLRUNdykRX34HzRfQj92HjSwvy5ek7wjM442BK7STdfA", // 2024-09-09 17:51:08 + 685000 => "8HDbhgB3oq8iUVD8YvbhTpyTHLQqyEjLyqgmNuSwQHKC", // 2024-09-13 14:41:22 + 690000 => "HU9GESw6NNUWnSsZ1rktNzJWE5AyAcxbvcqf4d5pf2Ev", // 2024-09-17 11:09:57 + 695000 => "Fjmqsy8me1Fdrx8G5xtu7EHQh3ha2PVusT2myiaFDgn9", // 2024-09-21 07:45:24 + 700000 => "2PFJW3iUdHTSMYD45geCs39XChvzAA1pTkf52A5mAaRo", // 2024-09-25 04:47:05 + 705000 => "5ZExFWZw2wXvV8AGnNzGBDeYAUmmMoWzVHG4dXnfadnP", // 2024-09-29 01:11:19 + 710000 => "A1MA62eN86PM1aRezbWP78aivxtydQPjHkgwCvcs26Jb", // 2024-10-02 21:56:10 + 715000 => "3vLcdWpWE1Zz2xVUuGW8XrYwRvCbFNKwf9K8c8RiQfr4", // 2024-10-06 18:32:15 + 720000 => "yXwDNA8SpRmfAwqD2afUk1Ctw1io6MXBKTTcJAtKXaM", // 2024-10-10 15:31:49 + 725000 => "96dDq9y1STC3qFQpLMVDLWQZaBDR85kBEh8VJkQGxhhA", // 2024-10-14 11:57:59 + 730000 => "Fo2pWjacRda2HsqXEbCFGPzbKNGkGYgNqGnm51Q4f5RQ", // 2024-10-18 08:25:07 + 735000 => "86sNYLwURbY5tF3dykg4p8cfcZQmTPNzM9dYH2zZTHdj", // 2024-10-22 04:39:37 + 740000 => "EpVBvTdJkMdbEgbHqnV7T5vVD7UJPuCWQatSwf4Uouuv", // 2024-10-26 00:54:18 + 745000 => "9VzUDCBp6UuVoYiuHciZwwJisyAbrdXwWt13Hgx7UhWL", // 2024-10-29 20:55:20 + 750000 => "97y3wB4hcUzZQCwDxFgWw2jdZY2Spk25eXhQjBsJpbpt", // 2024-11-02 17:21:25 + 755000 => "7MxRvN6o82GypCntw13JnZKHLXCRSDbiJcUEY5qEGKvo", // 2024-11-06 13:44:06 + 760000 => "6JD1oo7kksx4LjdzYx8kEZihTXF1TbtsoXW3vy222QX4", // 2024-11-10 10:00:25 + 765000 => "5b1Fzt5DXE2Kasb8kX7KmCfkLogwDu8bHjjXfi5s2A5f", // 2024-11-14 06:36:29 + 770000 => "AGczsoQ3benuUh15C92PcmUidgrHKyWWD1RC7fTBwn8h", // 2024-11-18 03:34:23 + 775000 => "2PaNHJJpb3wXT1otxcbRyvBbQVo8WbUiCWJkzQgJNC2c", // 2024-11-22 00:00:57 + 780000 => "EMHwUyRBDoNRYRbcznJbJ6SYhni5vzW1SJ2uKgEuQJo7", // 2024-11-25 20:37:43 + 785000 => "8tq7CzqciKcY5UjAzeN9ibgwKYDVysnfBLj3tApmCgWZ", // 2024-11-29 17:15:06 + 790000 => "E4FaUDuUtTo9j7oo8zGQuHCJBzgasZyzGXKRbekCjovf", // 2024-12-03 13:37:26 + 795000 => "D8djZWfxWkBKu8zkvXUayBXBJTGedUpaqesUMYZbRLV6", // 2024-12-07 09:52:22 + 800000 => "9VWSDBKPJeH1Wp7SsxGadi5vPBkJcTq3EQopo9wN77ER", // 2024-12-11 06:19:03 + 805000 => "F4Yg9ypBgCJ4d1Vgz3m4A2mQhzLyt2ReD62xr2zy3MJi", // 2024-12-15 02:40:48 + 810000 => "HiHujKeQSjNCyiZv1PzZTfDFdgWVHYq6wp62JgvB7hap", // 2024-12-18 22:40:21 + 815000 => "6kuAxR773268NcTZawBWQKyELyiEDDxsvkuKbSy4vjuk", // 2024-12-22 18:57:06 + 820000 => "2NMHcW9DjUVedxckf1bspBTkxhu6S6z6VNRAfQM1kW7R", // 2024-12-26 15:42:26 + 825000 => "5gHGADNFmYaLcACcRsCZ1YGvtcZKLRkjo5Q82GGA5uwL", // 2024-12-30 12:17:26 + 830000 => "6ococ6kNtrd8huhQ2TiCsswLD6T8CpEx7D8rz46xMZRY", // 2025-01-03 08:45:37 + 835000 => "9jWHmrQXZJwsq5g44mGBmNq3JYfArDt4m8gbJ1czpfUL", // 2025-01-07 04:54:00 + 840000 => "EkMGQGUnEcyVjEBJAwfGXF9tQcs9pM9pw2NvBZwSk2Ux", // 2025-01-11 01:39:08 + 845000 => "4YhbYPJaR5mRvdzEqTXuCX5sTKcQZz1hR5PCZ76tnzEA", // 2025-01-14 21:44:34 + 850000 => "9iTmUj4epSBNoTsqjac15t6CKdWBPLzBBhC34AWDYuHp", // 2025-01-18 18:04:40 + 855000 => "9ccmphxedz8zP2DDt5n6Y2UZg9HyVfwg1kddfEbnrz3X", // 2025-01-22 14:44:45 + 860000 => "9bKFU8Gdxv1hgz2r8cg3FHhQugjm3t2n2raTpnz1DZet", // 2025-01-26 10:59:38 + 865000 => "ngGDumKcrA1qoPoayr65L1pMRJCqrz7nYgodKRzPqqL", // 2025-01-30 07:00:00 + 870000 => "Bab9TMw3jRrm8QaTicXnRjYNwpzyMcMJ8bCXTzzFYc1W", // 2025-02-03 03:45:14 + 875000 => "AHgZQeKASLdagpt2UwqwNSbBfckAyyAPUEXUfJbs86ZZ", // 2025-02-07 00:28:06 + 880000 => "75FKbxy9SYU3CaWArovbPkAoKKrSxZZvnK1KABVC8cTa", // 2025-02-10 20:22:48 + 885000 => "Gdchg4ah2WwEGQpogoGDesiNuvjqaEpuKGhE7kNKw182", // 2025-02-14 16:56:57 + 890000 => "4MnM6v3TCdLMPTxmcmRt6Shn7MB6bi4EdeT6jQRdZmBT", // 2025-02-18 14:00:02 + 895000 => "ARtx4uWsR3X4TB8sXtgW8KiHakm3gaB5YY96yEF2qVBs", // 2025-02-22 10:42:30 + 900000 => "6xB4acpP9wH67UbQPetLMJuwie9GPaN74W2MMdEPqy2e", // 2025-02-26 07:31:50 + 905000 => "AcPAbz3Th1x5p4BmW2aKcP2Hi3S7tYvAgtyxrSRdu3Ta", // 2025-03-02 04:39:32 + 910000 => "4FQkMuJ134cFjsgLXM5ZHX6FRhhced4hB1QQB1UNcnKD", // 2025-03-06 01:12:51 + 915000 => "6613yadgpzt8uXPze7VTojdRNr48hHMFcapY5GUHyGN3", // 2025-03-09 21:23:11 + 920000 => "9roX4cv7yhtmbQHdCgnLzWsnu1pPYMzXH3zDeH4QLsiK", // 2025-03-13 17:53:32 + 925000 => "6xcQaSr2WkezjEHmiFuBRLcScFgQ623NTFmzaRKaJLRy", // 2025-03-17 14:09:38 + 930000 => "6qT9H76bUyxH1yX4xQqNkwqxzME42PBMk7KmD1Tjyt97", // 2025-03-21 10:21:37 + 935000 => "FPLnvt2aFfZc7rHDEQKxKPhshv4u4EChA4Dzi8UzXXc9", // 2025-03-25 06:58:47 + 940000 => "C9ZgWoTbaUo9RfxcRSB3WzABL5Z2X62r6raQba6oZkTW", // 2025-03-29 03:28:49 + 945000 => "8djUy2s3ErqKoaAZ8tEqYBjTuG6VGwmL4CcPj1AYWhnE", // 2025-04-02 00:05:26 + 950000 => "HyJvaVMGvqYDdqZEqW4Y6Bra8jx9ADwY2MSy7y4ND4s2", // 2025-04-05 20:13:52 + 955000 => "ALxZpH1PYWskBkk5ZH4DveEF9xA8n5cVBV6JQ9vmKj74", // 2025-04-09 17:02:18 + 960000 => "EscLk32eamDdueoS19WroJZ7NP9fJNeZ7MTZHVBavbJJ", // 2025-04-13 13:35:32 + 965000 => "GAcupX77Bh82YbXqQZi2PSd2Yj8rrSekoriGVs3ZJrWa", // 2025-04-17 10:03:52 + 970000 => "ASrHRYyLoqVHxZxvR87gyPM65czPpAcySdWB3bgBdyGp", // 2025-04-21 06:40:39 + 975000 => "7pggPX6w1AmwVd7L6cLXcuyaTQNno8REJRijaBP58bzE", // 2025-04-25 03:31:58 + 980000 => "C16rzAjghYuQ3TWnmGSFzyJDodC3QWPteoy6BQ3Ea1r1", // 2025-04-28 23:58:41 + 985000 => "6eZgYP8LDPzsoUi7xR7q2LBjw3DKGgNo2YmVqcxJ9oPH", // 2025-05-02 20:53:28 + 990000 => "9AB2Mk9NoARaWsvWWUhMMcUHEPpoLXoXxDNcobm7mfCD", // 2025-05-06 17:05:00 + 995000 => "SGDLZEWGgDP8Ru8gk4ATmdXujo2mHbApmjqaMwkCPhc", // 2025-05-10 13:46:53 + 1000000 => "wPCYpmpbQGoigsWq32kEaJhbDCM6h4bKLJDpdzncGvb", // 2025-05-14 10:25:40 + 1005000 => "8T3TP2aLj2MqBpHfSTFUpaFJpsdSq5xZY5QXSLMSFCy6", // 2025-05-18 06:29:26 + 1010000 => "9GW7yS42dsv6Bid8zhjZeSGpMBLqLpxdg5MjEtyznVLf", // 2025-05-22 03:29:56 + 1015000 => "9dZreCGDwFdas3KC4Aw69NniuYBGGnyDH9SjauALpd4m", // 2025-05-26 00:12:06 + 1020000 => "DHGa1XSFT8gRhnYiwa3bs3SoWhg94PmcPP15RXnLNm4v", // 2025-05-29 20:47:59 + 1025000 => "GPPHqM9KDHMJH3fCjQkUDWU3dn77uxbYS95upGPBBC7N", // 2025-06-02 17:04:29 + 1030000 => "7LokzmGWVVtddiwLNTeiw1d7uxawV8kAdk89GVk8woDs", // 2025-06-06 13:58:10 + 1035000 => "5JWi4VMZ6QojLjeN2iAaFseKXqSUGCm7oYYuycfbPLUx", // 2025-06-10 10:46:11 + 1040000 => "9gNHs4Z9SSXh97HF58cGMszKirNZSQFJNNkTvf32qcbz", // 2025-06-14 06:57:19 + 1045000 => "7vpTD4WbVqzH9RQyKnid7apMKqM3riMqGZmsP6vF4HjU", // 2025-06-18 03:41:38 + 1050000 => "9y8MrKfRuuqRQSNCwFANyxTB6gE1KgZ8mXf7rX5iSUs7", // 2025-06-22 00:20:25 + 1055000 => "7EVcno2ham49gi6oPjNenWeXJrhtJkfZgwAwe8daN4Bx", // 2025-06-25 20:28:29 + 1060000 => "9BQrDUPcyZqah1fKrrToAAxs262Qmjy373gSktHPWKSY", // 2025-06-29 16:55:22 + 1065000 => "DEAmD7PfjJTLaEEuX6CZEBw19XT3YoJD51VBTg3SnqQQ", // 2025-07-03 13:31:12 + 1070000 => "7Vee5fBCeu7WjU8qH4hbFKozytuVEj3QGu87ADupxRUt", // 2025-07-07 10:18:45 + 1075000 => "Ff2FtySDyASqvsCycJ2EPFwaakZuHmfRyxrt1sPQ9FTN", // 2025-07-11 06:27:01 + 1080000 => "4eNxiciWcvY9S8GfqibPGDCmdqYXzjB9ouok36kxZWGw", // 2025-07-15 03:03:24 + 1085000 => "9L3CciPdTd7pibwqgqvkrrqZXPFB5ehaK5HndBuTUMN7", // 2025-07-18 23:36:22 + 1090000 => "3gWmKFvbDNokMYQiSw7woY96LS64FmYxKWdcrcY9NzN3", // 2025-07-22 20:33:05 + 1095000 => "Ciop2JDnhEgcVrfs93k1sAToyCjFTBjt1Qfyn6pkrUqN", // 2025-07-26 16:58:50 + 1100000 => "k2a1NFwTZwWVZwEFg2qg9ZypZRuqfLLE3JwMPSKiDxW", // 2025-07-30 20:58:57 + 1105000 => "EdUoV2LeZJpZqs7FAg7fYBL8PSLm7vhLxZB4M1F7YaJT", // 2025-08-03 17:28:57 + 1110000 => "DKPaHmGP2rPgDZwfnaVHvdP73o9A31Cq2TEHHYjKy2ZC", // 2025-08-07 13:45:16 + 1115000 => "5FktenpNSMQJbHeK9JXejHBGnJMp4ZynwbK5EDFBEzab", // 2025-08-11 10:19:31 + 1120000 => "2FA8Dd3pwHsaGhjSgshEytYxB3dp14Z1sL3eRTbV2DvJ", // 2025-08-15 06:40:41 + 1125000 => "5BqnAgiDxkqZu72C8KNzivMM89UZAdQMqp6jaqEgf1Xd", // 2025-08-19 03:17:25 + 1130000 => "54KTbyPFfK48s7N2UkChdgvZmeAQQkjz1BhFXRLcdD2R", // 2025-08-22 23:38:30 + 1135000 => "Byrk7h3Da2ATtLx1HkK3MsBioYWi1GFY1pP3jNc9orWK", // 2025-08-26 20:11:21 + 1140000 => "GMfUSodLy3xinREHG7XgYQUdmY7Vchzrngiwwv8c4gTA", // 2025-08-30 16:54:23 + 1145000 => "HucYsXkZnowZBGY5gDFosV6KxjPQtqqt921bKYDWFJMC", // 2025-09-03 13:35:12 + 1150000 => "AoMek37aDiGMfuvX49CRBFzE1UkKfZFJvCHZ6GUsYsCf", // 2025-09-07 10:12:24 + 1155000 => "3ytuEwEZjjQyaG1mjwTWsHwhfzQpUtyvKsPaLyaphWNp", // 2025-09-11 07:01:46 + 1160000 => "2tXySoZgmm4hp4U8BsftoqYYkGwr1o3RMdjor1nKmT47", // 2025-09-15 03:17:24 + 1165000 => "4zxmQxpdTxQ3yAE78qMKweY3ef9ksd1GnAmCXKXokgzZ", // 2025-09-18 23:35:02 + 1170000 => "GE3Wqnf7XzKXj35d9zFW8ZfhTFeKvH4RQkzKQKpPaycG", // 2025-09-22 20:20:24 + 1175000 => "4rGeqnud7hs3MdcQWv9iYnHdJt2G6SQvvn9kh6GtwCPL", // 2025-09-26 16:54:27 + 1180000 => "CA1Lmxr77GXzu6VM9mgatfosanN1yADDBBtrijGeCvse", // 2025-09-30 13:34:42 + 1185000 => "5ujJpqgfEHRffNimk8Fyc2aarJkNWEnyuHMiBbFAx3Pz", // 2025-10-04 09:42:44 + 1190000 => "D12fqZqzf2fjKgosbNr2k7ZgnL7oHytPavEVaipqnBCZ", // 2025-10-08 06:42:17 + 1195000 => "EcX83v4B1MM4Ry533ZE5KFupxjzXWP1Ah51bzofkzKbH", // 2025-10-12 03:33:45 + 1200000 => "7cx13d5ciQGCGmcq6pLSMEGvXpC7njxGsSCbRoMNAg13", // 2025-10-16 00:09:32 + 1205000 => "FBgPCfj98EndJa6dyHvA9rccoG9m5t3Q5Jk3eV7gN3Zv", // 2025-10-19 20:40:56 + 1210000 => "KeSHZfka5Sq19bcbTadTs3Hw8AFYzyJrexdg3fy85B1", // 2025-10-23 17:23:30 + 1215000 => "2KMg4H12ahENAYpsvHN7yruWFGQiN4Y1S43LAorBv5CN", // 2025-10-27 13:38:32 + 1220000 => "CcqmZEYoqZxMg7Q2HpEqaaZHJu7WM1J2F8uwQMEz1S19", // 2025-10-31 09:59:03 + 1225000 => "7yQtC3bYDaPekSt32s27DRdK69QXLA5ML4MrjAJuf4pg", // 2025-11-04 06:44:17 + 1230000 => "hBPbFYWduzUDWmxA6ySJho89KUaZn3jnDzLEFKSJARi", // 2025-11-08 03:24:40 + 1235000 => "DHSMSkEguPPiEyEf3iUf8iiTm4JcZkGbFcnpreo75195", // 2025-11-12 00:01:34 + 1240000 => "5Ce4DNzZAuAcFR1ayiPcvWgomcsX4g3j3KsvzwYVwh98", // 2025-11-15 21:07:57 ]; diff --git a/include/class/CommonSessionHandler.php b/include/class/CommonSessionHandler.php index abd16b3c..fe3a89a2 100644 --- a/include/class/CommonSessionHandler.php +++ b/include/class/CommonSessionHandler.php @@ -43,12 +43,20 @@ public function open($path, $name) return(true); } + /** + * Reads session data from a file. + * + * @param string $id The session ID. + * @return string The session data, or an empty string if the session does not exist or on failure. + */ #[\ReturnTypeWillChange] - public function read($id) + public function read(string $id): string { - $sess_file = $this->path."/sess_$id"; - if(file_exists($sess_file)) $out=@file_get_contents($sess_file); - return (string) $out; + $safeId = preg_replace('/[^a-zA-Z0-9,-]/', '', $id); // Sanitize the session ID to prevent path traversal attacks. + $sessionFilePath = $this->path . '/sess_' . $safeId; // Construct the full path to the session file. + $sessionData = file_get_contents($sessionFilePath); // returns the data as a string or FALSE on failure + + return ($sessionData !== false) ? $sessionData : ''; // Return the data if successful, or '' otherwise. } #[\ReturnTypeWillChange] diff --git a/include/class/NodeSync.php b/include/class/NodeSync.php index 002dbf5d..6849fca6 100644 --- a/include/class/NodeSync.php +++ b/include/class/NodeSync.php @@ -912,7 +912,7 @@ static function compareCheckPoints() { $block = Block::get($height); if(!empty($block)) { $block_ok = $block['id'] == $block_id; - _log("Compare checkpoint $height - $block_id block_ok=$block_ok", 2); + _log("Checkpoint validation at height=$height. Expected=$block_id, Actual=".$block['id'].", Match=".($block_ok ? 'Yes' : 'NO'), 2); if(!$block_ok) { $invalid_height = $height; break; diff --git a/include/init.inc.php b/include/init.inc.php index a0bc7aed..0ce2713d 100755 --- a/include/init.inc.php +++ b/include/init.inc.php @@ -133,7 +133,7 @@ $block = Block::get(1); if($block) { if($block['id']!=$checkpoints[1]) { - api_err("Invalid chain. Please check config files"); + api_err("Invalid genesis block. DB block id ".$block['id']." does not match checkpoint id ".$checkpoints[1]); } } diff --git a/web/apps/docs/Parsedown.php b/web/apps/docs/Parsedown.php new file mode 100644 index 00000000..1b9d6d5b --- /dev/null +++ b/web/apps/docs/Parsedown.php @@ -0,0 +1,1712 @@ +DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + $markup = $this->lines($lines); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + protected $safeMode; + + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ); + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = true; + } + + continue; + } + + if (strpos($line, "\t") !== false) + { + $parts = explode("\t", $line); + + $line = $parts[0]; + + unset($parts[0]); + + foreach ($parts as $part) + { + $shortage = 4 - mb_strlen($line, 'utf-8') % 4; + + $line .= str_repeat(' ', $shortage); + $line .= $part; + } + } + + $indent = 0; + + while (isset($line[$indent]) and $line[$indent] === ' ') + { + $indent ++; + } + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + $Blocks []= $CurrentBlock; + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) + { + $CurrentBlock['element']['text'] .= "\n".$text; + } + else + { + $Blocks []= $CurrentBlock; + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + + # ~ + + $Blocks []= $CurrentBlock; + + unset($Blocks[0]); + + # ~ + + $markup = ''; + + foreach ($Blocks as $Block) + { + if (isset($Block['hidden'])) + { + continue; + } + + $markup .= "\n"; + $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + } + + $markup .= "\n"; + + # ~ + + return $markup; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block'.$Type.'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block'.$Type.'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') + { + $Block = array( + 'markup' => $Line['body'], + ); + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['markup'] .= "\n" . $Line['body']; + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) + { + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if (isset($matches[1])) + { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r")); + + $class = 'language-'.$language; + + $Element['attributes'] = array( + 'class' => $class, + ); + } + + $Block = array( + 'char' => $Line['text'][0], + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ), + ); + + return $Block; + } + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) + { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n".$Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) + { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + { + $level ++; + } + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + + # + # List + + protected function blockList($Line) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => array( + 'name' => $name, + 'handler' => 'elements', + ), + ); + + if($name === 'ol') + { + $listStart = stristr($matches[0], '.', true); + + if($listStart !== '1') + { + $Block['element']['attributes'] = array('start' => $listStart); + } + } + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[2], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['li']['text'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $text, + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + return $Block; + } + + if ($Line['indent'] > 0) + { + $Block['li']['text'] []= ''; + + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + unset($Block['interrupted']); + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['text'] as &$li) + { + if (end($li['text']) !== '') + { + $li['text'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array) $matches[1], + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text'] []= ''; + + unset($Block['interrupted']); + } + + $Block['element']['text'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['text'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) + { + $Block = array( + 'element' => array( + 'name' => 'hr' + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = array( + 'name' => $matches[1], + 'depth' => 0, + 'markup' => $Line['text'], + ); + + $length = strlen($matches[0]); + + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + $Block['closed'] = true; + + $Block['void'] = true; + } + } + else + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + return; + } + + if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) + { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open + { + $Block['depth'] ++; + } + + if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close + { + if ($Block['depth'] > 0) + { + $Block['depth'] --; + } + else + { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) + { + $Block['markup'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['markup'] .= "\n".$Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => null, + ); + + if (isset($matches[3])) + { + $Data['title'] = $matches[3]; + } + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'hidden' => true, + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') + { + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + continue; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => 'text-align: '.$alignment.';', + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); + + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); + + return $Block; + } + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); + + foreach ($matches[0] as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: '.$Block['alignments'][$index].';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ); + + $Block['element']['text'][1]['text'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + $Block = array( + 'element' => array( + 'name' => 'p', + 'text' => $Line['text'], + 'handler' => 'line', + ), + ); + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '"' => array('SpecialCharacter'), + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), + '>' => array('SpecialCharacter'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!"*_&[:<>`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables=array()) + { + $markup = ''; + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = strpos($text, $marker); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + # check to see if the current inline type is nestable in the current context + + if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) + { + continue; + } + + $Inline = $this->{'inline'.$inlineType}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + foreach ($nonNestables as $non_nestable) + { + $Inline['element']['nonNestables'][] = $non_nestable; + } + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $markup .= $this->unmarkedText($unmarkedText); + + # compile the inline + $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $markup .= $this->unmarkedText($unmarkedText); + + $text = substr($text, $markerPosition + 1); + } + + $markup .= $this->unmarkedText($text); + + return $markup; + } + + # + # ~ + # + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = 'mailto:' . $url; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'markup' => $Excerpt['text'][1], + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], + ), + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'nonNestables' => array('Url', 'Link'), + 'text' => null, + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['text'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['text']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + + $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); + + if (isset($SpecialCharacter[$Excerpt['text'][0]])) + { + return array( + 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', + 'extent' => 1, + ); + } + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + { + $url = $matches[0][0]; + + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + if ($this->breaksEnabled) + { + $text = preg_replace('/[ ]*\n/', "
\n", $text); + } + else + { + $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); + $text = str_replace(" \n", "\n", $text); + } + + return $text; + } + + # + # Handlers + # + + protected function element(array $Element) + { + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } + + $markup = '<'.$Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= ' '.$name.'="'.self::escape($value).'"'; + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) + { + $text = $Element['rawHtml']; + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + if (isset($text)) + { + $markup .= '>'; + + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = array(); + } + + if (isset($Element['handler'])) + { + $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); + } + elseif (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } + + $markup .= ''; + } + else + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + foreach ($Elements as $Element) + { + $markup .= "\n" . $this->element($Element); + } + + $markup .= "\n"; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

') + { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "

"); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + # + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if ( ! empty($Element['attributes'])) + { + foreach ($Element['attributes'] as $att => $val) + { + # filter out badly parsed attribute + if ( ! preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) + { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) + { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) + { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) + { + return false; + } + else + { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ); +} diff --git a/web/apps/docs/index.php b/web/apps/docs/index.php new file mode 100644 index 00000000..8c879502 --- /dev/null +++ b/web/apps/docs/index.php @@ -0,0 +1,162 @@ +DEBUG: shutdown_handler @ ' . time() . '

'; + $error = error_get_last(); + if (! empty($error)) { + print '
' . print_r($error, true) . '
'; + } + // print '



'; +} + + +class ParsedownExt extends Parsedown { + private $docPath; + private $baseDir; + private $realBaseDir; + + public function __construct($docPath, $baseDir) + { + $this->docPath = $docPath; + $this->baseDir = $baseDir; + $this->realBaseDir = realpath($this->baseDir); + } + + protected function inlineLink($Excerpt) + { + $link = parent::inlineLink($Excerpt); + $href = $link['element']['attributes']['href']; + + // Don't rewrite external links, mailto links, or anchors + if (preg_match('/^(https?:\/\/|mailto:|#)/', $href)) { + return $link; + } + + // Don't rewrite links to non-markdown files + $allowedExtensions = ['md', 'png', 'pdf']; + $extension = pathinfo($href, PATHINFO_EXTENSION); + if ($extension && !in_array(strtolower($extension), $allowedExtensions)) { + return $link; + } + + $currentDocDir = $this->baseDir . ($this->docPath ? $this->docPath . '/' : ''); + $file = $currentDocDir . $href; + + $realFile = realpath($file); + + if ($realFile === false || strpos($realFile, $this->realBaseDir) !== 0) { + // This is an invalid link, pointing outside the docs directory. + // Let's make it a dead link and style it to indicate it's broken. + $link['element']['attributes']['href'] = '#'; + if (isset($link['element']['attributes']['class'])) { + $link['element']['attributes']['class'] .= ' broken-link'; + } else { + $link['element']['attributes']['class'] = 'broken-link'; + } + $link['element']['attributes']['title'] = 'Invalid link (points outside of documentation)'; + return $link; + } + + if ($realFile == $this->realBaseDir) { + $newDoc = ''; + } else { + $newDoc = substr($realFile, strlen($this->realBaseDir) + 1); + if (is_dir($realFile)) { + $newDoc .= '/'; + } + } + + $link['element']['attributes']['href'] = "/apps/docs/index.php?doc=".$newDoc; + return $link; + } +} + +$docsDir = dirname(dirname(dirname(__DIR__))); +$baseDir = $docsDir.'/docs/'; + +if(!empty($_GET['doc'])) { + $link = $_GET['doc']; + $file = $baseDir . $link; + if (is_dir($file)) { + if (substr($link, -1) !== '/') { + $link .= '/'; + } + $file = $baseDir . $link . 'README.md'; + } +} else { + $link = ''; + $file = $baseDir . 'README.md'; +} + +// Security: Prevent path traversal +$realFile = realpath($file); +$realBaseDir = realpath($baseDir); + +if ($realFile === false || strpos($realFile, $realBaseDir) !== 0) { + define("PAGE", "Docs - Not Found"); + define("APP_NAME", "Docs"); + http_response_code(404); + require_once __DIR__. '/../common/include/top.php'; + echo "

404 Docs Not Found

"; + require_once __DIR__ . '/../common/include/bottom.php'; + exit; +} else { + $file = $realFile; +} + +$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + +if ($extension === 'png') { + header('Content-Type: image/png'); + readfile($file); + exit; +} elseif ($extension === 'pdf') { + header('Content-Type: application/pdf'); + readfile($file); + exit; +} + +$relativePath = str_replace($baseDir, '', $file); +$docPath = dirname($relativePath); +if ($docPath == ".") { + $docPath = ""; +} +$pd = new ParsedownExt($docPath, $baseDir); +$pd->setSafeMode(true); +$text = file_get_contents($file); + +define("PAGE", "Docs"); +define("APP_NAME", "Docs"); + +?> + + +text($text); ?> + +
+
+
+
+ PHPCoin Docs Viewer v +
+
+
+ + +