Ανάπτυξη blockchain για τη βιομηχανία που χρησιμοποιεί το Go. Μέρος 1

Εδώ και τέσσερις μήνες εργάζομαι σε ένα έργο που ονομάζεται «Ανάπτυξη εργαλείων προστασίας και διαχείρισης δεδομένων σε κυβερνητικούς και βιομηχανικούς τομείς με βάση το blockchain».
Τώρα θα ήθελα να σας πω πώς ξεκίνησα αυτό το έργο και τώρα θα περιγράψω λεπτομερώς τον κώδικα του προγράμματος.

Ανάπτυξη blockchain για τη βιομηχανία που χρησιμοποιεί το Go. Μέρος 1

Αυτό είναι το πρώτο άρθρο μιας σειράς άρθρων. Εδώ περιγράφω τον διακομιστή και το πρωτόκολλο. Στην πραγματικότητα, ο αναγνώστης μπορεί ακόμη και να γράψει τις δικές του εκδόσεις αυτών των στοιχείων blockchain.

Και εδώ είναι το δεύτερο μέρος — σχετικά με τις δομές δεδομένων blockchain και συναλλαγών, καθώς και σχετικά με το πακέτο που υλοποιεί την αλληλεπίδραση με τη βάση δεδομένων.

Πέρυσι, στο Digital Breakthrough hackathon, είχαν μια ιδέα να φτιάξουν ένα χρήσιμο σύστημα για τη βιομηχανία και την ψηφιακή οικονομία χρησιμοποιώντας τεχνολογία κατανεμημένης λογιστικής· εκδόθηκε επίσης μια επιχορήγηση για την ανάπτυξη από το Innovation Assistance Foundation (θα πρέπει να γράψω ξεχωριστό άρθρο σχετικά με την επιχορήγηση, για όσους μόλις αρχίζουν να κάνουν startups ), και τώρα με σειρά.

