Last active
February 22, 2023 16:26
-
-
Save devsmt/18007b8c5fa0009d34b9088b1e37d3fd to your computer and use it in GitHub Desktop.
minimalistic blockchain in PHP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env php | |
<?php | |
declare (strict_types = 1); | |
/* | |
proof of concept of the inner workings of a Block Chain | |
*/ | |
class BlockChain { | |
public string $path; | |
public array $data; | |
public function __construct(string $path) { | |
$this->path = $path; | |
$this->read(); | |
} | |
// loads the BC in memory | |
public function read(): array{ | |
$file_path = $this->path; | |
$is_present = file_exists($file_path); | |
$this->data = []; | |
if ($is_present) { | |
$json_str = file_get_contents($file_path); | |
try { | |
$this->data = json_decode($json_str, $use_assoc = true, 512, JSON_THROW_ON_ERROR | JSON_OBJECT_AS_ARRAY); | |
} catch (\JsonException $exception) { //php7.3 | |
echo 'JSON decode error:' . $exception->getMessage() . "\n"; | |
} | |
} | |
return $this->data; | |
} | |
// writes the in memory data serialized to file | |
public function close(): void{ | |
$json = json_encode($this->data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); | |
file_put_contents($this->path, $json); // default is overwriting file | |
} | |
/** | |
* verifies the BlockChain | |
* @return array{0: bool, 1: string, 2: string} | |
*/ | |
public function verify(): array{ | |
$_res = function (bool $ok, string $msg, string $hash): array{ | |
return [$ok, $msg, $hash]; | |
}; | |
if (!empty($this->data)) { | |
foreach ($this->data as $key => $rec) { | |
$prev_check = ($key > 0) | |
? $this->data[$key - 1]['check'] | |
: self::EMPTY_CHECK; | |
$ok = self::hash($prev_check . $rec['content'] . $rec['time']) == $rec['check']; | |
if (!$ok) { | |
return $_res(false, "rec {$rec['check']} is invalid.", $rec['check']); | |
} | |
} | |
} | |
return $_res(true, '', ''); | |
} | |
// adds a block and signs it | |
public function add(string $content): void{ | |
$last_rec = $this->getLastRercord(); | |
$rec = [ | |
'content' => $content, | |
'time' => date('Y-m-d H:i:s'), | |
]; | |
$check = self::hash($last_rec['check'] . $rec['content'] . $rec['time']); | |
$rec['check'] = $check; | |
$this->data[] = $rec; | |
} | |
const EMPTY_CHECK = ''; | |
public function getLastRercord(): array{ | |
// if emty, return an emty data structure | |
if (empty($this->data)) { | |
return ['check' => self::EMPTY_CHECK]; | |
} | |
$rec = $this->data[count($this->data) - 1]; | |
return $rec; | |
} | |
// calc the hash | |
public static function hash(string $str): string{ | |
$hash = hash('sha256', $str); | |
return $hash; | |
} | |
} | |
//-- actions -------------------------------------- | |
function action_verify(BlockChain $BC): void { | |
list($ok, $msg, $k) = $BC->verify(); | |
echo ($ok ? "BlockChain is valid" : "invalid BlockChain: $msg") . "\n"; | |
} | |
function action_add(BlockChain $BC): void { | |
list($ok, $msg, $k) = $BC->verify(); | |
if ($ok) { | |
$content = readline('Content to add: '); | |
if (empty($content)) { | |
die('specify a content to add'); | |
} | |
$BC->add($content); | |
echo "data added" . "\n"; | |
} else { | |
echo "invalid BlockChain: $msg \n"; | |
} | |
} | |
function action_usage(): void { | |
echo "{$_SERVER['argv'][0]} [verify|add] [content] | |
actions: | |
verify = verify each block is consistent | |
add = adds a block | |
\n"; | |
} | |
function opt(int $k, string $def = ''): string{ | |
$argv = $_SERVER['argv']; | |
return (string) isset($argv[$k]) ? $argv[$k] : $def; | |
} | |
function main(array $argv, int $argc): void{ | |
$file_path = opt(2, ''); | |
if (empty($file_path)) { | |
die('invalid file path ' . $file_path); | |
} | |
$BC = new BlockChain($file_path); | |
$action = opt(1); | |
switch ($action) { | |
case 'verify': | |
action_verify($BC); | |
break; | |
case 'add': | |
action_add($BC); | |
break; | |
default: | |
action_usage(); | |
break; | |
} | |
$BC->close(); | |
exit(0); | |
} | |
main($argv = $_SERVER['argv'], $argc = $_SERVER['argc']); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment