Implementing "Blockshell" with Common Lisp(en)steemCreated with Sketch.

in blockchain •  7 years ago 

Since reading Blockshell[1] -- the command line interface for a minimal learning blockchain:
https://zhuanlan.zhihu.com/p/33520216
After that, the development of the blockchain program began.

The following text uses the "Blockshell" version of the Common Lisp step-by-step source code to view the original text.
https://github.com/sundawning/literate-programming/blob/master/blockshell.org
To build directly, eliminating the need to copy and paste.

The program is written using org-mode. The corresponding example exists in pairs with the defined part of the program. The code is an example and the code is a document.

This blog extracts the program part of the original text, and combines the characteristics of the Steemit website to rearrange it so that it can be published and read normally. Therefore, if you do not know anything about Common Lisp, the following may cause you to sleepy, please stop there; if you want to learn programming, but do not know which language to start with, recommend Common Lisp because Common Lisp's syntax is already Only parentheses are required.

System definition: sundawning.block-chain.asd

(in-package :common-lisp)

(asdf:defsystem #:sundawning.block-chain
  :depends-on (#:babel
               #:ironclad
               #:cl-store)
  :serial t
  :components ((:file "package")
               (:file "utility")
               (:file "block-chain")))

Package definition: package.lisp

(in-package :common-lisp)

(defpackage #:sundawning.block-chain
  (:use :common-lisp)
  (:export #:*data-blocks*
           #:data-block
           #:index
           #:previous-hash
           #:data
           #:timestamp
           #:nonce
           #:hash
           #:data-block-index
           #:data-block-previous-hash
           #:data-block-data
           #:data-block-timestamp
           #:data-block-nonce
           #:data-block-hash
           #:insert
           #:activate-initial-block
           #:all-blocks
           #:store
           #:restore
           #:objects-to-string
           #:string-to-hex-string
           #:class-slots-names
           #:reduce-list
           #:hash-table-keys
           ))

Utility function: utility.lisp

(in-package #:sundawning.block-chain)

Splicing multiple objects

  • Convert null value to empty string
(defun objects-to-string (&rest rest)
  (format nil "~{~A~}"
          (mapcar (lambda (i)
                    (if (null i)
                        ""
                        i))
                  rest)))
(objects-to-string 0 nil nil 3726796533 0 "dd9a97e5501949252f92370742c9102244ae1ca2462a332196ae57d037d90029")
"037267965330dd9a97e5501949252f92370742c9102244ae1ca2462a332196ae57d037d90029"

Convert string to hexadecimal string

  1. First sha256 code
  2. Press the hexadecimal code again
(defun string-to-hex-string (string)
  (when string
    (ironclad:byte-array-to-hex-string
     (ironclad:digest-sequence :sha256
                               (ironclad:digest-sequence :sha256
                                                         (babel:string-to-octets string))))))
(string-to-hex-string "037267965330dd9a97e5501949252f92370742c9102244ae1ca2462a332196ae57d037d90029")
"373c03001be1be19e52e64ceeffd6c270a28f265ed614f597c2b11eacd34d186"

all the keys of the hash table

(defun hash-table-keys (hash-table)
  (let ((keys ()))
    (maphash (lambda (key value)
               (push key keys))
             hash-table)
    keys))

The name of all the slots of the class

(defun class-slots-names (object)
  (mapcar #'sb-mop:slot-definition-name
          (sb-mop:class-slots (class-of object))))

The value of all slots in an object in a class

(defun class-object-values (object)
  (mapcar (lambda (slot-name)
            (slot-value object slot-name))
          (class-slots-names object)))
(0 NIL NIL 3726810699 0
 "22b28bad900cef94e5029c5dc20d4c847d3a2b0db48b9e60be2f6eb8f699bb5f")

Mosaic list

(defun reduce-list (list)
  (reduce (lambda (i j)
            (concatenate 'list i j))
          list
          :initial-value nil))
(reduce-list '((INDEX 0) (PREVIOUS-HASH NIL) (DATA NIL) (TIMESTAMP 3726798867) (NONCE 0)
               (HASH "5b6639084735a5d5daef7754bb76e0f883e470bc74c62ed6fdf3f5e51e415244")))
(INDEX 0 PREVIOUS-HASH NIL DATA NIL TIMESTAMP 3726798867 NONCE 0 HASH
 "5b6639084735a5d5daef7754bb76e0f883e470bc74c62ed6fdf3f5e51e415244")

Convert a vector to a list

(defun vector-to-list (vector)
  (loop
     :for i from 0 to (1- (array-total-size vector))
     :collect (aref vector i)))
(vector-to-list
 (vector 1 2 3))
(1 2 3)

Blockchain: block-chain.lisp

(in-package #:sundawning.block-chain)

build database

  1. Construct a database as a class and a vector
    • No need to change the database: Once you add data, you no longer need to change
    • No need to delete the database: As long as no database is used, the database is deprecated and the database is formatted. There is no need to delete data one by one.
    (defclass data-block ()
      ((index :initarg :index
              :accessor data-block-index)
       (previous-hash :initarg :previous-hash
                      :accessor data-block-previous-hash)
       (data :initarg :data
             :accessor data-block-data)
       (timestamp :initarg :timestamp
                  :accessor data-block-timestamp)
       (nonce :initarg :nonce
              :accessor data-block-nonce)
       (hash :initarg :hash
             :accessor data-block-hash)))
    
    (defmethod insert ((data-blocks vector) &key index previous-hash data timestamp nonce)
      (let ((hash (string-to-hex-string
                   (objects-to-string index previous-hash data timestamp nonce))))
        (vector-push-extend (make-instance 'data-block
                                           :hash hash
                                           :nonce nonce
                                           :timestamp timestamp
                                           :data data
                                           :previous-hash previous-hash
                                           :index index)
                            data-blocks)))
    
  2. Example: Using a Database
    1. Create a database
    2. Add data
    3. View the data added in the database
    (let ((data-blocks (make-array 1 :fill-pointer 0))
          (index 0)
          (previous-hash nil)
          (data nil)
          (timestamp (get-universal-time))
          (nonce 0))
      (insert data-blocks :index index
              :previous-hash previous-hash
              :data data
              :timestamp timestamp
              :nonce nonce)
      (let ((object (gethash (sundawning.block-chain::data-block-hash))
              (aref data-blocks 0)))
        (reduce-list
         (mapcar (lambda (slot-name)
                   (list slot-name (slot-value object slot-name)))
                 (class-slots-names object)))))
    
    (INDEX 0 PREVIOUS-HASH NIL DATA NIL TIMESTAMP 3726801811 NONCE 0 HASH
     "ca21eb1dd1f786c93395394c7ab31371b4300505d3f1367d17c79a3d2e10d1f5")
    

Activate the initial block

  • Clear the database
  • Add the first data to the database
(defparameter *data-blocks* nil)

(defmethod activate-initial-block ((data-blocks vector) data)
  (let ((index 0)
        (previous-hash nil)
        (data data)
        (timestamp (get-universal-time))
        (nonce 0))
    (setf *data-blocks* data-blocks)
    (insert *data-blocks*
            :index index
            :previous-hash previous-hash
            :data (or data
                      "Copyright (c) 2018 SunDawning <[email protected]> All rights reserved.")
            :timestamp timestamp
            :nonce nonce)))
(activate-initial-block (make-array 1 :fill-pointer 0)
                        nil)
0
*data-blocks*
#(#<DATA-BLOCK {10031EC173}>)

See all data blocks

(defmethod all-blocks ((data-blocks vector))
  (mapcar (lambda (object)
            (reduce-list
             (mapcar (lambda (slot-name)
                       (list slot-name (slot-value object slot-name)))
                     (class-slots-names object))))
          (subseq (vector-to-list data-blocks)
                  0
                  (length data-blocks))))
(all-blocks *data-blocks*)
((INDEX 1 PREVIOUS-HASH
  #1="d250ed4624d2282b5eaac8a501ea08560ca370b4cdac4897ab4a61b51e437b3d" DATA
  "第一个" TIMESTAMP 3726802462 NONCE 24 HASH
  "04b38373e95c31da583a59afac114efd244fd08f62c3f87668e5459cb5ad481a")
 (INDEX 0 PREVIOUS-HASH NIL DATA NIL TIMESTAMP 3726802341 NONCE 0 HASH #1#))

Calculate whether the data block meets the requirements

  1. Hash verification
    (defun hex-string-p (string)
      (cl-ppcre:scan-to-strings "^[0-9a-f]*$" string))
    
    (defun valid-difficulty-p (difficulty)
      (hex-string-p
       (remove-if-not #'alpha-char-p difficulty)))
    
    (defun hash-value-verification (difficulty hex-string)
      (if (valid-difficulty-p difficulty)
          (cl-ppcre:scan-to-strings difficulty hex-string)
          (error "The difficulty level is wrong, guy, did you see that the 16-letter content contained the English letters you gave?")))
    
    For example, see if a string starts with 4 zeros:
    (hash-value-verification "^0{4}"
                             "d250ed4624d2282b5eaac8a501ea08560ca370b4cdac4897ab4a61b51e437b3d")
    
    NIL
    
  2. Previous data block
    (defmethod previous-data-block ((data-blocks hash-table))
      (unless (< (hash-table-count data-blocks)
                 1)
        (gethash (nth (1- (hash-table-count data-blocks))
                      (hash-table-keys data-blocks))
                 data-blocks)))
    
  3. The hash value of the previous block
    • Extract the hash of the previous data block from the existing data block
    (defun previous-hash (data-blocks index)
      (data-block-hash
       (aref data-blocks
             (1- index))))
    
    (previous-hash *data-blocks* 1)
    
    "4aa489da52043ff43ea353934d4d09a0e4f39247d5361114aee098bb5bc47060"
    
  4. Add compliant data to the database
    (defun proof-of-work (difficulty data-blocks data)
      (let ((previous-hash (previous-hash data-blocks
                                          (length data-blocks)))
            (index (length data-blocks))
            (nonce 0))
        (loop
           (let ((time (get-universal-time)))
             (if (hash-value-verification difficulty
                                          (string-to-hex-string
                                           (objects-to-string index previous-hash data time nonce)))
                 (return
                   (insert data-blocks
                           :index index
                           :previous-hash previous-hash
                           :data data
                           :timestamp time
                           :nonce nonce))
                 (incf nonce))))))
    

start calculating

(defparameter *difficulty* "^0{4}")

(defun mine-block (&amp;optional data)
  (unless *data-blocks*
    (activate-initial-block (make-array 1 :fill-pointer 0)
                            nil))
  (proof-of-work *difficulty* *data-blocks* data))

(defmethod insert ((data string) &amp;rest rest)
  (declare (ignore rest))
  (typecase *data-blocks*
    (vector
     (proof-of-work *difficulty* *data-blocks* data))
    (hash-table
     (let ((previous-data-block (previous-data-block *data-blocks*)))
       (insert *data-blocks*
               :timestamp (get-universal-time)
               :data data
               :previous-hash (data-block-hash previous-data-block)
               :index (1+ (data-block-index previous-data-block)))))))

Example: A complete initialization, calculation process

  1. Load library
    (progn
      (ql:quickload :sundawning.block-chain)
      (in-package :sundawning.block-chain))
    
  2. start calculating
    (let ((*difficulty* "^0{2}}"))
      (dotimes (i 10)
        (mine-block))
      *data-blocks*)
    
    #(#<DATA-BLOCK {10022AC123}> #<DATA-BLOCK {10025345A3}>
      #<DATA-BLOCK {1002578E53}> #<DATA-BLOCK {10025D39D3}>
      #<DATA-BLOCK {10025EE533}> #<DATA-BLOCK {10025FF2E3}>
      #<DATA-BLOCK {10026CD953}> #<DATA-BLOCK {1002833F93}>
      #<DATA-BLOCK {1002C488A3}> #<DATA-BLOCK {1002CF03C3}>
      #<DATA-BLOCK {1002CFD613}>)
    
  3. View the current data in the database
    (all-blocks *data-blocks*)
    
    With the initial block, an 11 data:
    ((INDEX 0 PREVIOUS-HASH NIL DATA NIL TIMESTAMP 3726808946 NONCE 0 HASH
      #1="ce215d7a6553a3239a95f37f5f3c2c5c091dc5f6de47538fea1a75ef6e40be44")
     (INDEX 1 PREVIOUS-HASH #1# DATA NIL TIMESTAMP 3726808971 NONCE 73893 HASH
      #2="0000590e0c8775d75d68eb001eea7c56f2c2d0b4bd95e1673f885c6d4919f49b")
     (INDEX 2 PREVIOUS-HASH #2# DATA NIL TIMESTAMP 3726808974 NONCE 114263 HASH
      #3="0000eb59241be0f79c403510b2a9c4b5950fb08b5c87d53839279d3be43bd06d")
     (INDEX 3 PREVIOUS-HASH #3# DATA NIL TIMESTAMP 3726808975 NONCE 57796 HASH
      #4="0000563cd56bcd96cad959ca71898207109ac20f6bac6d6382467a3af27ed601")
     (INDEX 4 PREVIOUS-HASH #4# DATA NIL TIMESTAMP 3726808977 NONCE 61293 HASH
      #5="0000660a90a9db8b696872721d86350448d83e4f9de37e6f4f785869078a7304")
     (INDEX 5 PREVIOUS-HASH #5# DATA NIL TIMESTAMP 3726808978 NONCE 29095 HASH
      #6="000022b033ba3b4a0eec38325402aa62839e68900b9bf711ebc2f4b3bf0d5fde")
     (INDEX 6 PREVIOUS-HASH #6# DATA NIL TIMESTAMP 3726808981 NONCE 144460 HASH
      #7="0000a0d8ca99940f51be182998bc6361bd06a1d12b05bcafa536209d44b2a38e")
     (INDEX 7 PREVIOUS-HASH #7# DATA NIL TIMESTAMP 3726808983 NONCE 78953 HASH
      #8="00004e4e3a8dcca8ac30a172e7a0992fe8670fd5d6a7f9f6f492ad6d5dd6ab6f")
     (INDEX 8 PREVIOUS-HASH #8# DATA NIL TIMESTAMP 3726808986 NONCE 114814 HASH
      #9="000006758ce31904d75a86afd8e30b202d981e3864d2d3927c04a4d3eecaa96e")
     (INDEX 9 PREVIOUS-HASH #9# DATA NIL TIMESTAMP 3726808988 NONCE 77439 HASH
      #10="000093396d15a35b32e64d84b614636b228c2b79f3cb07d0661a8fcd4d82142a")
     (INDEX 10 PREVIOUS-HASH #10# DATA NIL TIMESTAMP 3726808988 NONCE 33390 HASH
      "000002321f992fd65a8eb91446442088e750359ccee86fe620c8bb1a9567dee3"))
    

Verify that a block of data is a block of data in the database

(defun valid-data-block-p (data-blocks data-block difficulty)
  (let ((index (data-block-index data-block))
        (length (length data-blocks))
        (hash (string-to-hex-string
               (apply #'objects-to-string
                      (subseq (class-object-values data-block)
                              0
                              (1- (length (class-object-values data-block)))))))
        (data-block-hash (data-block-hash data-block))
        (data-block-previous-hash (data-block-previous-hash data-block)))
    (labels ((equal-previous-data-block-hash-p (data-blocks index)
               (equal data-block-previous-hash
                      (data-block-hash
                       (aref data-blocks
                             (1- index)))))
             (equal-next-data-block-hash-p (data-blocks index)
               (equal data-block-hash
                      (data-block-previous-hash
                       (aref data-blocks
                             (1+ index))))))
      (and (hash-value-verification difficulty hash)
           (equal data-block-hash hash)
           (cond ((or (= length 1)
                      (= index 0))
                  (equal data-block-hash
                         (data-block-hash
                          (aref data-blocks 0))))
                 ((< (1+ index)
                     length)
                  (and (equal-previous-data-block-hash-p data-blocks index)
                       (equal-next-data-block-hash-p data-blocks index)))
                 ((= (1+ index)
                     length)
                  (equal-previous-data-block-hash-p data-blocks index)))))))

Self-certification:

(valid-data-block-p *data-blocks*
                    (aref *data-blocks* 1)
                    1)
T

His certificate:

(let ((data-blocks (make-array 1 :fill-pointer 0))
      (index 0)
      (previous-hash nil)
      (data nil)
      (timestamp (get-universal-time))
      (nonce 0))
  (insert data-blocks :index index
          :previous-hash previous-hash
          :data data
          :timestamp timestamp
          :nonce nonce)
  (valid-data-block-p *data-blocks*
                      (aref data-blocks 0)
                      0))
NIL

Database save and restore

(defun store (data-blocks filespec)
  (cl-store:store data-blocks filespec))

(defun restore (filespec)
  (setf *data-blocks*
        (cl-store:restore filespec)))

problem

(progn
  (ql:quickload :sundawning.block-chain)
  (in-package :sundawning.block-chain))

(let ((*difficulty* 4))
  (loop
     :repeat 10
     :collect (mine-block)))
The value
  NIL
is not of type
  VECTOR
   [Condition of type TYPE-ERROR]

Restarts:
 0: [RETRY] Retry SLIME REPL evaluation request.
 1: [*ABORT] Return to SLIME&#39;s top level.
 2: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1004B58403}>)

Backtrace:
  0: (PREVIOUS-HASH NIL 0)
      Locals:
        DATA-BLOCKS = NIL
        INDEX = 0
  1: (PROOF-OF-WORK 4 NIL NIL)
      Locals:
        DATA = NIL
        DATA-BLOCKS = NIL
        DIFFICULTY = 4

The question shows that "data-blocks" is a null value indicating that the database has no data. The solution is to create an initialization data block when the database is found to be empty:

(defun mine-block (&amp;optional data)
  (unless *data-blocks*
    (activate-initial-block data))
  (proof-of-work *difficulty* *data-blocks* data))

Footnotes

[1]: GitHub - daxeel/blockshell: Minimal Blockchain Learning CLI:
https://github.com/daxeel/blockshell

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!