<?php
/**
* A very simple BlockChain implementation intended to illustrate the concept.
*
* Marty Anstey (https://marty.anstey.ca/)
* August 2015
*
* The block index simply maps a block to a disk offset for convenience.
* It's not necessary, but it makes it much easier to quickly locate any
* block within the chain.
*
* ISAM index:
* [4] count
* -----------
* [4] offset
* [4] length
* ...
*
* Block format:
* [4] magic
* [1] format (0x01)
* [4] timestamp
* [n] hash of previous block
* [4] data length
* [?] data
*
*
*/
define('_magic', 0xD5E8A97F);
define('_hashalg', 'sha256');
define('_hashlen', 32);
define('_blksize', (13 + _hashlen));
/*
$res = addblock('blockchain.dat','Some data');
if ($res!==TRUE) exit("ERROR: ".$res."\n");
*/
function addblock($fn,$data,$genesis=FALSE) {
$indexfn = $fn.'.idx';
if (!$genesis) {
if (!file_exists($fn)) return('Missing blockchain data file!');
if (!file_exists($indexfn)) return('Missing blockchain index file!');
// get disk location of last block from index
if (!$ix = fopen($indexfn, 'r+b')) return("Can't open ".$indexfn);
$maxblock = unpack('V', fread($ix,4))[1];
$zpos = (($maxblock*8)-4);
fseek($ix, $zpos, SEEK_SET);
$ofs = unpack('V', fread($ix, 4))[1];
$len = unpack('V', fread($ix, 4))[1];
// read last block and calculate hash
if (!$bc = fopen($fn,'r+b')) return("Can't open ".$fn);
fseek($bc, $ofs, SEEK_SET);
$block = fread($bc, $len);
$hash = hash(_hashalg, $block);
// add new block to the end of the chain
fseek($bc, 0, SEEK_END);
$pos = ftell($bc);
write_block($bc, $data, $hash);
fclose($bc);
// update index
update_index($ix, $pos, strlen($data), ($maxblock+1));
fclose($ix);
return TRUE;
}
else
{
if (file_exists($fn)) return('Blockchain data file already exists!');
if (file_exists($indexfn)) return('Blockchain index file already exists!');
$bc = fopen($fn, 'wb');
$ix = fopen($indexfn, 'wb');
write_block($bc, $data, str_repeat('00', _hashlen));
update_index($ix, 0, strlen($data), 1);
fclose($bc);
fclose($ix);
return TRUE;
}
}
function write_block(&$fp, $data, $prevhash) {
fwrite($fp, pack('V', _magic), 4); // Magic
fwrite($fp, chr(1), 1); // Version
fwrite($fp, pack('V', time()), 4); // Timestamp
fwrite($fp, hex2bin($prevhash), _hashlen); // Previous Hash
fwrite($fp, pack('V', strlen($data)), 4); // Data Length
fwrite($fp, $data, strlen($data)); // Data
}
function update_index(&$fp, $pos, $datalen, $count) {
fseek($fp, 0, SEEK_SET);
fwrite($fp, pack('V', $count), 4); // Record count
fseek($fp, 0, SEEK_END);
fwrite($fp, pack('V', $pos), 4); // Offset
fwrite($fp, pack('V', ($datalen+_blksize)), 4); // Length
}
|