Η ανάπτυξη πραγματοποιείται στη γλώσσα Go και η βάση δεδομένων στην οποία αποθηκεύονται τα μπλοκ είναι το LevelDB.
Τα κύρια μέρη είναι το πρωτόκολλο, ο διακομιστής (ο οποίος εκτελεί το TCP και το WebSocket - το πρώτο για το συγχρονισμό του blockchain, το δεύτερο για τη σύνδεση πελατών, την αποστολή συναλλαγών και εντολών από JavaScript, για παράδειγμα.

Όπως αναφέρθηκε, αυτό το blockchain χρειάζεται πρωτίστως για την αυτοματοποίηση και την προστασία της ανταλλαγής προϊόντων μεταξύ προμηθευτών και πελατών ή και των δύο σε ένα άτομο. Αυτοί οι άνθρωποι δεν βιάζονται να εμπιστευτούν ο ένας τον άλλον. Ωστόσο, το καθήκον δεν είναι μόνο να δημιουργήσετε ένα «βιβλίο επιταγών» με μια ενσωματωμένη αριθμομηχανή, αλλά ένα σύστημα που αυτοματοποιεί τις περισσότερες από τις συνήθεις εργασίες που προκύπτουν κατά την εργασία με τον κύκλο ζωής του προϊόντος. Ο bytecode που είναι υπεύθυνος για αυτό το θέμα, όπως συνηθίζεται με τα blockchains, αποθηκεύεται στις εισόδους και τις εξόδους των συναλλαγών (οι ίδιες οι συναλλαγές αποθηκεύονται σε μπλοκ, τα μπλοκ στο LevelDB είναι προκωδικοποιημένα σε μορφή GOB). Αρχικά, ας μιλήσουμε για το πρωτόκολλο και τον διακομιστή (γνωστός και ως κόμβος).

Το πρωτόκολλο δεν είναι περίπλοκο, η ουσία του είναι να μεταβεί στη λειτουργία φόρτωσης ορισμένων δεδομένων, συνήθως ενός μπλοκ ή συναλλαγής, ως απόκριση σε μια ειδική γραμμή εντολών, και είναι επίσης απαραίτητο για την ανταλλαγή αποθεμάτων, ώστε ο κόμβος να γνωρίζει ποιος είναι είναι συνδεδεμένο και πώς έχουν να κάνουν δουλειά (οι κόμβοι που συνδέονται για τη συνεδρία συγχρονισμού ονομάζονται επίσης "γειτονικοί" επειδή η IP τους είναι γνωστή και τα δεδομένα κατάστασής τους αποθηκεύονται στη μνήμη).

Οι φάκελοι (κατάλογοι όπως τους αποκαλεί το Linux) στην κατανόηση των προγραμματιστών Go ονομάζονται πακέτα, επομένως στην αρχή κάθε αρχείου με κώδικα Go από αυτόν τον κατάλογο γράφουν το πακέτο folder_name_where_this_file βρίσκεται. Διαφορετικά, δεν θα μπορείτε να τροφοδοτήσετε το πακέτο στον μεταγλωττιστή. Λοιπόν, αυτό δεν είναι μυστικό για όσους γνωρίζουν αυτή τη γλώσσα. Αυτά είναι τα πακέτα:

  • Επικοινωνία δικτύου (διακομιστής, πελάτης, πρωτόκολλο)
  • Δομές αποθηκευμένων και μεταδιδόμενων δεδομένων (μπλοκ, συναλλαγή)
  • Βάση δεδομένων (blockchain)
  • Ομοφωνία
  • Στοιβαγμένη εικονική μηχανή (xvm)
  • Βοηθητικό (κρυπτογράφηση, τύποι) αυτό είναι όλο για τώρα.

Εδώ είναι ο σύνδεσμος για το github

Αυτή είναι μια εκπαιδευτική έκδοση, στερείται αλληλεπίδρασης μεταξύ των διεργασιών και πολλών πειραματικών στοιχείων, αλλά η δομή αντιστοιχεί σε αυτήν στην οποία πραγματοποιείται η ανάπτυξη. Αν έχετε κάτι να προτείνετε στα σχόλια, θα χαρώ να το λάβω υπόψη στην περαιτέρω ανάπτυξη. Και τώρα για μια εξήγηση του διακομιστή και πρωτόκολλο.

Ας δούμε πρώτα τον διακομιστή.

Η υπορουτίνα διακομιστή λειτουργεί ως διακομιστής δεδομένων που εκτελείται πάνω από το πρωτόκολλο TCP χρησιμοποιώντας δομές δεδομένων από το πακέτο πρωτοκόλλου.

Η ρουτίνα χρησιμοποιεί τα ακόλουθα πακέτα: διακομιστής, πρωτόκολλο, τύποι. Στην ίδια τη συσκευασία tcp_server.go περιέχει δομή δεδομένων Σερβίρισμα.

type Serve struct {
	Port string
	BufSize int
	ST *types.Settings
}

Μπορεί να δεχθεί τις ακόλουθες παραμέτρους:

  • Θύρα δικτύου μέσω της οποίας θα ανταλλάσσονται δεδομένα
  • Αρχείο διαμόρφωσης διακομιστή σε μορφή JSON
  • Σημαία για εκτέλεση σε λειτουργία εντοπισμού σφαλμάτων (ιδιωτικό blockchain)

Πρόοδος:

  • Διαβάζει τη διαμόρφωση από το αρχείο JSON
  • Η σημαία λειτουργίας εντοπισμού σφαλμάτων ελέγχεται: εάν έχει οριστεί, ο προγραμματιστής συγχρονισμού δικτύου δεν εκκινείται και η αλυσίδα μπλοκ δεν φορτώνεται
  • Εκκίνηση της δομής δεδομένων διαμόρφωσης και εκκίνηση του διακομιστή

διακομιστή

  • Πραγματοποιεί την εκκίνηση του διακομιστή TCP και της αλληλεπίδρασης δικτύου σύμφωνα με το πρωτόκολλο.
  • Έχει μια δομή δεδομένων Serve που αποτελείται από έναν αριθμό θύρας, μέγεθος buffer και έναν δείκτη στη δομή τύπους.Ρυθμίσεις
  • Η μέθοδος Run ξεκινά την αλληλεπίδραση δικτύου (ακρόαση εισερχόμενων συνδέσεων σε μια δεδομένη θύρα, όταν λαμβάνεται μια νέα σύνδεση, η επεξεργασία της μεταφέρεται στη μέθοδο ιδιωτικής λαβής σε ένα νέο νήμα)
  • В λαβή Τα δεδομένα από τη σύνδεση διαβάζονται σε ένα buffer, μετατρέπονται σε αναπαράσταση συμβολοσειράς και μεταβιβάζονται σε πρωτόκολλο.Επιλογή
  • πρωτόκολλο.Επιλογή επιστρέφει αποτέλεσμα ή προκαλεί σφάλμα. αποτέλεσμα στη συνέχεια μεταφέρθηκε σε πρωτόκολλο.Ερμηνεύστεπου επιστρέφει intrpr - αντικείμενο του τύπου InterpreteData, ή προκαλεί σφάλμα κατά την επεξεργασία του αποτελέσματος επιλογής
  • Στη συνέχεια εκτελείται ο διακόπτης intrpr.Commands[0] που ελέγχει ένα από τα: αποτέλεσμα, inv, σφάλμα και υπάρχει ένα τμήμα αθέτηση
  • Στην ενότητα αποτέλεσμα ο διακόπτης βρίσκεται από την τιμή intrpr.Commands[1] που ελέγχει τις τιμές μήκος προσωρινής μνήμης и εκδοχή (σε κάθε περίπτωση καλείται η αντίστοιχη συνάρτηση)

Λειτουργίες GetVersion и Μήκος Buffer βρίσκονται στο αρχείο srvlib.go πακέτο διακομιστή

GetVersion(conn net.Conn, version string)

απλά εκτυπώνει στην κονσόλα και στέλνει την έκδοση που έχει περάσει στην παράμετρο στον πελάτη:

conn.Write([]byte("result:" + version))

.
Λειτουργία

BufferLength(conn net.Conn, intrpr *protocol.InterpreteData)

φορτώνει ένα μπλοκ, μια συναλλαγή ή άλλα συγκεκριμένα δεδομένα ως εξής:

  • Εκτυπώνει στην κονσόλα τον τύπο δεδομένων που καθορίζεται στο πρωτόκολλο που πρέπει να γίνει αποδεκτός:
    fmt.Println("DataType:", intrpr.Commands[2])
  • Διαβάζει την τιμή intrpr.Σώμα σε μια αριθμητική μεταβλητή buf_len
  • Δημιουργεί buffer newbuf καθορισμένο μέγεθος:
    make([]byte, buf_len)
  • Στέλνει μια εντάξει απάντηση:
    conn.Write([]byte("result:ok"))
  • Γεμίζει πλήρως το buffer από τη ροή ανάγνωσης:
    io.ReadFull(conn, newbuf)

    .

  • Εκτυπώνει τα περιεχόμενα του buffer στην κονσόλα
    fmt.Println(string(newbuf))

    και τον αριθμό των byte που διαβάζονται

    fmt.Println("Bytes length:", n)
  • Στέλνει μια εντάξει απάντηση:
    conn.Write([]byte("result:ok"))

Οι μέθοδοι από το πακέτο διακομιστή έχουν ρυθμιστεί ώστε να επεξεργάζονται δεδομένα που λαμβάνονται χρησιμοποιώντας λειτουργίες από το πακέτο πρωτόκολλο.

Πρωτόκολλο

Ένα πρωτόκολλο χρησιμεύει ως μέσο που αντιπροσωπεύει δεδομένα στην ανταλλαγή δικτύου.

Επιλογή (συμβολοσειρά str) (συμβολοσειρά, σφάλμα) εκτελεί την πρωτογενή επεξεργασία των δεδομένων που λαμβάνονται από τον διακομιστή, λαμβάνει μια συμβολοσειρά αναπαράσταση των δεδομένων ως είσοδο και επιστρέφει μια συμβολοσειρά προετοιμασμένη για Διερμηνέας:

  • Η συμβολοσειρά εισόδου χωρίζεται σε κεφάλι και σώμα χρησιμοποιώντας ReqParseN2(str)
  • Η κεφαλή χωρίζεται σε στοιχεία και τοποθετείται σε μια φέτα εντολών χρησιμοποιώντας το ReqParseHead(head)
  • В διακόπτης (εντολές[0]) επιλέξτε την ληφθείσα εντολή (cmd, κλειδί, διεύθυνση ή ενεργοποιείται το τμήμα αθέτηση)
  • Στο cmd ελέγχονται 2 εντολές διακόπτης(εντολές[1]) — μήκος и μεταστροφή.
  • μήκος ελέγχει τον τύπο δεδομένων εντολές[2] και το αποθηκεύει Τύπος δεδομένων
  • Το ελέγχει σώμα περιέχει μια τιμή συμβολοσειράς
    len(body) < 1
  • Επιστρέφει τη συμβολοσειρά απάντησης:
    "result:bufferlength:" + datatype + "/" + body
  • μεταστροφή επιστρέφει μια συμβολοσειρά
    return "result:version/auto"

Διερμηνέας

Περιέχει τη δομή InterpreteData και εκτελεί δευτερεύουσα επεξεργασία των δεδομένων που επιστρέφονται από Επιλογή χορδές και σχηματισμός αντικειμένων InterpreteData.

type InterpreteData struct {
	Head string
	Commands []string
	Body string
	IsErr bool
	ErrCode int 
	ErrMessage string
}

Λειτουργία

Interprete(str string) (*InterpreteData, error)

δέχεται μια χορδή αποτέλεσμα και δημιουργεί και επιστρέφει μια αναφορά στο αντικείμενο InterpreteData.

Πρόοδος:

  • Ομοίως Επιλογή το κεφάλι και το σώμα εξάγονται χρησιμοποιώντας ReqParseN2(str)
  • η κεφαλή χωρίζεται σε στοιχεία χρησιμοποιώντας ReqParseHead(κεφαλή)
  • Το αντικείμενο αρχικοποιείται InterpreteData και επιστρέφεται ένας δείκτης σε αυτό:

res := &InterpreteData{
	Head: head,
	Commands: commands,
	Body: body,
}
return res, nil

Αυτό το αντικείμενο χρησιμοποιείται σε server.go κύριο πακέτο.

Πελάτης

Το πακέτο πελάτη περιέχει τις λειτουργίες TCPConnect и TCPResponseData.

Λειτουργία

TCPConnect(s *types.Settings, data []byte, payload []byte)

λειτουργεί ως εξής:

  • Γίνεται σύνδεση με τη σύνδεση που καθορίζεται στο αντικείμενο ρυθμίσεων που πέρασε
    net.Dial("tcp", s.Host + ":" + s.Port)
  • Τα δεδομένα που διαβιβάζονται στην παράμετρο δεδομένων μεταδίδονται:
    conn.Write(data)
  • Η απάντηση διαβάζεται
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    και εκτυπώθηκε στην κονσόλα

    fmt.Println(string(resp[:n]))
  • Εάν μεταφερθεί ωφέλιμο φορτίο στη συνέχεια το μεταβιβάζει
    conn.Write(payload)

    και διαβάζει επίσης την απόκριση διακομιστή, εκτυπώνοντάς την στην κονσόλα

Λειτουργία

 TCPResponseData(conn net.Conn, bufsiz int) ([]byte, int, error)

δημιουργεί ένα buffer του καθορισμένου μεγέθους, διαβάζει την απόκριση του διακομιστή εκεί και επιστρέφει αυτό το buffer και τον αριθμό των byte που διαβάστηκαν, καθώς και ένα αντικείμενο σφάλματος.

Υπορουτίνα πελάτη

Χρησιμεύει για την αποστολή εντολών σε διακομιστές κόμβων, καθώς και για τη λήψη σύντομων στατιστικών στοιχείων και δοκιμών.

Μπορεί να δεχτεί τις ακόλουθες παραμέτρους: αρχείο διαμόρφωσης σε μορφή JSON, δεδομένα που θα σταλούν στον διακομιστή ως συμβολοσειρά, διαδρομή προς το αρχείο που θα σταλεί στο ωφέλιμο φορτίο, σημαία εξομοίωσης προγραμματιστή κόμβου, τύπος δεδομένων που μεταφέρονται ως αριθμητική τιμή.

  • Λήψη της διαμόρφωσης
    st := types.ParseConfig(*config)
  • Εάν περάσει η σημαία ΟΝΕ, ξεκινά οδοστρωτήρας
  • Εάν παρέχεται η σημαία f που υποδεικνύει τη διαδρομή προς το αρχείο, τότε φορτώνουμε τα δεδομένα του FDB και το περιεχόμενο αποστέλλεται στον διακομιστή
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Εάν το αρχείο δεν έχει καθοριστεί, τότε απλώς αποστέλλονται τα δεδομένα από τη σημαία -d:
    client.TCPConnect(st, []byte(*data), nil)

Όλα αυτά είναι μια απλοποιημένη αναπαράσταση που δείχνει τη δομή του πρωτοκόλλου. Κατά την ανάπτυξη, η απαραίτητη λειτουργικότητα προστίθεται στη δομή του.

Στο δεύτερο μέρος θα μιλήσω για δομές δεδομένων για μπλοκ και συναλλαγές, στο 3 για τον διακομιστή WebSocket για σύνδεση από JavaScript, στο 4 θα εξετάσω τον προγραμματιστή συγχρονισμού, στη συνέχεια μια μηχανή στοίβας που επεξεργάζεται bytecode από εισόδους και εξόδους, κρυπτογραφία και πισίνες για εξόδους.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο