From 5d01fc6c7219762734bfe477a1b987d5a67fdc6d Mon Sep 17 00:00:00 2001 From: Kleissner Date: Wed, 11 Aug 2021 22:36:47 +0200 Subject: [PATCH] Initial code to read/create the users blockchain database on the file system. --- Blockchain.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++ Peernet.go | 1 + 2 files changed, 109 insertions(+) diff --git a/Blockchain.go b/Blockchain.go index bdc7bb0..1d2fe43 100644 --- a/Blockchain.go +++ b/Blockchain.go @@ -6,8 +6,116 @@ Author: Peter Kleissner package core +import ( + "encoding/binary" + "encoding/hex" + "os" + "strconv" + + "github.com/PeernetOfficial/core/store" + "github.com/btcsuite/btcd/btcec" +) + // BlockchainHeight is the current count of blocks var BlockchainHeight = uint32(0) // BlockchainVersion is the version of the blockchain var BlockchainVersion = uint64(0) + +// filenameUserBlockchain is the filename/folder of the user's blockchain +const filenameUserBlockchain = "self.blockchain" + +// the key names in the key/value database are constant and must not collide with block numbers (i.e. they must be >64 bit) +const keyHeaderHeight = "header.height" +const keyHeaderVersion = "header.version" +const keyHeaderSignature = "header.signature" + +// BlockchainHeader is the virtual blockchain header. The data is not stored as single header record, but split up. +type BlockchainHeader struct { + Height uint64 + Version uint64 + PublicKey *btcec.PublicKey +} + +var userBlockchainDB store.Store +var userBlockchainHeader BlockchainHeader + +// initUserBlockchain initializes the users blockchain. It creates the blockchain file if it does not exist already. +// If it is corrupted, it will log the error and exit the process. +func initUserBlockchain() { + // open existing blockchain file or create new one + blockchain, err := store.NewPebbleStore(filenameUserBlockchain) + if err != nil { + Filters.LogError("initUserBlockchain", "error opening user blockchain: %s\n", err.Error()) + os.Exit(1) + } + + // verify header + var height, version uint64 + heightB, found1 := blockchain.Get([]byte(keyHeaderHeight)) + versionB, found2 := blockchain.Get([]byte(keyHeaderVersion)) + headerSignature, isSignature := blockchain.Get([]byte(keyHeaderSignature)) + + if found1 && len(heightB) != 8 || found2 && len(versionB) != 8 || isSignature && len(headerSignature) != 65 { + Filters.LogError("initUserBlockchain", "corrupt user blockchain database. Invalid header length. Height is '%s', version '%s' and signature '%s'.\n", hex.EncodeToString(heightB), hex.EncodeToString(versionB), hex.EncodeToString(headerSignature)) + os.Exit(1) + } + + if found1 && len(heightB) == 8 { + height = binary.BigEndian.Uint64(heightB) + } + if found2 && len(heightB) == 8 { + version = binary.BigEndian.Uint64(versionB) + } + + if isSignature { + // validate header signature + headerA := strconv.FormatUint(height, 10) + "/" + strconv.FormatUint(version, 10) + headerPublicKey, _, err := btcec.RecoverCompact(btcec.S256(), headerSignature[:], hashData([]byte(headerA))) + if err != nil { + Filters.LogError("initUserBlockchain", "corrupt user blockchain database. Error decoding signature. Height is '%s', version '%s', signature '%s'. %s\n", hex.EncodeToString(heightB), hex.EncodeToString(versionB), hex.EncodeToString(headerSignature), err.Error()) + os.Exit(1) + } + if !headerPublicKey.IsEqual(peerPublicKey) { + Filters.LogError("initUserBlockchain", "corrupt user blockchain database. Signature key mismatch. Height is '%s', version '%s', signature '%s'.\n", hex.EncodeToString(heightB), hex.EncodeToString(versionB), hex.EncodeToString(headerSignature)) + os.Exit(1) + } + + userBlockchainHeader = BlockchainHeader{Height: height, Version: version, PublicKey: headerPublicKey} + + } else if !found1 && !found2 && !isSignature { + // First run: create header signature! + height = 0 + version = 0 + signature, _ := blockchainCreateHeaderSignature(height, version) + if err := blockchain.Set([]byte(keyHeaderSignature), signature); err != nil { + Filters.LogError("initUserBlockchain", "initializing user blockchain: %s", err.Error()) + os.Exit(1) + } + + } else { + // header signature not present, but height/version is -> corrupt data. + Filters.LogError("initUserBlockchain", "corrupt user blockchain database. Height is '%s', version '%s' and signature '%s'.\n", hex.EncodeToString(heightB), hex.EncodeToString(versionB), hex.EncodeToString(headerSignature)) + os.Exit(1) + } + + userBlockchainDB = blockchain +} + +func blockchainCreateHeaderSignature(height, version uint64) (signature []byte, err error) { + headerA := strconv.FormatUint(height, 10) + "/" + strconv.FormatUint(version, 10) + signature, err = btcec.SignCompact(btcec.S256(), peerPrivateKey, hashData([]byte(headerA)), true) + + if err != nil { + Filters.LogError("blockchainCreateHeaderSignature", "creating signature of '%s': %s\n", headerA, err.Error()) + } + + return signature, err +} + +// UserBlockchainHeader returns the users blockchain header which stores the height and version number. +func UserBlockchainHeader() (header BlockchainHeader) { + return userBlockchainHeader +} + +// ---- low-level blockchain manipulation functions ---- diff --git a/Peernet.go b/Peernet.go index 2f9a403..6dd6667 100644 --- a/Peernet.go +++ b/Peernet.go @@ -10,6 +10,7 @@ package core func Init() { initFilters() initPeerID() + initUserBlockchain() initKademlia() initMessageSequence() initSeedList()