hash = $ahash; $this->height = $aheight; $this->name = $aname; $this->desc = $adesc; } } class blockchain { private static $url_info = 'https://blockchain.info/'; private static $offline_block = OFFLINE_PATH.'/offline_block.zip'; /* * Liste de blocks spéciaux dans l'histoire du Bitcoin * * 'GENESIS' - Premier block de la blochain * 'THE_ANSWER' - Block 42 (pour le fun) * 'HAL_FINLEY' - Hal Finley has mined this block * 'FIRST_TX' - First exchange between Satoshi and Hal Finley * 'LUCIFER' - Block 666 (pour le fun) * 'LEET' - Block 1337 (pour le fun) * 'FISRT_USD_TX' - Martti Malmi change 5000 BTC for 5,02 $ * 'PIZZA' - Block 57035 : le block du pizza day, 22 05 2010 * 'HALVING_1' - First halving, block 2100000, 28 11 2012 * 'HALVING_2' - Second halving, block 420000, 09 07 2017 * 'BIP_91_LOCK' - Block 477120 : Verouillage du BIP 91, 23/07/2017 * 'BCC' - Block 478558 : Bitcoin Cash Fork 01/08/2017 * 'SEGWIT_LOCK' - Block 479808 SEGWIT est verrouillé 09 08 2017 * 'SEGWIT' - Block 481823 SEGWIT est activé 24 08 2017 * 'HALVING_3' - Third Hhalving, block 630000, 11 05 2020 * 'DORMEUR' - Block 631058 Une adresse datant de 2009 dépense ses 50 BTC de reward * TX : cb1440c787d8a46977886405a34da89939e1b04907f567bf182ef27ce53a8d71 * 'SALVADOR' - BTC monnaie légale au Salvaldor 07 09 2021 * ____JUMP______ : Lorsque le cours atteint certains palier (à la hausse ou à la baisse) * WHALE____ : Lorsqu'une baleine remonte respirer (grosse TX) * GOLGOTH____ : Lorsque le GOLGOTH pousse fort ... (grosse TX) */ private static $special_blocks = null; // ---- // -- Des fonctions outils // -- Parce que depuis PHP.7.0.33-10, file_get_contents ne focnitonne plus en HTTPS // ---- private static function get_curl_data($url) { $ch = curl_init(); $timeout = 5; curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,$timeout); $data = curl_exec($ch); curl_close($ch); return $data; } private static function get_data($url, $with_curl = true) { if (!$with_curl) return file_get_contents($url); return self::get_curl_data($url); } // // Init special blocks array ... // public static function init() { self::$special_blocks = array (); self::$special_blocks[] = new block( '00000000000000000001f5beb60d9ca47adf18db5136b558379ed533fc9f6c85', 753335, 'ELISABETH_2_HAS_DIED' ); self::$special_blocks[] = new block( '000000000000000000065676a19a2dfba0171b592d52f5ea7db54d45bb386400', 724697, 'UKRAINE' ); self::$special_blocks[] = new block( '000000000000000000031ec514b89bdac2c4786bbcc3a1a0acd1206f82181901', 718720, 'POITIERS' ); self::$special_blocks[] = new block( '00000000000000000000e55da58b378880e797f9f3e96811d3e64a2f801eed9a', 700700, 'BAB_BRUXELLES' ); self::$special_blocks[] = new block( '00000000000000000000bfade5b2979c3d47c6dd6a1e8ddf53380e8c3d736ce0', 699382, 'SALVADOR' ); self::$special_blocks[] = new block( '0000000000000000000b7b8574bc6fd285825ec2dbcbeca149121fc05b0c828c', 666666, 'MORNING_STAR' ); self::$special_blocks[] = new block( '000000000000000000070daa5861fe1e7064ef8007825431229c6c1cab2c766f', 654364, 'WHALE20201026' ); self::$special_blocks[] = new block( '00000000000000000005c0f74e8b00c3961d6dfbf32936edeaa300015949f3c4', 632676, '2020JUMP10000' ); self::$special_blocks[] = new block( '00000000000000000000f811e171eee52157e9a95963140e62fa83610f23ea7e', 631058, 'DORMEUR' ); self::$special_blocks[] = new block( '000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d', 630000, 'HALVING_3' ); self::$special_blocks[] = new block( '0000000000000000001186079bbf9a5d945231236135af7a766bd34d814e7319', 628710, 'RIP_STEEVE' ); self::$special_blocks[] = new block( '000000000000000000099457d2aeb2b7fc8ad8adb1490814cb674dc5767ae9b9', 622453, 'COVID19' ); self::$special_blocks[] = new block( '00000000000000000001a3c68111789a6c2cc76f1209d1dae63b05460053eb2b', 619165, 'EQUILIBRE202002' ); self::$special_blocks[] = new block( '0000000000000000000f2306f08e8f34872a24dfaad3423801a91ee1626e9ea4', 618986, 'SOPHIA202002' ); self::$special_blocks[] = new block( '0000000000000000001085a869441fa2aa77f149a887af0ce59846ef51da6e4c', 616193, 'EQUILIBRE' ); self::$special_blocks[] = new block( '0000000000000000000b05f877e6e49b380f4f78b3cfb605b67439f825dba197', 613470, '2020JUMP9000' ); self::$special_blocks[] = new block( '00000000000000000009e8fb4ac719a362c1c4e3df439740069ee58e2a713258', 612149, 'DEMISSION20200110' ); self::$special_blocks[] = new block( '000000000000000000051f84a7a1d0f5b2ddaf5682cbec5f7acb2bf5fa339725', 593879, 'GOLGOTH201909' ); // 94 500 BTC, soit environ 1 milliards de dollars, 700 dollars de fees ... self::$special_blocks[] = new block( '00000000000000000014fcb29e6e3b0ead3bd2e307d7f619a935f1d5323e9013', 593468, 'WHALE201909' ); self::$special_blocks[] = new block( '0000000000000000000f9f2dadfb8f312572183272802cbfcc4ff95b4ee6777d', 545911, 'WHALE201810' ); self::$special_blocks[] = new block( '00000000000000000021e800c1e8df51b22c1588e5a624bea17e9faa34b2dc4a', 528249, 'BLOCK21E800' ); self::$special_blocks[] = new block( '0000000000000000000fe6d521a187a5523d5cef6f6c178923ff82ffe5a0f372', 506734, 'HURRICANE_1' ); self::$special_blocks[] = new block( '0000000000000000004b27f9ee7ba33d6f048f684aaeb0eea4befd80f1701126', 501726, 'NO_REWARD' ); self::$special_blocks[] = new block( '000000000000000000cbeff0b533f8e1189cf09dfbebf57a8ebe349362811b80', 481823, 'SEGWIT' ); self::$special_blocks[] = new block( '0000000000000000012e6060980c6475a9a8e62a1bf44b76c5d51f707d54522c', 479808, 'SEGWIT_LOCK' ); self::$special_blocks[] = new block( '00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148', 478559, 'BCC' ); self::$special_blocks[] = new block( '0000000000000000015411ca4b35f7b48ecab015b14de5627b647e262ba0ec40', 477120, 'BIP_91_LOCK' ); self::$special_blocks[] = new block( '000000000000000002cce816c0ab2c5c269cb081896b7dcb34b8422d6b74ffa1', 420000, 'HALVING_2' ); self::$special_blocks[] = new block( '0000000000000001bc7156dd1183c87859b326affa3a5cdd157e809537f0b284', 270953, 'WHALE201311' ); self::$special_blocks[] = new block( '000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e', 210000, 'HALVING_1' ); self::$special_blocks[] = new block( '000000000000041c718cd2fa4270ab80c917bb94caa79c84b417b7924a867a68', 196883, 'JOHN_CONWAY' ); self::$special_blocks[] = new block( '00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00', 57035, 'PIZZA' ); self::$special_blocks[] = new block( '00000000132fbe8314fc571c0be60b31ccd461c9ee85f42bde8c6d160a9dacc0', 24835, 'FIRST_USD_TX' ); self::$special_blocks[] = new block( '000000000a73e64735a2b75c97ea674950a9018da1420d01328a918c9ff9852c', 5637, 'TOPISTO' ); self::$special_blocks[] = new block( '00000000a70ba4a405c67310757606dd955cf1a3a8e5c042335d78394ea6cb67', 3654, 'DORMEUR_ORIGINE' ); self::$special_blocks[] = new block( '000000008bf44a528a09d203203a6a97c165cf53a92ecc27aed0b49b86a19564', 1337, 'LEET' ); self::$special_blocks[] = new block( '00000000fc5b3c76f27f810ee775e480ae7fd604fd196b2d8da4257fcd39f4f9', 666, 'LUCIFER' ); self::$special_blocks[] = new block( '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee', 170, 'FIRST_TX' ); self::$special_blocks[] = new block( '00000000a2886c95400fd3b263b9920af80b118b28fee5d2a162a18e4d9d8b2f', 78, 'HAL_FINLEY' ); self::$special_blocks[] = new block( '00000000314e90489514c787d615cea50003af2023796ccdd085b6bcc1fa28f5', 42, 'THE_ANSWER' ); self::$special_blocks[] = new block( '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 0, 'GENESIS' ); } // --- // --- Retourne le JSON du tableau des blocs speciaux // --- public static function getSpecialBlocksJSON() { return json_encode(self::$special_blocks); } // --- // --- Retourne le nom d'un block à partir de son hash // --- si ce n'est pas un block special, on renvoie le hash // --- public static function hash2SpecialName($block_hash) { foreach(self::$special_blocks as $value) if ($block_hash == $value->hash) return $value->name; return $block_hash; } // --- // --- Accès aux blocks spéciaux // --- On en rajoute un : le LAST block // --- En fait toute valeur ne faisant pas partie des blocks connus // --- public static function getSpecialBlock($nom_du_block) { $block_hash = ''; foreach(self::$special_blocks as $value) { if ($nom_du_block == $value->name) { $block_hash = $value->hash; break; } } if (file_exists(self::$offline_block)) $block_hash = 'offline'; if ($block_hash == '') $block_hash = self::getLastBlockHash(); return self::getBlockWithHash($block_hash); } // --- // --- Accès au dernier block en cache ... // --- public static function getLastCacheBlockHash() { // on commence par le cache d'images $myarray = glob(DATA_PATH.'/hasard/*.png'); if (isset($myarray[0])) { usort( $myarray, function( $a, $b ) { return filemtime($b) - filemtime($a); } ); return substr(basename($myarray[0],'.png'),0,strlen(self::$special_blocks[0]->hash)); } // S'il n'y a rien dans le cache d'images $filename=DATA_PATH.'/finished_block_list.txt'; if (file_exists($filename)) { $handle = fopen($filename, "r"); if ($handle) { while (($buffer = fgets($handle, 4096)) !== false) { $valeurs = explode(" ",$buffer); if ($valeurs[0] == 'LAST') return $valeurs[1]; } } } return NULL; } // --- // --- Accès au dernier block // --- public static function getLastBlockHash() { if (file_exists(self::$offline_block)) return 'offline'; $filename=self::$url_info.'/latestblock'; $message=self::get_data($filename); if ($message === FALSE) return FALSE; $the_block = json_decode($message); return $the_block->hash; } // --- // --- Accès à partir du hash // --- public static function getBlockWithHash($block_hash) { // Si on est offline, peut importe le hash passé if (!file_exists(self::$offline_block)) { if (!file_exists(DATA_PATH."/json/$block_hash.zip")) { $filename=self::$url_info.'/rawblock/'.$block_hash; $message=self::get_data($filename); if ( $message === FALSE ) return FALSE; $the_block = json_decode($message); return self::saveBlockInTmpDir($the_block); } touch(DATA_PATH."/json/$block_hash.zip"); } return self::zip2Block($block_hash); } // --- // --- Gestion du cache // --- private static function saveBlockInTmpDir($the_block) { if (file_exists(DATA_PATH.'/json/'.$the_block->hash.'.zip')) return $the_block; // Blockchain.info renvoie des nonces négatifs // Je repasse par la forme binaire pour retrouver // une valeur positive $the_block->nonce_binary_str = decbin($the_block->nonce); if (strlen($the_block->nonce_binary_str) > 32) $the_block->nonce_binary_str = substr($the_block->nonce_binary_str,-32,32); $the_block->topisto_nonce_blockchain_info = $the_block->nonce; $the_block->nonce = bindec($the_block->nonce_binary_str); if (!isset($the_block->topisto_inputs)) { $the_block->topisto_inputs = 0; $the_block->topisto_outputs = 0; $the_block->topisto_fees = 0; $the_block->topisto_reward = 0; foreach($the_block->tx as $transaction) { $tx_inputs = 0; $tx_outputs = 0; if (isset($transaction->inputs)) foreach($transaction->inputs as $input) if (isset($input->prev_out->value)) $tx_inputs += $input->prev_out->value; if (isset($transaction->out)) foreach($transaction->out as $output) if (isset($output->value)) $tx_outputs += $output->value; $tx_fees = abs($tx_inputs - $tx_outputs); $the_block->topisto_inputs += $tx_inputs; if ($tx_inputs == 0) { $the_block->topisto_reward += $tx_outputs; continue; } $the_block->topisto_outputs += $tx_outputs; $the_block->topisto_fees += $tx_fees; } // On retire les frais de la récompense $the_block->topisto_reward -= $the_block->topisto_fees; self::block2zip($the_block); } return $the_block; } private static function block2zip($the_block) { $zip = new ZipArchive(); if($zip->open(DATA_PATH.'/json/'.$the_block->hash.'.zip', ZipArchive::CREATE) === true) { $zip->addFromString($the_block->hash.'.json', json_encode($the_block)); $zip->close(); } } private static function zip2Block($the_block_hash) { $local_hash = $the_block_hash; $zipBlock = DATA_PATH."/json/$the_block_hash.zip"; if (file_exists(self::$offline_block)) { $local_hash = 'offline'; $zipBlock = self::$offline_block; } $zip = new ZipArchive; if ($zip->open($zipBlock) === TRUE) { $the_block = json_decode($zip->getFromName("$local_hash.json")); $zip->close(); return $the_block; } return NULL; } public static function DrawBlockHeaderFooter($the_block, $vImage, $hauteur, $couleur = -1) { $color_tab = [ [ [19,15,64], [255,255,255] ], [ [106,176,76], [0,0,0] ], [ [106,176,76], [255,255,255] ], [ [240,147,43], [0,0,0] ], [ [34,166,179], [0,0,0] ], [ [240,147,43], [255,255,255] ], // pour splinelineblack [ [17,61,86], [255,255,255] ], [ [17,61,86], [255,134,63] ], [ [234,220,207], [127,106,85] ], [ [48,51,107], [255,255,255] ], [ [246, 229, 141], [235,77,75] ], [ [40, 40, 40], [158,227,253] ], [ [255, 255, 255], [0, 0, 0] ], [ [0, 0, 0], [255, 255, 255] ], [ [255, 255, 255], [255, 0, 0] ] ]; $color = $couleur; if (($color == -1) || ($color > (count($color_tab)-1))) $color = $the_block->nonce % count($color_tab); // Rajout des HASHES $white = imagecolorallocate($vImage, 254, 254, 254); $fond = imagecolorallocate($vImage, 255, 255, 255); $black = imagecolorallocate($vImage, 0, 0, 0); $w = imagesx($vImage); $h = imagesy($vImage); // On rend le blanc transparent imagecolortransparent($vImage, $white); // On place un fond transparent sur toute l'image imagefilledrectangle($vImage, 0, 0, $w, $h, $white); // Puis un fond sur la zone de dessin... imagefilledrectangle($vImage, 0, $hauteur, $w, $h-$hauteur, $fond); $len = strlen($the_block->hash); $ratio_w = ($w - 10) / $len; $ratio_h = $hauteur / 32; // car hexadécimal // Récupérer les hashes dans 2 tableaux $tableau1 = str_split($the_block->hash); $tableau2 = str_split($the_block->prev_block); // Convertir les tableaux en liste de points $points1 = []; $points2 = []; $points1[] = 0; $points1[] = $hauteur - 1; $points2[] = 0; $points2[] = $hauteur - 1; for($i=0;$i<$len;$i++) { // Le HASH $coin_x1 = $ratio_w * $i; $coin_x2 = $ratio_w * ($i + 1); $coin_y1 = $hauteur; $coin_y2 = 5 + hexdec($tableau1[$i])*$ratio_h; $points1[] = $coin_x1; $points1[] = $coin_y2; $points1[] = $coin_x2; $points1[] = $coin_y2; // Le PREV HASH $coin_x1 = $ratio_w * $i; $coin_x2 = $ratio_w * ($i + 1); $coin_y1 = $hauteur; $coin_y2 = 25 - hexdec($tableau2[$i])*$ratio_h; $points2[] = $coin_x1; $points2[] = $coin_y2; $points2[] = $coin_x2; $points2[] = $coin_y2; } // Rajouter une barre au HASH $coin_x1 = $ratio_w * $i; $coin_x2 = $ratio_w * ($i + 1); $coin_y1 = $hauteur; $coin_y2 = 5; $points1[] = $coin_x1; $points1[] = $coin_y2; $points1[] = $coin_x2; $points1[] = $coin_y2; // Rajouter une barre au PREV HASH $coin_x1 = $ratio_w * $i; $coin_x2 = $ratio_w * ($i + 1); $coin_y1 = $hauteur; $coin_y2 = 25; $points2[] = $coin_x1; $points2[] = $coin_y2; $points2[] = $coin_x2; $points2[] = $coin_y2; // Rajouter un coin $points1[] = $w; $points1[] = $hauteur - 1; $points2[] = $w; $points2[] = $hauteur - 1; // Inverser le PREV HASH for($i=0;$i= $h) $points2[$i+1] = $h - 1; } // Dessiner le HASH imagefilledpolygon($vImage, $points1, (count($points1)/2), $fond); imagepolygon($vImage, $points1, (count($points1)/2), $black); // Dessiner le PREV HASH imagefilledpolygon($vImage, $points2, (count($points2)/2), $fond); imagepolygon($vImage, $points2, (count($points2)/2), $black); // Rajout des textes $the_name = blockchain::hash2SpecialName($the_block->hash); if ($the_name == $the_block->hash) $the_name = date('Ymd H:i:s', $the_block->time); putenv('GDFONTPATH='.RESS_PATH.'/fonts/'); $font = 'DS-DIGIB.TTF'; $the_texte = "Height : ".$the_block->height; imagettftext($vImage,18, 0, 5, $hauteur-5, $black, $font, $the_texte); $the_texte = "Inputs : ".$the_block->topisto_inputs; if (count($the_block->tx)==1) $the_texte = "Reward : ".$the_block->topisto_reward; imagettftext($vImage, 15, 0, 25, ($h - $hauteur)+18, $black, $font, $the_texte); $bbox = imagettfbbox(14, 0, $font, $the_name); imagettftext($vImage, 14, 0, ($w-3)-($bbox[2]-$bbox[0]), ($hauteur-5), $black, $font, $the_name); $draw = imagecolorallocate($vImage, $color_tab[$color][1][0], $color_tab[$color][1][1], $color_tab[$color][1][2]); $fond = imagecolorallocate($vImage, $color_tab[$color][0][0], $color_tab[$color][0][1], $color_tab[$color][0][2]); return [$white, $fond, $draw, $font, $color_tab[$color][1], $color_tab[$color][0]]; } public static function getTransactionData($the_block, $type=1) { // Cela a déjà été calculé if (isset($the_block->data[$type])) return $the_block->data[$type]; $data = []; foreach($the_block->tx as $transaction) { $total_outputs = 0; $total_inputs = 0; $value = 0; if (isset($transaction->out)) foreach($transaction->out as $output) if (isset($output->value)) $total_outputs += $output->value; if (isset($transaction->inputs)) foreach($transaction->inputs as $input) if (isset($input->prev_out->value)) $total_inputs += $input->prev_out->value; switch($type) { // OUTPUTS case 1: if ($total_inputs != 0) $data[] = ["hash" => $transaction->hash, "value" => $total_outputs]; break; // INPUTS case 2: $data[] = ["hash" => $transaction->hash, "value" => $total_inputs]; break; // FEES case 3: if ($total_inputs != 0) $data[] = ["hash" => $transaction->hash, "value" => ($total_outputs-$total_inputs)]; break; // REWARD case 4: if ($total_inputs == 0) $data[] = ["hash" => $transaction->hash, "value" => $total_outputs]; break; } } if (!isset($the_block->data)) $the_block->data = []; $the_block->data[$type] = $data; return $data; } } blockchain::init(); ?>