Skip to content

Instantly share code, notes, and snippets.

@devsmt
Last active February 22, 2023 16:26
Show Gist options
  • Save devsmt/18007b8c5fa0009d34b9088b1e37d3fd to your computer and use it in GitHub Desktop.
Save devsmt/18007b8c5fa0009d34b9088b1e37d3fd to your computer and use it in GitHub Desktop.
minimalistic blockchain in PHP
#!/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