├── .gitignore ├── LINKS.md ├── bindata └── genesis.db ├── sql ├── 01_meta.sql └── 10_keys.sql ├── LICENSE.txt ├── chainparams.example.json ├── mining_pow.go ├── chainparams.go ├── main.go ├── blockwebserver.go ├── config.go ├── p2pcoordinator.go ├── crypto.go ├── util.go ├── bindata.go ├── README.md ├── db.go ├── cliactions.go ├── p2p.go └── blockchain.go /.gitignore: -------------------------------------------------------------------------------- 1 | daisy 2 | daisy.exe 3 | run.bat 4 | -------------------------------------------------------------------------------- /LINKS.md: -------------------------------------------------------------------------------- 1 | * [Do you need a blockchain?](http://eprint.iacr.org/2017/375.pdf) 2 | 3 | -------------------------------------------------------------------------------- /bindata/genesis.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivoras/daisy/HEAD/bindata/genesis.db -------------------------------------------------------------------------------- /sql/01_meta.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE _meta ( 2 | key VARCHAR NOT NULL PRIMARY KEY, 3 | value VARCHAR 4 | ); -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Released under the GPLv3 license. 2 | (c) 2017 Ivan Voras 3 | 4 | Alternative / commercial licenses available. -------------------------------------------------------------------------------- /chainparams.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "consensus_type": "PoA", 3 | "creator": "Ivan Voras ", 4 | "genesis_block_timestamp": "2018-08-16T12:49:32+02:00", 5 | "bootstrap_peers": [ "cosmos.ivoras.net:2017" ], 6 | "description": "The Mighty Blockchain" 7 | } 8 | -------------------------------------------------------------------------------- /sql/10_keys.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE _keys ( 2 | op CHAR NOT NULL, -- 'A' for adding, 'R' for revoking 3 | pubkey_hash VARCHAR NOT NULL, -- in the format 'type:hex' 4 | pubkey VARCHAR NOT NULL, -- hex-encoded 5 | sigkey_hash VARCHAR NOT NULL, -- same format as pubkey_hash 6 | signature VARCHAR NOT NULL, 7 | metadata VARCHAR, 8 | PRIMARY KEY (pubkey_hash, sigkey_hash) 9 | ); 10 | -------------------------------------------------------------------------------- /mining_pow.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "os" 7 | "time" 8 | ) 9 | 10 | // mineSqlite3Database mines a SQLite3 database file, by adjusting the user_version field 11 | // in the database header as a "nonce", and using SHA256 for the actual hashing. The file 12 | // must exist and must be closed. 13 | func mineSqlite3Database(fileName string, difficultyBits int) (string, error) { 14 | startNonce := uint32(time.Now().Unix()) 15 | f, err := os.OpenFile(fileName, os.O_RDWR, 0) 16 | if err != nil { 17 | return "", err 18 | } 19 | defer f.Close() 20 | b := make([]byte, 4) 21 | for nonce := startNonce + 1; nonce != startNonce; nonce++ { 22 | binary.LittleEndian.PutUint32(b, nonce) 23 | _, err := f.WriteAt(b, 60) // https://www.sqlite.org/fileformat2.html#database_header 24 | if err != nil { 25 | return "", err 26 | } 27 | f.Sync() 28 | hash, err := hashFileToBytes(fileName) 29 | if err != nil { 30 | return "", err 31 | } 32 | nZeroes := countStartZeroBits(hash) 33 | if nZeroes == difficultyBits { 34 | return hex.EncodeToString(hash), nil 35 | } 36 | } 37 | return "", nil 38 | } 39 | -------------------------------------------------------------------------------- /chainparams.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | ChainConsensusPoA = 0 5 | ChainConsensusPoW = 1 6 | ) 7 | 8 | // ChainParams holds blockchain configuration 9 | type ChainParams struct { 10 | // GenesisBlockHash is the SHA256 hash of the genesis block payload 11 | GenesisBlockHash string `json:"genesis_block_hash"` 12 | 13 | // GenesisBlockHashSignature is the signature of the genesis block's hash, with the key in the genesis block 14 | GenesisBlockHashSignature string `json:"genesis_block_hash_signature"` 15 | 16 | // GenesisBlockTimestamp is the timestamp of the genesis block 17 | GenesisBlockTimestamp string `json:"genesis_block_timestamp"` 18 | 19 | Creator string `json:"creator"` 20 | CreatorPublicKey string `json:"creator_public_key"` 21 | 22 | // List of host:port string specifying default peers for this blockchain. If empty, the defaults are used. 23 | BootstrapPeers []string `json:"bootstrap_peers"` 24 | 25 | // Consensus algorithm used: "PoA", "PoW" 26 | ConsensusTypeString string `json:"consensus_type"` 27 | ConsensusType int `json:"-"` 28 | 29 | // Description of the blockchain (e.g. its purpose) 30 | Description string `json:"description"` 31 | } 32 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | const ( 12 | eventQuit = iota 13 | ) 14 | 15 | type sysEventMessage struct { 16 | event int 17 | idata int 18 | } 19 | 20 | // Passes messages such as eventQuit 21 | var sysEventChannel = make(chan sysEventMessage, 5) 22 | 23 | func main() { 24 | rand.Seed(p2pEphemeralID + getNowUTC()) // Initialise weak RNG with strong RNG 25 | log.Println("Starting up", p2pClientVersionString, "...") 26 | sigChannel := make(chan os.Signal, 1) 27 | signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM) 28 | 29 | configInit() 30 | if processPreBlockchainActions() { 31 | return 32 | } 33 | dbInit() 34 | cryptoInit() 35 | blockchainInit(true) 36 | if processActions() { 37 | return 38 | } 39 | log.Printf("Ephemeral ID: %x\n", p2pEphemeralID) 40 | go p2pCoordinator.Run() 41 | go p2pServer() 42 | go p2pClient() 43 | go blockWebServer() 44 | 45 | for { 46 | select { 47 | case msg := <-sysEventChannel: 48 | switch msg.event { 49 | case eventQuit: 50 | log.Println("Exiting") 51 | os.Exit(msg.idata) 52 | } 53 | case sig := <-sigChannel: 54 | switch sig { 55 | case syscall.SIGINT: 56 | sysEventChannel <- sysEventMessage{event: eventQuit, idata: 0} 57 | log.Println("^C detected") 58 | case syscall.SIGTERM: 59 | sysEventChannel <- sysEventMessage{event: eventQuit, idata: 0} 60 | log.Println("Quit signal detected") 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /blockwebserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func blockWebSendBlock(w http.ResponseWriter, r *http.Request) { 14 | vars := mux.Vars(r) 15 | 16 | blockHeight, err := strconv.Atoi(vars["height"]) 17 | if err != nil { 18 | log.Println(vars) 19 | w.WriteHeader(http.StatusBadRequest) 20 | return 21 | } 22 | 23 | blockFilename := blockchainGetFilename(blockHeight) 24 | if _, err := os.Stat(blockFilename); os.IsNotExist(err) { 25 | w.WriteHeader(http.StatusNotFound) 26 | log.Println("Block file not found:", blockFilename) 27 | return 28 | } 29 | 30 | log.Println("HTTP serving block", blockHeight, "to", r.RemoteAddr) 31 | w.Header().Set("Content-Type", "application/x-sqlite3") 32 | w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%08x.db\"", blockHeight)) 33 | http.ServeFile(w, r, blockFilename) 34 | // log.Println("Done serving block", blockHeight) 35 | } 36 | 37 | func blockWebSendChainParams(w http.ResponseWriter, r *http.Request) { 38 | log.Println("HTTP serving chainparams.json to", r.RemoteAddr) 39 | 40 | w.Header().Set("Content-Type", "application/json") 41 | w.Header().Set("Content-Disposition", "attachment; filename=\"chainparams.json\"") 42 | _, err := w.Write(jsonifyWhateverToBytes(chainParams)) 43 | if err != nil { 44 | log.Println(err) 45 | } 46 | } 47 | 48 | func blockWebServer() { 49 | r := mux.NewRouter() 50 | r.HandleFunc("/block/{height}", blockWebSendBlock) 51 | r.HandleFunc("/chainparams.json", blockWebSendChainParams) 52 | 53 | serverAddress := fmt.Sprintf(":%d", cfg.httpPort) 54 | 55 | log.Println("HTTP listening on", serverAddress) 56 | err := http.ListenAndServe(serverAddress, r) 57 | if err != nil { 58 | panic(err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "os/user" 11 | ) 12 | 13 | // DefaultP2PPort is the default TCP port for p2p connections 14 | const DefaultP2PPort = 2017 15 | 16 | // DefaultBlockWebServerPort is the default TCP port for the HTTP server 17 | const DefaultBlockWebServerPort = 2018 18 | 19 | // DefaultConfigFile is the default configuration filename 20 | const DefaultConfigFile = "/etc/daisy/config.json" 21 | 22 | // DefaultDataDir is the default data directory 23 | const DefaultDataDir = ".daisy" 24 | 25 | var cfg struct { 26 | configFile string 27 | P2pPort int `json:"p2p_port"` 28 | DataDir string `json:"data_dir"` 29 | httpPort int `json:"http_port"` 30 | showHelp bool 31 | faster bool 32 | p2pBlockInline bool 33 | } 34 | 35 | // Initialises defaults, parses command line 36 | func configInit() { 37 | u, err := user.Current() 38 | if err != nil { 39 | log.Panicln(err) 40 | } 41 | cfg.DataDir = fmt.Sprintf("%s/%s", u.HomeDir, DefaultDataDir) 42 | 43 | // Init defaults 44 | cfg.P2pPort = DefaultP2PPort 45 | cfg.httpPort = DefaultBlockWebServerPort 46 | 47 | // Config file is parsed first 48 | for i, arg := range os.Args { 49 | if arg == "-conf" || arg == "--conf" { 50 | if i+1 >= len(os.Args) { 51 | log.Fatal("-conf requires filename argument") 52 | } 53 | cfg.configFile = os.Args[i+1] 54 | } 55 | } 56 | if cfg.configFile != "" { 57 | loadConfigFile() 58 | } 59 | 60 | // Then override the configuration with command-line flags 61 | flag.IntVar(&cfg.P2pPort, "port", cfg.P2pPort, "P2P port") 62 | flag.IntVar(&cfg.httpPort, "http-port", cfg.httpPort, "HTTP port") 63 | flag.StringVar(&cfg.DataDir, "dir", cfg.DataDir, "Data directory") 64 | flag.BoolVar(&cfg.showHelp, "help", false, "Shows CLI usage information") 65 | flag.BoolVar(&cfg.faster, "faster", false, "Be faster when starting up") 66 | flag.BoolVar(&cfg.p2pBlockInline, "p2pblockinline", false, "Send blocks to peers inline instead of over HTTP") 67 | flag.Parse() 68 | 69 | if cfg.showHelp { 70 | actionHelp() 71 | os.Exit(0) 72 | } 73 | 74 | if _, err := os.Stat(cfg.DataDir); err != nil { 75 | log.Println("Data directory", cfg.DataDir, "doesn't exist, creating.") 76 | err = os.Mkdir(cfg.DataDir, 0700) 77 | if err != nil { 78 | log.Panicln(err) 79 | } 80 | } 81 | if cfg.P2pPort < 1 || cfg.P2pPort > 65535 { 82 | log.Fatal("Invalid TCP port", cfg.P2pPort) 83 | } 84 | } 85 | 86 | // Loads the JSON config file. 87 | func loadConfigFile() { 88 | data, err := ioutil.ReadFile(cfg.configFile) 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | err = json.Unmarshal(data, &cfg) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /p2pcoordinator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | ) 9 | 10 | // Messages to the p2p controller goroutine 11 | const ( 12 | p2pCtrlSearchForBlocks = iota 13 | p2pCtrlHaveNewBlock 14 | p2pCtrlConnectPeers 15 | ) 16 | 17 | type p2pCtrlMessage struct { 18 | msgType int 19 | payload interface{} 20 | } 21 | 22 | var p2pCtrlChannel = make(chan p2pCtrlMessage, 8) 23 | 24 | // Data related to the (single instance of) the global p2p coordinator. This is also a 25 | // single-threaded object, its fields and methods are only expected to be accessed from 26 | // the Run() goroutine. 27 | type p2pCoordinatorType struct { 28 | timeTicks chan int 29 | lastTickBlockchainHeight int 30 | recentlyRequestedBlocks *StringSetWithExpiry 31 | lastReconnectTime time.Time 32 | badPeers *StringSetWithExpiry 33 | } 34 | 35 | // XXX: singletons in go? 36 | var p2pCoordinator = p2pCoordinatorType{ 37 | recentlyRequestedBlocks: NewStringSetWithExpiry(5 * time.Second), 38 | lastReconnectTime: time.Now(), 39 | timeTicks: make(chan int), 40 | badPeers: NewStringSetWithExpiry(15 * time.Minute), 41 | } 42 | 43 | func (co *p2pCoordinatorType) Run() { 44 | co.lastTickBlockchainHeight = dbGetBlockchainHeight() 45 | ticker := time.NewTicker(10 * time.Second) 46 | defer ticker.Stop() 47 | for { 48 | select { 49 | case msg := <-p2pCtrlChannel: 50 | switch msg.msgType { 51 | case p2pCtrlSearchForBlocks: 52 | co.handleSearchForBlocks(msg.payload.(*p2pConnection)) 53 | case p2pCtrlConnectPeers: 54 | co.handleConnectPeers(msg.payload.([]string)) 55 | } 56 | case <-ticker.C: 57 | co.handleTimeTick() 58 | } 59 | } 60 | } 61 | 62 | // Retrieves block hashes from a node which apparently has more blocks than we do. 63 | // ToDo: This is a simplistic version. Make it better by introducing quorums. 64 | func (co *p2pCoordinatorType) handleSearchForBlocks(p2pcStart *p2pConnection) { 65 | msg := p2pMsgGetBlockHashesStruct{ 66 | p2pMsgHeader: p2pMsgHeader{ 67 | P2pID: p2pEphemeralID, 68 | Root: chainParams.GenesisBlockHash, 69 | Msg: p2pMsgGetBlockHashes, 70 | }, 71 | MinBlockHeight: dbGetBlockchainHeight(), 72 | MaxBlockHeight: p2pcStart.chainHeight, 73 | } 74 | log.Printf("Searching for blocks from %d to %d", msg.MinBlockHeight, msg.MaxBlockHeight) 75 | p2pcStart.chanToPeer <- msg 76 | } 77 | 78 | func (co *p2pCoordinatorType) handleConnectPeers(addresses []string) { 79 | localAddresses := getLocalAddresses() 80 | 81 | for _, address := range addresses { 82 | host, _, err := splitAddress(address) 83 | if err != nil { 84 | log.Println(address, err) 85 | continue 86 | } 87 | canonicalAddress := fmt.Sprintf("%s:%d", host, DefaultP2PPort) 88 | if p2pPeers.HasAddress(canonicalAddress) || co.badPeers.Has(canonicalAddress) { 89 | continue 90 | } 91 | addr, err := net.ResolveTCPAddr("tcp", canonicalAddress) 92 | if err != nil { 93 | continue 94 | } 95 | if inStrings(addr.IP.String(), localAddresses) { 96 | continue 97 | } 98 | // Detect if there's a canonical peer on the other side, somewhat brute-forceish 99 | conn, err := net.DialTCP("tcp", nil, addr) 100 | if err != nil { 101 | return 102 | } 103 | p2pc, err := p2pSetupPeer(addr.String(), conn) 104 | if err != nil { 105 | log.Println("handleConnectPeers:", err) 106 | continue 107 | } 108 | go p2pc.handleConnection() 109 | log.Println("Detected canonical peer at", canonicalAddress) 110 | dbSavePeer(canonicalAddress) 111 | } 112 | } 113 | 114 | // Executed periodically to perform time-dependant actions. Do not rely on the 115 | // time period to be predictable or precise. 116 | func (co *p2pCoordinatorType) handleTimeTick() { 117 | newHeight := dbGetBlockchainHeight() 118 | if newHeight > co.lastTickBlockchainHeight { 119 | log.Println("New blocks detected. New max height:", newHeight) 120 | co.floodPeersWithNewBlocks(co.lastTickBlockchainHeight, newHeight) 121 | co.lastTickBlockchainHeight = newHeight 122 | } 123 | if time.Since(co.lastReconnectTime) >= 10*time.Minute { 124 | co.lastReconnectTime = time.Now() 125 | p2pPeers.saveConnectablePeers() 126 | co.connectDbPeers() 127 | } 128 | p2pPeers.tryPeersConnectable() 129 | } 130 | 131 | func (co *p2pCoordinatorType) floodPeersWithNewBlocks(minHeight, maxHeight int) { 132 | blockHashes := dbGetHeightHashes(minHeight, maxHeight) 133 | msg := p2pMsgBlockHashesStruct{ 134 | p2pMsgHeader: p2pMsgHeader{ 135 | P2pID: p2pEphemeralID, 136 | Root: chainParams.GenesisBlockHash, 137 | Msg: p2pMsgBlockHashes, 138 | }, 139 | Hashes: blockHashes, 140 | } 141 | p2pPeers.lock.With(func() { 142 | for p2pc := range p2pPeers.peers { 143 | p2pc.chanToPeer <- msg 144 | } 145 | }) 146 | } 147 | 148 | func (co *p2pCoordinatorType) connectDbPeers() { 149 | peers := dbGetSavedPeers() 150 | for peer := range peers { 151 | if p2pPeers.HasAddress(peer) { 152 | continue 153 | } 154 | if co.badPeers.Has(peer) { 155 | continue 156 | } 157 | p2pc, err := p2pConnectPeer(peer) 158 | if err != nil { 159 | continue 160 | } 161 | go p2pc.handleConnection() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /crypto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "crypto/x509" 9 | "encoding/asn1" 10 | "encoding/hex" 11 | "fmt" 12 | "log" 13 | "math/big" 14 | "unsafe" 15 | ) 16 | 17 | var bigIntZero = big.NewInt(0) 18 | 19 | type ecdsaSignature struct { 20 | R *big.Int 21 | S *big.Int 22 | } 23 | 24 | func cryptoInit() { 25 | if dbNumPrivateKeys() == 0 { 26 | log.Println("Generating the initial wallet keypair...") 27 | generatePrivateKey(-1) 28 | log.Println("Generated.") 29 | } 30 | } 31 | 32 | // Generates a keypair and writes it to the private database 33 | func generatePrivateKey(height int) *ecdsa.PrivateKey { 34 | keys, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | privateKey, err := x509.MarshalECPrivateKey(keys) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | publicKey, err := x509.MarshalPKIXPublicKey(&keys.PublicKey) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | publicKeyHash := getPubKeyHash(publicKey) 47 | 48 | dbWritePublicKey(publicKey, publicKeyHash, height) 49 | dbWritePrivateKey(privateKey, publicKeyHash) 50 | 51 | return keys 52 | } 53 | 54 | // Returns a hex string prefixed with the hash type and ":", 55 | // e.g. "1:b12d4ac..." 56 | func getPubKeyHash(b []byte) string { 57 | hash := sha256.Sum256(b) 58 | return "1:" + hex.EncodeToString(hash[:]) 59 | } 60 | 61 | // getAPrivateKey returns a random keypair read from the database 62 | // This is mostly useful when the database has only one keypair ;) 63 | func cryptoGetAPrivateKey() (*ecdsa.PrivateKey, string, error) { 64 | privateKeyBytes, publicKeyHash, err := dbGetAPrivateKey() 65 | if err != nil { 66 | return nil, "", err 67 | } 68 | dbPubKey, err := dbGetPublicKey(publicKeyHash) 69 | if err != nil { 70 | return nil, "", err 71 | } 72 | keys, err := x509.ParseECPrivateKey(privateKeyBytes) 73 | if err != nil { 74 | return nil, "", err 75 | } 76 | pubKey, err := x509.ParsePKIXPublicKey(dbPubKey.publicKeyBytes) 77 | if err != nil { 78 | log.Panicln(err) 79 | return nil, "", err 80 | } 81 | keys.PublicKey = *pubKey.(*ecdsa.PublicKey) 82 | if !elliptic.P256().IsOnCurve(keys.PublicKey.X, keys.PublicKey.Y) { 83 | return nil, "", fmt.Errorf("Elliptic key verification error for %s", publicKeyHash) 84 | } 85 | 86 | // Check if we can get the right public key hash back again 87 | testPublicKeyBytes, err := x509.MarshalPKIXPublicKey(&keys.PublicKey) 88 | if err != nil { 89 | log.Panicln(err) 90 | } 91 | testPublicKeyHash := getPubKeyHash(testPublicKeyBytes) 92 | if testPublicKeyHash != publicKeyHash { 93 | return nil, "", fmt.Errorf("Loaded keypair %s, but the calculated public key hash doesn't match: %s", publicKeyHash, testPublicKeyHash) 94 | } 95 | 96 | return keys, publicKeyHash, nil 97 | } 98 | 99 | // Decodes the given bytes into a public key 100 | func cryptoDecodePublicKeyBytes(key []byte) (*ecdsa.PublicKey, error) { 101 | ikey, err := x509.ParsePKIXPublicKey(key) 102 | return ikey.(*ecdsa.PublicKey), err 103 | } 104 | 105 | // Returns a hash of the given public key 106 | func cryptoMustGetPublicKeyHash(key *ecdsa.PublicKey) string { 107 | publicKeyBytes, err := x509.MarshalPKIXPublicKey(key) 108 | if err != nil { 109 | log.Fatalln(err) 110 | } 111 | return getPubKeyHash(publicKeyBytes) 112 | } 113 | 114 | // Signs the given public key hash with the given private key and returns the signature as a byte blob. 115 | func cryptoSignPublicKeyHash(myPrivateKey *ecdsa.PrivateKey, publicKeyHash string) ([]byte, error) { 116 | if publicKeyHash[1] != ':' { 117 | return nil, fmt.Errorf("cryptoSignPublicKeyHash() expects a public key in the \"type:hex\" format, not \"%s\"", publicKeyHash) 118 | } 119 | publicKeyHashBytes, err := hex.DecodeString(publicKeyHash[2:]) 120 | if err != nil { 121 | return nil, err 122 | } 123 | return cryptoSignBytes(myPrivateKey, publicKeyHashBytes) 124 | } 125 | 126 | // Returns nil (i.e. "no error") if the verification succeeds 127 | func cryptoVerifyPublicKeyHashSignature(publicKey *ecdsa.PublicKey, publicKeyHash string, signature []byte) error { 128 | if publicKeyHash[1] != ':' { 129 | return fmt.Errorf("cryptoVerifyPublicKeyHash() expects a public key in the \"type:hex\" format, not \"%s\"", publicKeyHash) 130 | } 131 | publicKeyHashBytes, err := hex.DecodeString(publicKeyHash[2:]) 132 | if err != nil { 133 | return err 134 | } 135 | return cryptoVerifyBytes(publicKey, publicKeyHashBytes, signature) 136 | } 137 | 138 | // Signs a hex-encoded byte blob. and returns a hex-encoded signature byte blob 139 | func cryptoSignHex(myPrivateKey *ecdsa.PrivateKey, hash string) (string, error) { 140 | hashBytes, err := hex.DecodeString(hash) 141 | if err != nil { 142 | return "", err 143 | } 144 | signatureBytes, err := cryptoSignBytes(myPrivateKey, hashBytes) 145 | if err != nil { 146 | return "", err 147 | } 148 | return hex.EncodeToString(signatureBytes), nil 149 | } 150 | 151 | // Verifies the given signature of a hash, both hex-encoded. Returns nil if everything's ok. 152 | func cryptoVerifyHex(publicKey *ecdsa.PublicKey, hash string, signature string) error { 153 | hashBytes, err := hex.DecodeString(hash) 154 | if err != nil { 155 | return err 156 | } 157 | signatureBytes, err := hex.DecodeString(signature) 158 | if err != nil { 159 | return err 160 | } 161 | return cryptoVerifyBytes(publicKey, hashBytes, signatureBytes) 162 | } 163 | 164 | // Signs a hex-encoded byte blob. and returns a signature byte blob 165 | func cryptoSignHexBytes(myPrivateKey *ecdsa.PrivateKey, hash string) ([]byte, error) { 166 | hashBytes, err := hex.DecodeString(hash) 167 | if err != nil { 168 | return nil, err 169 | } 170 | signatureBytes, err := cryptoSignBytes(myPrivateKey, hashBytes) 171 | if err != nil { 172 | return nil, err 173 | } 174 | return signatureBytes, nil 175 | } 176 | 177 | // Verifies the given signature of a hash. Returns nil if everything's ok. 178 | func cryptoVerifyHexBytes(publicKey *ecdsa.PublicKey, hash string, signatureBytes []byte) error { 179 | hashBytes, err := hex.DecodeString(hash) 180 | if err != nil { 181 | return err 182 | } 183 | return cryptoVerifyBytes(publicKey, hashBytes, signatureBytes) 184 | 185 | } 186 | 187 | // Signes a byte blob with the given private key. 188 | func cryptoSignBytes(myPrivateKey *ecdsa.PrivateKey, hash []byte) ([]byte, error) { 189 | var sig ecdsaSignature 190 | var err error 191 | var signature []byte 192 | for { 193 | sig.R, sig.S, err = ecdsa.Sign(rand.Reader, myPrivateKey, hash) 194 | if err != nil { 195 | return nil, fmt.Errorf("sign: %v", err) 196 | } 197 | signature, err = asn1.Marshal(sig) 198 | if err != nil { 199 | return nil, fmt.Errorf("marshal: %v", err) 200 | } 201 | if sig.R.Cmp(bigIntZero) != 0 { 202 | break 203 | } 204 | } 205 | return signature, nil 206 | } 207 | 208 | // Verifies a signed byte blob 209 | func cryptoVerifyBytes(publicKey *ecdsa.PublicKey, hash []byte, signature []byte) error { 210 | var sig ecdsaSignature 211 | _, err := asn1.Unmarshal(signature, &sig) 212 | if err != nil { 213 | return err 214 | } 215 | if ecdsa.Verify(publicKey, hash, sig.R, sig.S) { 216 | // Verification succeded 217 | return nil 218 | } 219 | return fmt.Errorf("Signature verification failed") 220 | } 221 | 222 | // Returns a random positive 63-bit integer 223 | func randInt63() int64 { 224 | buf := make([]byte, 8) 225 | n, err := rand.Read(buf) 226 | if err != nil { 227 | log.Panic(err) 228 | } 229 | if n != len(buf) { 230 | log.Panic("Cannot read 8 random bytes") 231 | } 232 | v := *(*int64)(unsafe.Pointer(&buf[0])) 233 | if v >= 0 { 234 | return v 235 | } 236 | return -v 237 | } 238 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | // WithMutex extends the Mutex type with the convenient .With(func) function 19 | type WithMutex struct { 20 | sync.Mutex 21 | } 22 | 23 | // With executes the given function with the mutex locked 24 | func (m *WithMutex) With(f func()) { 25 | m.Mutex.Lock() 26 | f() 27 | m.Mutex.Unlock() 28 | } 29 | 30 | // Converts the given Unix timestamp to time.Time 31 | func unixTimeStampToUTCTime(ts int) time.Time { 32 | return time.Unix(int64(ts), 0) 33 | } 34 | 35 | // Gets the current Unix timestamp in UTC 36 | func getNowUTC() int64 { 37 | return time.Now().UTC().Unix() 38 | } 39 | 40 | // Mashals the given map of strings to JSON 41 | func stringMap2JsonBytes(m map[string]string) []byte { 42 | b, err := json.Marshal(m) 43 | if err != nil { 44 | log.Panicln("Cannot json-ise the map:", err) 45 | } 46 | return b 47 | } 48 | 49 | // Returns a hex-encoded hash of the given byte slice 50 | func hashBytesToHexString(b []byte) string { 51 | hash := sha256.Sum256(b) 52 | return hex.EncodeToString(hash[:]) 53 | } 54 | 55 | // Returns a hex-encoded hash of the given file 56 | func hashFileToHexString(fileName string) (string, error) { 57 | file, err := os.Open(fileName) 58 | if err != nil { 59 | return "", err 60 | } 61 | defer func() { 62 | err = file.Close() 63 | if err != nil { 64 | log.Printf("hashFileToHexString file.Close: %v", err) 65 | } 66 | }() 67 | hash := sha256.New() 68 | _, err = io.Copy(hash, file) 69 | if err != nil { 70 | return "", err 71 | } 72 | return hex.EncodeToString(hash.Sum(nil)), nil 73 | } 74 | 75 | func hashFileToBytes(fileName string) ([]byte, error) { 76 | file, err := os.Open(fileName) 77 | if err != nil { 78 | return nil, err 79 | } 80 | defer func() { 81 | err = file.Close() 82 | if err != nil { 83 | log.Printf("hashFileToHexString file.Close: %v", err) 84 | } 85 | }() 86 | hash := sha256.New() 87 | _, err = io.Copy(hash, file) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return hash.Sum(nil), nil 92 | } 93 | 94 | func mustDecodeHex(hexs string) []byte { 95 | b, err := hex.DecodeString(hexs) 96 | if err != nil { 97 | log.Panic("mustDecodeHex:", err) 98 | } 99 | return b 100 | } 101 | 102 | // StrIfMap is a convenient data type for dealing with maps of strings to interface{} 103 | type StrIfMap map[string]interface{} 104 | 105 | // GetString returns a string from this map. 106 | func (m StrIfMap) GetString(key string) (string, error) { 107 | var ok bool 108 | var ii interface{} 109 | if ii, ok = m[key]; !ok { 110 | return "", fmt.Errorf("No '%s' key in map", key) 111 | } 112 | var val string 113 | if val, ok = ii.(string); !ok { 114 | return "", fmt.Errorf("The '%s' key in map is not a string", key) 115 | } 116 | return val, nil 117 | } 118 | 119 | // GetInt64 returns an Int64 from this map. 120 | func (m StrIfMap) GetInt64(key string) (int64, error) { 121 | var ok bool 122 | var ii interface{} 123 | if ii, ok = m[key]; !ok { 124 | return 0, fmt.Errorf("No '%s' key in map", key) 125 | } 126 | var val float64 127 | if val, ok = ii.(float64); !ok { 128 | return 0, fmt.Errorf("The '%s' key in map is not an int64", key) 129 | } 130 | return int64(val), nil 131 | } 132 | 133 | // GetInt returns an int from this map. 134 | func (m StrIfMap) GetInt(key string) (int, error) { 135 | var ok bool 136 | var ii interface{} 137 | if ii, ok = m[key]; !ok { 138 | return 0, fmt.Errorf("No '%s' key in map", key) 139 | } 140 | var val float64 141 | if val, ok = ii.(float64); !ok { 142 | return 0, fmt.Errorf("The '%s' key in map is not an int64", key) 143 | } 144 | return int(val), nil 145 | } 146 | 147 | // GetIntStringMap returns a map of integers to strings from this map. 148 | func (m StrIfMap) GetIntStringMap(key string) (map[int]string, error) { 149 | var ok bool 150 | var ii interface{} 151 | if ii, ok = m[key]; !ok { 152 | return nil, fmt.Errorf("No '%s' key in map", key) 153 | } 154 | var val map[string]interface{} 155 | if val, ok = ii.(map[string]interface{}); !ok { 156 | return nil, fmt.Errorf("The '%s' key in map is not a map[string]interface{}", key) 157 | } 158 | var val2 = make(map[int]string) 159 | for k, v := range val { 160 | i, err := strconv.Atoi(k) 161 | if err != nil { 162 | return nil, err 163 | } 164 | var s string 165 | if s, ok = v.(string); !ok { 166 | return nil, fmt.Errorf("The value in the hashes map is not a string?") 167 | } 168 | val2[i] = s 169 | } 170 | return val2, nil 171 | } 172 | 173 | // GetStringList returns a slice of strings from this map 174 | func (m StrIfMap) GetStringList(key string) ([]string, error) { 175 | var ok bool 176 | var ii interface{} 177 | if ii, ok = m[key]; !ok { 178 | return nil, fmt.Errorf("No '%s' key in map", key) 179 | } 180 | var ilist []interface{} 181 | if ilist, ok = ii.([]interface{}); !ok { 182 | return nil, fmt.Errorf("The '%s' key in map is not an array of interface{}", key) 183 | } 184 | var result []string 185 | for n, is := range ilist { 186 | var s string 187 | if s, ok = is.(string); !ok { 188 | return nil, fmt.Errorf("Element of %d the '%s' key is not a string", n, key) 189 | } 190 | result = append(result, s) 191 | } 192 | return result, nil 193 | } 194 | 195 | // StringSetWithExpiry is a set of strings whose entries disappear after a given time. 196 | type StringSetWithExpiry struct { 197 | data map[string]time.Time 198 | age time.Duration 199 | lock WithMutex 200 | } 201 | 202 | // NewStringSetWithExpiry returns a new StringSetWithExpiry, with the given expiry duration. 203 | func NewStringSetWithExpiry(d time.Duration) *StringSetWithExpiry { 204 | ss := StringSetWithExpiry{data: make(map[string]time.Time), age: d} 205 | return &ss 206 | } 207 | 208 | // Add adds the given string to the set 209 | func (ss *StringSetWithExpiry) Add(s string) { 210 | ss.lock.With(func() { 211 | ss.data[s] = time.Now() 212 | }) 213 | ss.CheckExpire() 214 | } 215 | 216 | // CheckExpire walks the set and removes the entries which have expired. 217 | func (ss *StringSetWithExpiry) CheckExpire() int { 218 | count := 0 219 | ss.lock.With(func() { 220 | var toExpire []string 221 | for s, t := range ss.data { 222 | d := time.Since(t) 223 | if d >= ss.age { 224 | toExpire = append(toExpire, s) 225 | } 226 | } 227 | for _, s := range toExpire { 228 | delete(ss.data, s) 229 | } 230 | count = len(toExpire) 231 | }) 232 | return count 233 | } 234 | 235 | // Has tests if a string is present and not expired in this set. 236 | func (ss *StringSetWithExpiry) Has(s string) bool { 237 | var ok bool 238 | ss.lock.With(func() { 239 | var t time.Time 240 | t, ok = ss.data[s] 241 | if ok { 242 | if time.Since(t) >= ss.age { 243 | // It's there but it's expired. 244 | ok = false 245 | } 246 | } 247 | }) 248 | return ok 249 | } 250 | 251 | // TestAndSet atomically tests if the string s is present in the set and adds it if it isn't. 252 | // Returns true iff it was in the set. 253 | func (ss *StringSetWithExpiry) TestAndSet(s string) bool { 254 | var ok bool 255 | ss.lock.With(func() { 256 | var t time.Time 257 | t, ok = ss.data[s] 258 | if !ok { 259 | ss.data[s] = time.Now() 260 | } else { 261 | if time.Since(t) >= ss.age { 262 | // It's there but it's expired. 263 | ok = false 264 | } 265 | } 266 | }) 267 | return ok 268 | } 269 | 270 | // Convert whatever to a JSON string 271 | func jsonifyWhatever(i interface{}) string { 272 | jsonb, err := json.Marshal(i) 273 | if err != nil { 274 | log.Panic(err) 275 | } 276 | return string(jsonb) 277 | } 278 | 279 | // Convert whatever to JSON bytes 280 | func jsonifyWhateverToBytes(i interface{}) []byte { 281 | jsonb, err := json.Marshal(i) 282 | if err != nil { 283 | log.Panic(err) 284 | } 285 | return jsonb 286 | } 287 | 288 | // Splits an address string in the form of "host:port" into its separate host and port parts 289 | func splitAddress(address string) (string, int, error) { 290 | i := strings.LastIndex(address, ":") // Not using strings.Split because of IPv6 291 | var host string 292 | var port int 293 | var err error 294 | if i > -1 { 295 | host = address[0:i] 296 | port, err = strconv.Atoi(address[i+1:]) 297 | if err != nil { 298 | return "", 0, err 299 | } 300 | } else { 301 | host = address 302 | } 303 | return host, port, nil 304 | } 305 | 306 | // Returns a list of local IP addresses 307 | func getLocalAddresses() []string { 308 | addresses := []string{} 309 | ifaces, err := net.Interfaces() 310 | if err != nil { 311 | log.Println(err) 312 | return addresses 313 | } 314 | for _, i := range ifaces { 315 | if strings.HasPrefix(i.Name, "lo") { 316 | continue 317 | } 318 | addrs, err := i.Addrs() 319 | if err != nil { 320 | log.Println(err) 321 | continue 322 | } 323 | for _, addr := range addrs { 324 | var ip net.IP 325 | switch v := addr.(type) { 326 | case *net.IPNet: 327 | ip = v.IP 328 | case *net.IPAddr: 329 | ip = v.IP 330 | } 331 | if strings.HasPrefix(ip.String(), "127.") { 332 | continue 333 | } 334 | addresses = append(addresses, ip.String()) 335 | } 336 | } 337 | return addresses 338 | } 339 | 340 | // Returns true if s is in list 341 | func inStrings(s string, list []string) bool { 342 | for _, x := range list { 343 | if s == x { 344 | return true 345 | } 346 | } 347 | return false 348 | } 349 | 350 | // isDirEmpty returns true if a directory is empty. 351 | func isDirEmpty(name string) (bool, error) { 352 | f, err := os.Open(name) 353 | if err != nil { 354 | return false, err 355 | } 356 | defer f.Close() 357 | 358 | _, err = f.Readdirnames(1) // Or f.Readdir(1) 359 | if err == io.EOF { 360 | return true, nil 361 | } 362 | return false, err // Either not empty or error, suits both cases 363 | } 364 | 365 | func fileExists(name string) bool { 366 | if _, err := os.Stat(name); err == nil { 367 | return true 368 | } 369 | return false 370 | } 371 | 372 | // Copy the src file to dst. Any existing file will be overwritten and will not 373 | // copy file attributes. 374 | func copyFile(src, dst string) error { 375 | in, err := os.Open(src) 376 | if err != nil { 377 | return err 378 | } 379 | defer in.Close() 380 | 381 | out, err := os.Create(dst) 382 | if err != nil { 383 | return err 384 | } 385 | defer out.Close() 386 | 387 | _, err = io.Copy(out, in) 388 | if err != nil { 389 | return err 390 | } 391 | return out.Close() 392 | } 393 | 394 | func countStartZeroBits(b []byte) int { 395 | nBits := 0 396 | for i := 0; i < len(b); i++ { 397 | if b[i] == 0 { 398 | nBits += 8 399 | } else { 400 | for z := uint(7); z >= 0; z-- { 401 | if b[i]&(1< 0 209 | } 210 | 211 | // Panics if the system databases are not open 212 | func assertSysDbOpen() { 213 | if mainDb == nil || privateDb == nil { 214 | log.Panic("Databases are not open") 215 | } 216 | } 217 | 218 | // Checks if a public key is present in the system databases 219 | func dbPublicKeyExists(hash string) bool { 220 | var count int 221 | if err := mainDb.QueryRow("SELECT COUNT(*) FROM pubkeys WHERE pubkey_hash=?", hash).Scan(&count); err != nil { 222 | log.Panicln(err) 223 | } 224 | return count > 0 225 | } 226 | 227 | // Writes a public key to the system databases 228 | func dbWritePublicKey(pubkey []byte, hash string, blockHeight int) { 229 | _, err := mainDb.Exec("INSERT INTO pubkeys(pubkey_hash, pubkey, state, time_added, block_height) VALUES (?, ?, ?, ?, ?)", 230 | hash, hex.EncodeToString(pubkey), "A", time.Now().Unix(), blockHeight) 231 | if err != nil { 232 | log.Panic(err) 233 | } 234 | } 235 | 236 | // Marks a public key as revoked. 237 | func dbRevokePublicKey(hash string) { 238 | _, err := mainDb.Exec("UPDATE pubkeys SET time_revoked=? WHERE pubkey_hash=?", getNowUTC(), hash) 239 | if err != nil { 240 | log.Panic(err) 241 | } 242 | } 243 | 244 | // Writes the given private key byte blob to the system databases 245 | func dbWritePrivateKey(privkey []byte, hash string) { 246 | _, err := privateDb.Exec("INSERT INTO privkeys(pubkey_hash, privkey, time_added) VALUES (?, ?, ?)", hash, hex.EncodeToString(privkey), time.Now().Unix()) 247 | if err != nil { 248 | log.Panic(err) 249 | } 250 | } 251 | 252 | // Returns a list of public keys hashes corresponding to private keys in the system databases 253 | func dbGetMyPublicKeyHashes() []string { 254 | var result []string 255 | rows, err := privateDb.Query("SELECT pubkey_hash FROM privkeys") 256 | if err != nil { 257 | log.Panic(err) 258 | } 259 | for rows.Next() { 260 | var pubkeyHash string 261 | err := rows.Scan(&pubkeyHash) 262 | if err != nil { 263 | log.Panic(err) 264 | } 265 | result = append(result, pubkeyHash) 266 | } 267 | return result 268 | } 269 | 270 | // Returns the current blockchain height 271 | func dbGetBlockchainHeight() int { 272 | assertSysDbOpen() 273 | var height int 274 | err := mainDb.QueryRow("SELECT COALESCE(MAX(height), -1) FROM blockchain").Scan(&height) 275 | if err != nil { 276 | log.Panic(err) 277 | } 278 | return height 279 | } 280 | 281 | // Returns a map of heights and hashes for the requested range of block heights 282 | func dbGetHeightHashes(minHeight, maxHeight int) map[int]string { 283 | rows, err := mainDb.Query("SELECT height, hash FROM blockchain WHERE height BETWEEN ? AND ? ORDER BY height", minHeight, maxHeight) 284 | if err != nil { 285 | log.Panic(err) 286 | } 287 | defer func() { 288 | err = rows.Close() 289 | if err != nil { 290 | log.Fatalf("dbGetHeightHashes rows.Close: %v", err) 291 | } 292 | }() 293 | hh := make(map[int]string) 294 | for rows.Next() { 295 | var height int 296 | var hash string 297 | if err = rows.Scan(&height, &hash); err != nil { 298 | log.Panic(err) 299 | } 300 | hh[height] = hash 301 | } 302 | return hh 303 | } 304 | 305 | // Returns a random private key from the system databases 306 | func dbGetAPrivateKey() ([]byte, string, error) { 307 | var publicKeyHash string 308 | var privateKey string 309 | err := privateDb.QueryRow("SELECT pubkey_hash, privkey FROM privkeys LIMIT 1").Scan(&publicKeyHash, &privateKey) 310 | if err != nil && err != sql.ErrNoRows { 311 | log.Fatal(err) 312 | } 313 | if err == sql.ErrNoRows { 314 | return nil, "", err 315 | } 316 | privateKeyBytes, err := hex.DecodeString(privateKey) 317 | if err != nil { 318 | log.Println(err) 319 | return nil, "", err 320 | } 321 | return privateKeyBytes, publicKeyHash, nil 322 | } 323 | 324 | // Returns the public key corresponding to the given public key hash, by reading it from the system databases. 325 | func dbGetPublicKey(publicKeyHash string) (*DbPubKey, error) { 326 | var dbpk DbPubKey 327 | var publicKeyHexString string 328 | var timeAdded int 329 | var timeRevoked int 330 | var metadata string 331 | err := mainDb.QueryRow("SELECT pubkey_hash, pubkey, state, time_added, COALESCE(time_revoked, -1), COALESCE(metadata, ''), block_height FROM pubkeys WHERE pubkey_hash=?", publicKeyHash).Scan( 332 | &dbpk.publicKeyHash, &publicKeyHexString, &dbpk.state, &timeAdded, &timeRevoked, &metadata, &dbpk.addBlockHeight) 333 | if err != nil && err != sql.ErrNoRows { 334 | log.Panicln(err) 335 | } 336 | if err == sql.ErrNoRows { 337 | return nil, err 338 | } 339 | dbpk.publicKeyBytes, err = hex.DecodeString(publicKeyHexString) 340 | if err != nil { 341 | return nil, err 342 | } 343 | dbpk.timeAdded = unixTimeStampToUTCTime(timeAdded) 344 | if err != nil { 345 | return nil, err 346 | } 347 | if timeRevoked != -1 { 348 | dbpk.timeRevoked = unixTimeStampToUTCTime(timeRevoked) 349 | if err != nil { 350 | log.Println("Public key timeRevoked parsing failed for", publicKeyHash) 351 | return nil, err 352 | } 353 | dbpk.isRevoked = true 354 | } else { 355 | dbpk.isRevoked = false 356 | } 357 | if metadata != "" { 358 | err = json.Unmarshal([]byte(metadata), &dbpk.metadata) 359 | if err != nil { 360 | log.Println("Public key metadata unmarshall failed for", publicKeyHash) 361 | return nil, err 362 | } 363 | } 364 | return &dbpk, nil 365 | } 366 | 367 | // Returns a block hash by its height 368 | func dbGetBlockHashByHeight(height int) string { 369 | var hash string 370 | err := mainDb.QueryRow("SELECT hash FROM blockchain WHERE height=?", height).Scan(&hash) 371 | if err != nil { 372 | if err != sql.ErrNoRows { 373 | log.Panicln(err) 374 | } 375 | } 376 | return hash 377 | } 378 | 379 | // Returns a block indexed by the given height. 380 | func dbGetBlockByHeight(height int) (*DbBlockchainBlock, error) { 381 | var dbb DbBlockchainBlock 382 | var hashSignatureHex string 383 | var prevHashSignatureHex string 384 | var timeAccepted int 385 | err := mainDb.QueryRow("SELECT hash, height, prev_hash, sigkey_hash, hash_signature, prev_hash_signature, time_accepted, version FROM blockchain WHERE height=?", height).Scan( 386 | &dbb.Hash, &dbb.Height, &dbb.PreviousBlockHash, &dbb.SignaturePublicKeyHash, &hashSignatureHex, &prevHashSignatureHex, &timeAccepted, &dbb.Version) 387 | if err != nil && err != sql.ErrNoRows { 388 | log.Panicln(err) 389 | } 390 | if err == sql.ErrNoRows { 391 | return nil, err 392 | } 393 | dbb.PreviousBlockHashSignature, err = hex.DecodeString(prevHashSignatureHex) 394 | if err != nil { 395 | return nil, err 396 | } 397 | dbb.HashSignature, err = hex.DecodeString(hashSignatureHex) 398 | if err != nil { 399 | return nil, err 400 | } 401 | dbb.TimeAccepted = unixTimeStampToUTCTime(timeAccepted) 402 | if err != nil { 403 | return nil, err 404 | } 405 | return &dbb, nil 406 | } 407 | 408 | // Returns a block of the given hash 409 | func dbGetBlock(hash string) (*DbBlockchainBlock, error) { 410 | var dbb DbBlockchainBlock 411 | var hashSignatureHex string 412 | var prevHashSignatureHex string 413 | var timeAccepted int 414 | err := mainDb.QueryRow("SELECT hash, height, prev_hash, sigkey_hash, hash_signature, prev_hash_signature, time_accepted, version FROM blockchain WHERE hash=?", hash).Scan( 415 | &dbb.Hash, &dbb.Height, &dbb.PreviousBlockHash, &dbb.SignaturePublicKeyHash, &hashSignatureHex, &prevHashSignatureHex, &timeAccepted, &dbb.Version) 416 | if err != nil && err != sql.ErrNoRows { 417 | log.Panicln(err) 418 | } 419 | if err == sql.ErrNoRows { 420 | return nil, err 421 | } 422 | dbb.PreviousBlockHashSignature, err = hex.DecodeString(prevHashSignatureHex) 423 | if err != nil { 424 | return nil, err 425 | } 426 | dbb.HashSignature, err = hex.DecodeString(hashSignatureHex) 427 | if err != nil { 428 | return nil, err 429 | } 430 | dbb.TimeAccepted = unixTimeStampToUTCTime(timeAccepted) 431 | if err != nil { 432 | return nil, err 433 | } 434 | return &dbb, nil 435 | } 436 | 437 | // Tests if a block with the given hash exists in the db 438 | func dbBlockHashExists(hash string) bool { 439 | var count int 440 | err := mainDb.QueryRow("SELECT COUNT(*) FROM blockchain WHERE hash=?", hash).Scan(&count) 441 | if err != nil { 442 | log.Panic(err) 443 | } 444 | return count > 0 445 | } 446 | 447 | // Tests if the block with the given height exists in the db 448 | func dbBlockHeightExists(h int) bool { 449 | var count int 450 | err := mainDb.QueryRow("SELECT COUNT(*) FROM blockchain WHERE height=?", h).Scan(&count) 451 | if err != nil { 452 | log.Panic(err) 453 | } 454 | return count > 0 455 | } 456 | 457 | // Inserts a block record into the main database, without validation 458 | func dbInsertBlock(dbb *DbBlockchainBlock) error { 459 | _, err := mainDb.Exec("INSERT INTO blockchain (hash, height, prev_hash, sigkey_hash, hash_signature, prev_hash_signature, time_accepted, version) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 460 | dbb.Hash, dbb.Height, dbb.PreviousBlockHash, dbb.SignaturePublicKeyHash, hex.EncodeToString(dbb.HashSignature), hex.EncodeToString(dbb.PreviousBlockHashSignature), 461 | dbb.TimeAccepted.UTC().Unix(), dbb.Version) 462 | return err 463 | } 464 | 465 | func dbClearSavedPeers() error { 466 | _, err := mainDb.Exec("DELETE FROM peers") 467 | return err 468 | } 469 | 470 | // Gets a list of saved p2p peer addresses 471 | func dbGetSavedPeers() peerStringMap { 472 | result := peerStringMap{} 473 | rows, err := mainDb.Query("SELECT address, time_added FROM peers") 474 | if err != nil { 475 | log.Panic(err) 476 | } 477 | defer func() { 478 | err = rows.Close() 479 | if err != nil { 480 | log.Fatalf("dbGetSavedPeers rows.Close: %v", err) 481 | } 482 | }() 483 | for rows.Next() { 484 | var tmInt int 485 | var address string 486 | if err = rows.Scan(&address, &tmInt); err != nil { 487 | log.Println(err) 488 | continue 489 | } 490 | result[address] = unixTimeStampToUTCTime(tmInt) 491 | } 492 | return result 493 | } 494 | 495 | // Saves a p2p peer address to the db 496 | func dbSavePeer(address string) { 497 | _, err := mainDb.Exec("INSERT OR REPLACE INTO peers(address, time_added) VALUES (?, ?)", address, getNowUTC()) 498 | if err != nil { 499 | log.Panic(err) 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /cliactions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "os" 12 | "reflect" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // The binary can be called with some actions, like signblock, importblock, signkey. 18 | // This function processes those and returns true if it has found something to execute. 19 | // The processActions() function is called after the blockchain database is initialised 20 | // and active. 21 | func processActions() bool { 22 | if flag.NArg() == 0 { 23 | return false 24 | } 25 | cmd := flag.Arg(0) 26 | switch cmd { 27 | case "help": 28 | actionHelp() 29 | return true 30 | case "mykeys": 31 | actionMyKeys() 32 | return true 33 | case "query": 34 | actionQuery(flag.Arg(1)) 35 | return true 36 | case "signimportblock": 37 | if flag.NArg() < 2 { 38 | log.Fatalln("Not enough arguments: expecting ") 39 | } 40 | actionSignImportBlock(flag.Arg(1)) 41 | return true 42 | } 43 | return false 44 | } 45 | 46 | // processPreBlockchainActions is called to process actions which need to executed 47 | // before the blockchain database is running. 48 | func processPreBlockchainActions() bool { 49 | if flag.NArg() == 0 { 50 | return false 51 | } 52 | cmd := flag.Arg(0) 53 | switch cmd { 54 | case "newchain": 55 | if flag.NArg() < 2 { 56 | log.Fatalln("Not enough arguments: expecing chainparams.json") 57 | } 58 | actionNewChain(flag.Arg(1)) 59 | return true 60 | case "pull": 61 | if flag.NArg() < 2 { 62 | log.Fatalln("Not enough arguments: expecting chain URL") 63 | } 64 | actionPull(flag.Arg(1)) 65 | return true 66 | } 67 | return false 68 | } 69 | 70 | // Opens the given block file (SQLite database), creates metadata tables in it, signes the 71 | // block with one of the private keys, and accepts the resulting block into the blockchain. 72 | func actionSignImportBlock(fn string) { 73 | db, err := dbOpen(fn, false) 74 | if err != nil { 75 | log.Fatalln(err) 76 | } 77 | dbEnsureBlockchainTables(db) 78 | keypair, publicKeyHash, err := cryptoGetAPrivateKey() 79 | if err != nil { 80 | log.Fatalln(err) 81 | } 82 | lastBlockHeight := dbGetBlockchainHeight() 83 | dbb, err := dbGetBlockByHeight(lastBlockHeight) 84 | if err != nil { 85 | log.Fatalln(err) 86 | } 87 | if err = dbSetMetaInt(db, "Version", CurrentBlockVersion); err != nil { 88 | log.Panic(err) 89 | } 90 | err = dbSetMetaString(db, "PreviousBlockHash", dbb.Hash) 91 | if err != nil { 92 | log.Fatalln(err) 93 | } 94 | signature, err := cryptoSignHex(keypair, dbb.Hash) 95 | if err != nil { 96 | log.Fatalln(err) 97 | } 98 | err = dbSetMetaString(db, "PreviousBlockHashSignature", signature) 99 | if err != nil { 100 | log.Fatalln(err) 101 | } 102 | err = dbSetMetaString(db, "Timestamp", time.Now().Format(time.RFC3339)) 103 | if err != nil { 104 | log.Fatalln(err) 105 | } 106 | 107 | pkdb, err := dbGetPublicKey(publicKeyHash) 108 | if err != nil { 109 | log.Panic(err) 110 | } 111 | previousBlockHashSignature, err := hex.DecodeString(signature) 112 | if err != nil { 113 | log.Fatalln(err) 114 | } 115 | if creatorString, ok := pkdb.metadata["BlockCreator"]; ok { 116 | err = dbSetMetaString(db, "Creator", creatorString) 117 | if err != nil { 118 | log.Fatalln(err) 119 | } 120 | } 121 | err = dbSetMetaString(db, "CreatorPublicKey", pkdb.publicKeyHash) 122 | if err != nil { 123 | log.Fatalln(err) 124 | } 125 | if err = db.Close(); err != nil { 126 | log.Panic(err) 127 | } 128 | blockHashHex, err := hashFileToHexString(fn) 129 | if err != nil { 130 | log.Panic(err) 131 | } 132 | signature, err = cryptoSignHex(keypair, blockHashHex) 133 | if err != nil { 134 | log.Panic(err) 135 | } 136 | blockHashSignature, _ := hex.DecodeString(signature) 137 | 138 | newBlockHeight := lastBlockHeight + 1 139 | newBlock := DbBlockchainBlock{Hash: blockHashHex, HashSignature: blockHashSignature, PreviousBlockHash: dbb.Hash, PreviousBlockHashSignature: previousBlockHashSignature, 140 | Version: CurrentBlockVersion, SignaturePublicKeyHash: pkdb.publicKeyHash, Height: newBlockHeight, TimeAccepted: time.Now()} 141 | 142 | err = blockchainCopyFile(fn, newBlockHeight) 143 | if err != nil { 144 | log.Panic(err) 145 | } 146 | 147 | err = dbInsertBlock(&newBlock) 148 | if err != nil { 149 | log.Panic(err) 150 | } 151 | } 152 | 153 | // Runs a SQL query over all the blocks. 154 | func actionQuery(q string) { 155 | log.Println("Running query:", q) 156 | errCount := 0 157 | for h := dbGetBlockchainHeight(); h > 0; h-- { 158 | fn := blockchainGetFilename(h) 159 | db, err := dbOpen(fn, true) 160 | if err != nil { 161 | log.Panic(err) 162 | } 163 | rows, err := db.Query(q) 164 | if err != nil { 165 | errCount++ 166 | continue 167 | } 168 | cols, err := rows.Columns() 169 | if err != nil { 170 | log.Panic(err) 171 | } 172 | for rows.Next() { 173 | columns := make([]interface{}, len(cols)) 174 | columnPointers := make([]interface{}, len(cols)) 175 | for i := range columns { 176 | columnPointers[i] = &columns[i] 177 | } 178 | if err := rows.Scan(columnPointers...); err != nil { 179 | log.Panic(err) 180 | } 181 | row := make(map[string]interface{}) 182 | for i, colName := range cols { 183 | val := columnPointers[i].(*interface{}) 184 | if reflect.TypeOf(*val).String() == "[]uint8" { 185 | row[colName] = string((*val).([]byte)) 186 | } else { 187 | row[colName] = *val 188 | } 189 | } 190 | buf, err := json.Marshal(row) 191 | if err != nil { 192 | log.Panic(err) 193 | } 194 | fmt.Println(string(buf)) 195 | } 196 | } 197 | if errCount != 0 { 198 | log.Println("There have been", errCount, "errors.") 199 | } 200 | } 201 | 202 | // Shows the help message. 203 | func actionHelp() { 204 | fmt.Printf("usage: %s [flags] [command]\n", os.Args[0]) 205 | flag.PrintDefaults() 206 | fmt.Println("Commands:") 207 | fmt.Println("\thelp\t\tShows this help message") 208 | fmt.Println("\tmykeys\t\tShows a list of my public keys") 209 | fmt.Println("\tquery\t\tExecutes a SQL query on the blockchain (expects 1 argument: SQL query)") 210 | fmt.Println("\tsignimportblock\tSigns a block (creates metadata tables in it first) and imports it into the blockchain (expects 1 argument: a sqlite db filename)") 211 | fmt.Println("\tnewchain\tStarts a new chain with the given parameters (expects 1 argument: chainparams.json)") 212 | fmt.Println("\tpull\t\tPulls a blockchain from a HTTP URL (expects 1 argument: URL, e.g. http://example.com:2018/)") 213 | } 214 | 215 | // Shows the public keys which correspond to private keys in the system database. 216 | func actionMyKeys() { 217 | for _, k := range dbGetMyPublicKeyHashes() { 218 | fmt.Println(k) 219 | } 220 | } 221 | 222 | // NewChainParams is extended from ChainParams for new chain creation 223 | type NewChainParams struct { 224 | ChainParams 225 | GenesisDb string `json:"genesis_db"` 226 | } 227 | 228 | func actionNewChain(jsonFilename string) { 229 | jsonData, err := ioutil.ReadFile(jsonFilename) 230 | if err != nil { 231 | log.Fatalln(err) 232 | } 233 | 234 | ncp := NewChainParams{} 235 | err = json.Unmarshal(jsonData, &ncp) 236 | if err != nil { 237 | log.Fatalln(err) 238 | } 239 | if ncp.GenesisBlockTimestamp == "" { 240 | ncp.GenesisBlockTimestamp = time.Now().Format(time.RFC3339) 241 | } 242 | if ncp.CreatorPublicKey != "" || ncp.GenesisBlockHash != "" || ncp.GenesisBlockHashSignature != "" { 243 | log.Fatalln("chainparams.json must not contain cryptographic properties") 244 | } 245 | log.Println("Creating a new blockchain from", jsonFilename) 246 | 247 | empty, err := isDirEmpty(cfg.DataDir) 248 | if err != nil { 249 | log.Fatalln(err) 250 | } 251 | if !empty { 252 | log.Fatalln("Data directory must not be empty:", cfg.DataDir) 253 | } 254 | 255 | ensureBlockchainSubdirectoryExists() 256 | freshDb := true 257 | if ncp.GenesisDb != "" && fileExists(ncp.GenesisDb) { 258 | err = blockchainCopyFile(ncp.GenesisDb, 0) 259 | if err != nil { 260 | log.Fatalln(err) 261 | } 262 | freshDb = false 263 | } 264 | 265 | // Modify the new genesis db to include the metadata 266 | blockFilename := blockchainGetFilename(0) 267 | log.Println("Creating the genesis block at", blockFilename) 268 | db, err := dbOpen(blockFilename, false) 269 | if err != nil { 270 | log.Fatalln("dbOpen", err) 271 | } 272 | if freshDb { 273 | _, err = db.Exec("PRAGMA page_size=512") 274 | if err != nil { 275 | log.Fatalln(err) 276 | } 277 | } 278 | _, err = db.Exec("PRAGMA journal_mode=DELETE") 279 | if err != nil { 280 | log.Fatalln(err) 281 | } 282 | dbEnsureBlockchainTables(db) 283 | err = dbSetMetaInt(db, "Version", CurrentBlockVersion) 284 | if err != nil { 285 | log.Fatalln(err) 286 | } 287 | err = dbSetMetaString(db, "PreviousBlockHash", GenesisBlockPreviousBlockHash) 288 | if err != nil { 289 | log.Fatalln(err) 290 | } 291 | err = dbSetMetaString(db, "Creator", ncp.Creator) 292 | if err != nil { 293 | log.Fatalln(err) 294 | } 295 | err = dbSetMetaString(db, "Timestamp", ncp.GenesisBlockTimestamp) 296 | if err != nil { 297 | log.Fatalln(err) 298 | } 299 | err = dbSetMetaString(db, "Description", ncp.Description) 300 | if err != nil { 301 | log.Fatalln(err) 302 | } 303 | 304 | if len(ncp.BootstrapPeers) > 0 { 305 | // bootstrapPeers is required to be filled in before dbInit() 306 | bootstrapPeers = peerStringMap{} 307 | for _, peer := range ncp.BootstrapPeers { 308 | bootstrapPeers[peer] = time.Now() 309 | } 310 | } 311 | 312 | dbInit() // Create system databases 313 | cryptoInit() // Create the genesis keypair 314 | 315 | pubKeys := dbGetMyPublicKeyHashes() 316 | if len(pubKeys) != 1 { 317 | log.Fatalln("There should have been only one genesis keypair, found", len(pubKeys)) 318 | } 319 | err = dbSetMetaString(db, "CreatorPublicKey", pubKeys[0]) 320 | 321 | pKey, pubKeyHash, err := cryptoGetAPrivateKey() 322 | if err != nil { 323 | log.Fatalln(err) 324 | } 325 | if pubKeyHash != pubKeys[0] { 326 | log.Fatalln("The impossible has happened: two attempts to get the single public key have different results:", pubKeys[0], pubKeyHash) 327 | } 328 | log.Println("Genesis public key:", pubKeyHash) 329 | prevSig, err := cryptoSignHex(pKey, GenesisBlockPreviousBlockHash) 330 | if err != nil { 331 | log.Fatalln("cryptoSignHex", err) 332 | } 333 | err = dbSetMetaString(db, "PreviousBlockHashSignature", prevSig) 334 | if err != nil { 335 | log.Fatalln(err) 336 | } 337 | err = dbSetMetaString(db, "CreatorPubKey", pubKeyHash) 338 | if err != nil { 339 | log.Fatalln(err) 340 | } 341 | 342 | // Write the public key into the genesis block 343 | pubKey, err := dbGetPublicKey(pubKeyHash) 344 | if err != nil { 345 | log.Fatalln("Error getting public key from db", err) 346 | } 347 | selfSig, err := cryptoSignPublicKeyHash(pKey, pubKeyHash) 348 | if err != nil { 349 | log.Fatalln("Error signing publicKey", err) 350 | } 351 | _, err = db.Exec("INSERT INTO _keys (op, pubkey_hash, pubkey, sigkey_hash, signature) VALUES (?, ?, ?, ?, ?)", 352 | "A", pubKeyHash, hex.EncodeToString(pubKey.publicKeyBytes), pubKeyHash, hex.EncodeToString(selfSig)) 353 | if err != nil { 354 | log.Fatalln("Error recording the genesis block public key") 355 | } 356 | 357 | err = db.Close() 358 | if err != nil { 359 | log.Fatalln(err) 360 | } 361 | 362 | // Hash it, sign it, generate chainparams 363 | hash, err := hashFileToHexString(blockFilename) 364 | if err != nil { 365 | log.Fatalln(err) 366 | } 367 | ncp.GenesisBlockHash = hash 368 | ncp.CreatorPublicKey = pubKeyHash 369 | ncp.GenesisBlockHashSignature, err = cryptoSignHex(pKey, hash) 370 | if err != nil { 371 | log.Fatalln(err) 372 | } 373 | log.Println("Genesis block hash:", ncp.GenesisBlockHash) 374 | 375 | // Save the chainparams to the data dir 376 | cpJSON, err := json.Marshal(ncp.ChainParams) 377 | if err != nil { 378 | log.Fatalln(err) 379 | } 380 | err = ioutil.WriteFile(fmt.Sprintf("%s/%s", cfg.DataDir, chainParamsBaseName), cpJSON, 0644) 381 | if err != nil { 382 | log.Fatalln(err) 383 | } 384 | 385 | // Record the genesis block into the system database 386 | newBlock := DbBlockchainBlock{ 387 | Hash: ncp.GenesisBlockHash, 388 | HashSignature: mustDecodeHex(ncp.GenesisBlockHashSignature), 389 | PreviousBlockHash: GenesisBlockPreviousBlockHash, 390 | PreviousBlockHashSignature: mustDecodeHex(prevSig), 391 | Version: CurrentBlockVersion, 392 | SignaturePublicKeyHash: pubKeyHash, 393 | Height: 0, 394 | TimeAccepted: time.Now(), 395 | } 396 | err = dbInsertBlock(&newBlock) 397 | if err != nil { 398 | log.Panic(err) 399 | } 400 | 401 | // Reopen the database to verify 402 | log.Println("Reloading to verify...") 403 | blockchainInit(false) 404 | 405 | // If we make it to here, everything's ok. 406 | log.Println("All done.") 407 | } 408 | 409 | func actionPull(baseURL string) { 410 | if !strings.HasSuffix(baseURL, "/") { 411 | baseURL = baseURL + "/" 412 | } 413 | // Step 1: fetch chainparams 414 | cpURL := fmt.Sprintf("%schainparams.json", baseURL) 415 | resp, err := http.Get(cpURL) 416 | if err != nil { 417 | log.Fatalln("Error getting chainparams", cpURL, err) 418 | } 419 | defer resp.Body.Close() 420 | body, err := ioutil.ReadAll(resp.Body) 421 | if err != nil { 422 | log.Fatalln("Error reading chainparams", cpURL, err) 423 | } 424 | err = json.Unmarshal(body, &chainParams) 425 | if err != nil { 426 | log.Println(string(body)) 427 | log.Fatalln("Error decoding chainparams", cpURL, err) 428 | } 429 | if chainParams.GenesisBlockHash == "" || chainParams.GenesisBlockHashSignature == "" { 430 | log.Fatalln("Incomplete chainparams data", cpURL) 431 | } 432 | 433 | // Step 2: Fetch the genesis block 434 | gbURL := fmt.Sprintf("%s/block/0", baseURL) 435 | resp, err = http.Get(gbURL) 436 | if err != nil { 437 | log.Fatalln("Error getting genesis block", gbURL, err) 438 | } 439 | defer resp.Body.Close() 440 | body, err = ioutil.ReadAll(resp.Body) 441 | if err != nil { 442 | log.Fatalln("Error reading genesis block", gbURL, err) 443 | } 444 | 445 | // Step 3: initialise data directories 446 | if fileExists(cfg.DataDir) { 447 | if empty, err := isDirEmpty(cfg.DataDir); err != nil || !empty { 448 | log.Fatalln("Blockchain directory must be empty", cfg.DataDir) 449 | } 450 | } 451 | if _, err = os.Stat(cfg.DataDir); err != nil { 452 | log.Println("Data directory", cfg.DataDir, "doesn't exist, creating.") 453 | err = os.Mkdir(cfg.DataDir, 0700) 454 | if err != nil { 455 | log.Panicln(err) 456 | } 457 | } 458 | ensureBlockchainSubdirectoryExists() 459 | 460 | blockFilename := blockchainGetFilename(0) 461 | err = ioutil.WriteFile(blockFilename, body, 0664) 462 | if err != nil { 463 | log.Fatalln("Cannot write genesis block", blockFilename, err) 464 | } 465 | 466 | hash, err := hashFileToHexString(blockFilename) 467 | if err != nil { 468 | log.Fatalln(err) 469 | } 470 | if hash != chainParams.GenesisBlockHash { 471 | log.Fatalln("Mismatching genesis block hash") 472 | } 473 | 474 | // Step 4: Initialise databases 475 | dbInit() 476 | dbClearSavedPeers() 477 | cryptoInit() 478 | 479 | blk, err := OpenBlockFile(blockFilename) 480 | if err != nil { 481 | log.Fatalln("Error opening genesis block", blockFilename, err, "--", cfg.DataDir, "is in inconsistent state") 482 | } 483 | kops, err := blk.dbGetKeyOps() 484 | if err != nil { 485 | log.Fatalln("Error reading genesis block keys", err) 486 | } 487 | if len(kops) == 0 { 488 | log.Fatalln("No key ops in genesis block?!") 489 | } 490 | 491 | verified := false 492 | for kHash, ops := range kops { 493 | for _, op := range ops { 494 | if op.op == "A" { 495 | pubKey, err := cryptoDecodePublicKeyBytes(op.publicKeyBytes) 496 | if err != nil { 497 | log.Fatalln("Error decoding genesis block public key", kHash, err) 498 | } 499 | if chainParams.CreatorPublicKey != getPubKeyHash(op.publicKeyBytes) { 500 | continue 501 | } 502 | if err = cryptoVerifyHex(pubKey, chainParams.GenesisBlockHash, chainParams.GenesisBlockHashSignature); err == nil { 503 | verified = true 504 | dbWritePublicKey(op.publicKeyBytes, chainParams.CreatorPublicKey, 0) 505 | } else { 506 | log.Fatalln("Error verifying genesis block signature", err) 507 | } 508 | } 509 | } 510 | } 511 | if !verified { 512 | log.Fatalln("Cannot verify genesis block signature") 513 | } 514 | blk.Close() 515 | 516 | hashSignature, err := hex.DecodeString(chainParams.GenesisBlockHashSignature) 517 | if err != nil { 518 | log.Fatalln("Error hex-decoding hash signature", err) 519 | } 520 | blk.HashSignature = hashSignature 521 | err = dbInsertBlock(blk.DbBlockchainBlock) 522 | if err != nil { 523 | log.Panic(err) 524 | } 525 | 526 | // Save the chainparams to the data dir 527 | cpJSON, err := json.Marshal(chainParams) 528 | if err != nil { 529 | log.Fatalln(err) 530 | } 531 | err = ioutil.WriteFile(fmt.Sprintf("%s/%s", cfg.DataDir, chainParamsBaseName), cpJSON, 0644) 532 | if err != nil { 533 | log.Fatalln(err) 534 | } 535 | 536 | // Reopen the database to verify 537 | log.Println("Reloading to verify...") 538 | blockchainInit(false) 539 | 540 | // If we make it to here, everything's ok. 541 | log.Println("All done.") 542 | } 543 | -------------------------------------------------------------------------------- /p2p.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/zlib" 7 | "encoding/base64" 8 | "encoding/hex" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net" 16 | "net/http" 17 | "os" 18 | "sort" 19 | "strconv" 20 | "time" 21 | ) 22 | 23 | const p2pClientVersionString = "godaisy/0.2" 24 | 25 | // Header for JSON messages we're sending 26 | type p2pMsgHeader struct { 27 | Root string `json:"root"` 28 | Msg string `json:"msg"` 29 | P2pID int64 `json:"p2p_id"` 30 | } 31 | 32 | // The hello message 33 | const p2pMsgHello = "hello" 34 | 35 | type p2pMsgHelloStruct struct { 36 | p2pMsgHeader 37 | Version string `json:"version"` 38 | ChainHeight int `json:"chain_height"` 39 | MyPeers []string `json:"my_peers"` 40 | } 41 | 42 | // The message asking for block hashes 43 | const p2pMsgGetBlockHashes = "getblockhashes" 44 | 45 | type p2pMsgGetBlockHashesStruct struct { 46 | p2pMsgHeader 47 | MinBlockHeight int `json:"min_block_height"` 48 | MaxBlockHeight int `json:"max_block_height"` 49 | } 50 | 51 | // The message reporting block hashes a node has 52 | const p2pMsgBlockHashes = "blockhashes" 53 | 54 | type p2pMsgBlockHashesStruct struct { 55 | p2pMsgHeader 56 | Hashes map[int]string `json:"hashes"` 57 | } 58 | 59 | // The message asking for block data 60 | const p2pMsgGetBlock = "getblock" 61 | 62 | type p2pMsgGetBlockStruct struct { 63 | p2pMsgHeader 64 | Hash string `json:"hash"` 65 | } 66 | 67 | // The message containing one block's data 68 | const p2pMsgBlock = "block" 69 | 70 | type p2pMsgBlockStruct struct { 71 | p2pMsgHeader 72 | Hash string `json:"hash"` 73 | HashSignature string `json:"hash_signature"` 74 | Size int64 `json:"size"` 75 | Encoding string `json:"encoding"` 76 | Data string `json:"data"` 77 | } 78 | 79 | // Map of peer addresses, for easy set-like behaviour 80 | type peerStringMap map[string]time.Time 81 | 82 | var bootstrapPeers = peerStringMap{ 83 | "cosmos.ivoras.net:2017": time.Now(), 84 | "fielder.ivoras.net:2017": time.Now(), 85 | } 86 | 87 | // The temporary ID of this node, using strong RNG 88 | var p2pEphemeralID = randInt63() & 0xffffffffffff 89 | 90 | // Everything useful describing one p2p connection 91 | type p2pConnection struct { 92 | conn net.Conn 93 | address string // host:port 94 | peer *bufio.ReadWriter 95 | peerID int64 96 | isConnectable bool // using the default port 97 | testedConnectable bool // using the default port 98 | chainHeight int 99 | refreshTime time.Time 100 | chanToPeer chan interface{} // structs go out 101 | chanFromPeer chan StrIfMap // StrIfMaps go in 102 | } 103 | 104 | // A set of p2p connections 105 | type p2pPeersSet struct { 106 | peers map[*p2pConnection]time.Time 107 | lock WithMutex // Warning: do not do any IO/network operations while holding this lock 108 | } 109 | 110 | // The global set of p2p connections. XXX: Singletons in Go? 111 | var p2pPeers = p2pPeersSet{peers: make(map[*p2pConnection]time.Time)} 112 | 113 | // Adds a p2p connections to the set of p2p connections 114 | func (p *p2pPeersSet) Add(c *p2pConnection) { 115 | p.lock.With(func() { 116 | p.peers[c] = time.Now() 117 | }) 118 | } 119 | 120 | // Removes a p2p connection from the set of p2p connections 121 | func (p *p2pPeersSet) Remove(c *p2pConnection) { 122 | p.lock.With(func() { 123 | delete(p.peers, c) 124 | }) 125 | } 126 | 127 | func (p *p2pPeersSet) HasAddress(address string) bool { 128 | found := false 129 | p.lock.With(func() { 130 | for peer := range p.peers { 131 | if peer.address == address { 132 | found = true 133 | break 134 | } 135 | } 136 | }) 137 | return found 138 | } 139 | 140 | func (p *p2pPeersSet) GetAddresses(onlyConnectable bool) []string { 141 | var addresses []string 142 | p.lock.With(func() { 143 | for peer := range p.peers { 144 | if onlyConnectable && !peer.isConnectable { 145 | continue 146 | } 147 | addresses = append(addresses, peer.address) 148 | } 149 | }) 150 | return addresses 151 | } 152 | 153 | func (p *p2pPeersSet) tryPeersConnectable() { 154 | addressesToTry := map[string]string{} 155 | 156 | p.lock.With(func() { 157 | for peer := range p.peers { 158 | if peer.testedConnectable || peer.isConnectable { 159 | continue 160 | } 161 | host, port, err := splitAddress(peer.address) 162 | if err != nil { 163 | continue 164 | } 165 | if port == DefaultP2PPort { 166 | // we're already connected to it 167 | continue 168 | } 169 | 170 | address := fmt.Sprintf("%s:%d", host, DefaultP2PPort) 171 | peer.testedConnectable = true 172 | 173 | addressesToTry[peer.address] = address 174 | } 175 | }) 176 | 177 | for paddress, address := range addressesToTry { 178 | conn, err := net.Dial("tcp", address) 179 | if err != nil { 180 | continue 181 | } 182 | p.lock.With(func() { 183 | for peer := range p.peers { 184 | if peer.address == paddress { 185 | peer.isConnectable = true 186 | } 187 | } 188 | }) 189 | 190 | err = conn.Close() 191 | if err != nil { 192 | log.Println(err) 193 | } 194 | } 195 | } 196 | 197 | func (p *p2pPeersSet) saveConnectablePeers() { 198 | dbPeers := dbGetSavedPeers() 199 | localAddresses := getLocalAddresses() 200 | 201 | p.lock.With(func() { 202 | for peer := range p.peers { 203 | if !peer.isConnectable { 204 | continue 205 | } 206 | host, _, err := splitAddress(peer.address) 207 | if err != nil { 208 | continue 209 | } 210 | canonicalAddress := fmt.Sprintf("%s:%d", host, DefaultP2PPort) 211 | addr, err := net.ResolveTCPAddr("tcp", canonicalAddress) 212 | if err != nil { 213 | continue 214 | } 215 | if _, ok := dbPeers[addr.IP.String()]; ok { 216 | // Already in db 217 | continue 218 | } 219 | if inStrings(addr.String(), localAddresses) { 220 | // Local interface 221 | continue 222 | } 223 | log.Println("Detected canonical peer at", canonicalAddress) 224 | dbSavePeer(canonicalAddress) 225 | } 226 | }) 227 | 228 | } 229 | 230 | func p2pServer() { 231 | serverAddress := ":" + strconv.Itoa(cfg.P2pPort) 232 | l, err := net.Listen("tcp", serverAddress) 233 | if err != nil { 234 | log.Println("Cannot listen on", serverAddress) 235 | log.Fatal(err) 236 | } 237 | defer func() { 238 | err = l.Close() 239 | if err != nil { 240 | log.Fatalf("p2pServer l.Close: %v", err) 241 | } 242 | }() 243 | log.Println("P2P listening on", serverAddress) 244 | for { 245 | conn, err := l.Accept() 246 | if err != nil { 247 | log.Println("Error accepting socket:", err) 248 | sysEventChannel <- sysEventMessage{event: eventQuit} 249 | return 250 | } 251 | if p2pCoordinator.badPeers.Has(conn.RemoteAddr().String()) { 252 | log.Println("Ignoring bad peer", conn.RemoteAddr().String()) 253 | continue 254 | } 255 | p2pc, err := p2pSetupPeer(conn.RemoteAddr().String(), conn) 256 | if err != nil { 257 | log.Println("Error setting up peer", conn.RemoteAddr().String(), err) 258 | continue 259 | } 260 | go p2pc.handleConnection() 261 | } 262 | } 263 | 264 | func p2pClient() { 265 | p2pCoordinator.connectDbPeers() 266 | } 267 | 268 | func (p2pc *p2pConnection) sendMsg(msg interface{}) error { 269 | bmsg, err := json.Marshal(msg) 270 | if err != nil { 271 | return err 272 | } 273 | n, err := p2pc.peer.Write(bmsg) 274 | if err != nil { 275 | return err 276 | } 277 | if n != len(bmsg) { 278 | return fmt.Errorf("didn't write entire message: %v vs %v", n, len(bmsg)) 279 | } 280 | n, err = p2pc.peer.Write([]byte("\n")) 281 | if err != nil { 282 | return err 283 | } 284 | if n != 1 { 285 | return errors.New("didn't write newline") 286 | } 287 | //log.Println("... successfully wrote", string(bmsg)) 288 | return p2pc.peer.Flush() 289 | } 290 | 291 | func (p2pc *p2pConnection) handleConnection() { 292 | defer func() { 293 | log.Println("Cleaning up connection", p2pc.address) 294 | p2pPeers.Remove(p2pc) 295 | err := p2pc.conn.Close() 296 | if err != nil { 297 | log.Printf("p2pc.conn.Close: %v", err) 298 | } 299 | log.Println("Finished cleaning up connection", p2pc.address) 300 | }() 301 | 302 | // Only store the IP address as the address. 303 | // This must be done in the goroutine because resolving can block for a long time. 304 | addr, err := net.ResolveTCPAddr("tcp", p2pc.address) 305 | if err == nil { 306 | p2pc.address = addr.String() 307 | } 308 | 309 | p2pc.peer = bufio.NewReadWriter(bufio.NewReader(p2pc.conn), bufio.NewWriter(p2pc.conn)) 310 | 311 | // XXX: the state machine shouldn't start by the listener sending something 312 | // (security best practices) 313 | helloMsg := p2pMsgHelloStruct{ 314 | p2pMsgHeader: p2pMsgHeader{ 315 | P2pID: p2pEphemeralID, 316 | Root: chainParams.GenesisBlockHash, 317 | Msg: p2pMsgHello, 318 | }, 319 | Version: p2pClientVersionString, 320 | ChainHeight: dbGetBlockchainHeight(), 321 | MyPeers: p2pPeers.GetAddresses(true), 322 | } 323 | err = p2pc.sendMsg(helloMsg) 324 | if err != nil { 325 | log.Println(err) 326 | return 327 | } 328 | log.Println("Handling connection", p2pc.address) 329 | exit := false 330 | 331 | go func() { 332 | var line []byte 333 | for { 334 | line, err = p2pc.peer.ReadBytes('\n') 335 | if err != nil { 336 | log.Println("Error reading data from", p2pc.address, err) 337 | p2pc.chanFromPeer <- StrIfMap{"_error": "Error reading data"} 338 | break 339 | } 340 | var msg StrIfMap 341 | err = json.Unmarshal(line, &msg) 342 | if err != nil { 343 | log.Println("Cannot parse JSON", strconv.QuoteToASCII(string(line)), "from", p2pc.address) 344 | p2pc.chanFromPeer <- StrIfMap{"_error": "Cannot parse JSON"} 345 | break 346 | } 347 | 348 | var root string 349 | if root, err = msg.GetString("root"); err != nil { 350 | log.Printf("Problem with chain root from %v: %v", p2pc.address, err) 351 | p2pc.chanFromPeer <- StrIfMap{"_error": "Problem with chain root"} 352 | break 353 | } 354 | if root != chainParams.GenesisBlockHash { 355 | log.Printf("Received message from %v for a different chain than mine (%s vs %s). Ignoring.", p2pc.conn, root, chainParams.GenesisBlockHash) 356 | continue 357 | } 358 | p2pc.chanFromPeer <- msg 359 | } 360 | log.Println("Shutting down receiver for", p2pc.address) 361 | exit = true // In any case, if this goroutine exits, we want to shut down everything 362 | }() 363 | 364 | ticker := time.NewTicker(1 * time.Second) 365 | defer ticker.Stop() 366 | 367 | for !exit { 368 | select { 369 | case msg := <-p2pc.chanFromPeer: 370 | // log.Printf("... chainFromPeer: %s: %s", p2pc.address, jsonifyWhatever(msg)) 371 | var _error string 372 | if _error, err = msg.GetString("_error"); err == nil { 373 | log.Printf("Fatal error from %v: %v", p2pc.address, _error) 374 | exit = true 375 | break 376 | } 377 | var cmd string 378 | if cmd, err = msg.GetString("msg"); err != nil { 379 | log.Printf("Error with msg from %v: %v", p2pc.address, err) 380 | exit = true 381 | break 382 | } 383 | switch cmd { 384 | case p2pMsgHello: 385 | p2pc.handleMsgHello(msg) 386 | case p2pMsgGetBlockHashes: 387 | p2pc.handleGetBlockHashes(msg) 388 | case p2pMsgBlockHashes: 389 | p2pc.handleBlockHashes(msg) 390 | case p2pMsgGetBlock: 391 | p2pc.handleGetBlock(msg) 392 | case p2pMsgBlock: 393 | p2pc.handleBlock(msg) 394 | } 395 | case msg := <-p2pc.chanToPeer: 396 | err := p2pc.sendMsg(msg) 397 | if err != nil { 398 | log.Println("Error sending to peer:", err) 399 | exit = true 400 | } 401 | case <-ticker.C: 402 | // so the exit variable gets tested 403 | continue 404 | } 405 | } 406 | // The connection has been dismissed 407 | } 408 | 409 | func (p2pc *p2pConnection) handleMsgHello(msg StrIfMap) { 410 | var ver string 411 | var err error 412 | if ver, err = msg.GetString("version"); err != nil { 413 | log.Println(p2pc.conn, err) 414 | return 415 | } 416 | if p2pc.chainHeight, err = msg.GetInt("chain_height"); err != nil { 417 | log.Println(p2pc.conn, err) 418 | return 419 | } 420 | if p2pc.peerID == 0 { 421 | if p2pc.peerID, err = msg.GetInt64("p2p_id"); err != nil { 422 | log.Println(p2pc.conn, err) 423 | return 424 | } 425 | } 426 | var remotePeers []string 427 | if remotePeers, err = msg.GetStringList("my_peers"); err == nil { 428 | p2pCtrlChannel <- p2pCtrlMessage{msgType: p2pCtrlConnectPeers, payload: remotePeers} 429 | } 430 | log.Printf("Hello from %v %s (%x) %d blocks", p2pc.address, ver, p2pc.peerID, p2pc.chainHeight) 431 | // Check for duplicates 432 | dup := false 433 | p2pPeers.lock.With(func() { 434 | for p := range p2pPeers.peers { 435 | if p.peerID == p2pc.peerID && p != p2pc { 436 | log.Printf("%v looks like a duplicate of %v (%x), dropping it.", p2pc.address, p.address, p2pc.peerID) 437 | dup = true 438 | return 439 | } 440 | } 441 | }) 442 | if p2pc.peerID == p2pEphemeralID { 443 | log.Printf("%v is apparently myself (%x). Dropping it.", p2pc.conn, p2pc.peerID) 444 | dup = true 445 | } 446 | if dup { 447 | p2pCoordinator.badPeers.Add(p2pc.address) 448 | err = p2pc.conn.Close() 449 | if err != nil { 450 | log.Printf("p2pc.conn.Close: %v", err) 451 | } 452 | return 453 | } 454 | p2pc.refreshTime = time.Now() 455 | if p2pc.chainHeight > dbGetBlockchainHeight() { 456 | p2pCtrlChannel <- p2pCtrlMessage{msgType: p2pCtrlSearchForBlocks, payload: p2pc} 457 | } 458 | } 459 | 460 | // Handle getblockhashes 461 | func (p2pc *p2pConnection) handleGetBlockHashes(msg StrIfMap) { 462 | var minBlockHeight int 463 | var maxBlockHeight int 464 | var err error 465 | if minBlockHeight, err = msg.GetInt("min_block_height"); err != nil { 466 | log.Println(p2pc.conn, err) 467 | return 468 | } 469 | if maxBlockHeight, err = msg.GetInt("max_block_height"); err != nil { 470 | log.Println(p2pc.conn, err) 471 | return 472 | } 473 | log.Printf("*** Sending block hashes from %d to %d to %s", minBlockHeight, maxBlockHeight, p2pc.address) 474 | respMsg := p2pMsgBlockHashesStruct{ 475 | p2pMsgHeader: p2pMsgHeader{ 476 | P2pID: p2pEphemeralID, 477 | Root: chainParams.GenesisBlockHash, 478 | Msg: p2pMsgBlockHashes, 479 | }, 480 | Hashes: dbGetHeightHashes(minBlockHeight, maxBlockHeight), 481 | } 482 | p2pc.chanToPeer <- respMsg 483 | } 484 | 485 | // Handle receiving blockhashes 486 | func (p2pc *p2pConnection) handleBlockHashes(msg StrIfMap) { 487 | var hashes map[int]string 488 | var err error 489 | if hashes, err = msg.GetIntStringMap("hashes"); err != nil { 490 | log.Println(p2pc.conn, err) 491 | return 492 | } 493 | heights := make([]int, len(hashes)) 494 | n := 0 495 | for h := range hashes { 496 | heights[n] = h 497 | n++ 498 | } 499 | sort.Ints(heights) 500 | log.Println("handleBlockHashes: got", jsonifyWhatever(heights)) 501 | for _, h := range heights { 502 | if dbBlockHeightExists(h) { 503 | log.Println("handleBlockHashes: already have block:", h) 504 | if dbGetBlockHashByHeight(h) != hashes[h] { 505 | log.Println("ERROR: Blockchain desynced: received block hash at height", h, "to be", hashes[h], "instead of", dbGetBlockHashByHeight(h)) 506 | return 507 | } 508 | continue 509 | } 510 | if p2pCoordinator.recentlyRequestedBlocks.TestAndSet(hashes[h]) { 511 | continue 512 | } 513 | log.Println("Requesting block", hashes[h]) 514 | msg := p2pMsgGetBlockStruct{ 515 | p2pMsgHeader: p2pMsgHeader{ 516 | P2pID: p2pEphemeralID, 517 | Root: chainParams.GenesisBlockHash, 518 | Msg: p2pMsgGetBlock, 519 | }, 520 | Hash: hashes[h], 521 | } 522 | p2pc.chanToPeer <- msg 523 | } 524 | } 525 | 526 | // getblock: a request to transfer a block 527 | func (p2pc *p2pConnection) handleGetBlock(msg StrIfMap) { 528 | hash, err := msg.GetString("hash") 529 | if err != nil { 530 | log.Println(p2pc.conn, err) 531 | return 532 | } 533 | dbb, err := dbGetBlock(hash) 534 | if err != nil { 535 | log.Println(p2pc.conn, err) 536 | return 537 | } 538 | fileName := blockchainGetFilename(dbb.Height) 539 | st, err := os.Stat(fileName) 540 | if err != nil { 541 | log.Println(err) 542 | return 543 | } 544 | fileSize := st.Size() 545 | 546 | var msgBlockEncoding, msgBlockData string 547 | 548 | if cfg.p2pBlockInline { 549 | f, err := os.Open(fileName) 550 | if err != nil { 551 | log.Println(err) 552 | return 553 | } 554 | defer func() { 555 | err = f.Close() 556 | if err != nil { 557 | log.Printf("handleGetBlock f.Close: %v", err) 558 | } 559 | }() 560 | var zbuf bytes.Buffer 561 | w := zlib.NewWriter(&zbuf) 562 | written, err := io.Copy(w, f) 563 | if err != nil { 564 | log.Println(err) 565 | return 566 | } 567 | if written != fileSize { 568 | log.Println("Something broke when working with zlib:", written, "vs", fileSize) 569 | return 570 | } 571 | err = w.Close() 572 | if err != nil { 573 | log.Panic(err) 574 | } 575 | msgBlockEncoding = "zlib-base64" 576 | msgBlockData = base64.StdEncoding.EncodeToString(zbuf.Bytes()) 577 | } else { 578 | msgBlockEncoding = "http" 579 | msgBlockData = fmt.Sprintf("http://%s:%d/block/%d", getLocalAddresses()[0], cfg.httpPort, dbb.Height) 580 | log.Println("*** Instructing the peer to get a block from", msgBlockData) 581 | } 582 | 583 | respMsg := p2pMsgBlockStruct{ 584 | p2pMsgHeader: p2pMsgHeader{ 585 | P2pID: p2pEphemeralID, 586 | Root: chainParams.GenesisBlockHash, 587 | Msg: p2pMsgBlock, 588 | }, 589 | Hash: hash, 590 | HashSignature: hex.EncodeToString(dbb.HashSignature), 591 | Encoding: msgBlockEncoding, 592 | Data: msgBlockData, 593 | Size: fileSize, 594 | } 595 | p2pc.chanToPeer <- respMsg 596 | log.Println("*** Sent block", hash, "to", p2pc.address) 597 | } 598 | 599 | // block: A block is received 600 | func (p2pc *p2pConnection) handleBlock(msg StrIfMap) { 601 | hash, err := msg.GetString("hash") 602 | if err != nil { 603 | log.Println(err) 604 | return 605 | } 606 | hashSignature, err := msg.GetString("hash_signature") 607 | if err != nil { 608 | log.Println(err) 609 | return 610 | } 611 | dataString, err := msg.GetString("data") 612 | if err != nil { 613 | log.Println(err) 614 | return 615 | } 616 | if dbBlockHashExists(hash) { 617 | log.Println("Replacing blocks not yet implemented") 618 | return 619 | } 620 | fileSize, err := msg.GetInt64("size") 621 | if err != nil { 622 | log.Println(err) 623 | } 624 | var blockFile *os.File 625 | encoding, err := msg.GetString("encoding") 626 | if err != nil { 627 | log.Printf("encoding: %v", err) 628 | return 629 | } 630 | if encoding == "zlib-base64" { 631 | zlibData, err := base64.StdEncoding.DecodeString(dataString) 632 | if err != nil { 633 | log.Println(err) 634 | return 635 | } 636 | blockFile, err = ioutil.TempFile("", "daisy") 637 | if err != nil { 638 | log.Println(err) 639 | return 640 | } 641 | defer func() { 642 | err = blockFile.Close() 643 | if err != nil { 644 | log.Printf("handleBlock blockFile.Close: %v", err) 645 | } 646 | err = os.Remove(blockFile.Name()) 647 | if err != nil { 648 | log.Printf("remove: %v", err) 649 | } 650 | }() 651 | r, err := zlib.NewReader(bytes.NewReader(zlibData)) 652 | if err != nil { 653 | log.Println(err) 654 | return 655 | } 656 | defer func() { 657 | err = r.Close() 658 | if err != nil { 659 | log.Printf("handleBlock r.Close: %v", err) 660 | } 661 | }() 662 | written, err := io.Copy(blockFile, r) 663 | if err != nil { 664 | log.Println(err) 665 | return 666 | } 667 | if written != fileSize { 668 | log.Println("Error decoding block: sizes don't match:", written, "vs", fileSize) 669 | return 670 | } 671 | } else if encoding == "http" { 672 | log.Println("Getting block", hash, "from", dataString) 673 | resp, err := http.Get(dataString) 674 | if err != nil { 675 | log.Println("Error receiving block at", dataString, err) 676 | return 677 | } 678 | defer resp.Body.Close() 679 | blockFile, err = ioutil.TempFile("", "daisy") 680 | if err != nil { 681 | log.Println("Error creating temp file", err) 682 | return 683 | } 684 | written, err := io.Copy(blockFile, resp.Body) 685 | if err != nil { 686 | log.Println("Error saving block:", err) 687 | blockFile.Close() 688 | os.Remove(blockFile.Name()) 689 | return 690 | } 691 | if written != fileSize { 692 | log.Println("Error decoding block: sizes don't match:", written, "vs", fileSize) 693 | blockFile.Close() 694 | os.Remove(blockFile.Name()) 695 | return 696 | } 697 | err = blockFile.Close() 698 | if err != nil { 699 | log.Printf("handleBlock blockFile.Close: %v", err) 700 | } 701 | defer func() { 702 | err = os.Remove(blockFile.Name()) 703 | if err != nil { 704 | log.Printf("remove: %v", err) 705 | } 706 | }() 707 | } else { 708 | log.Println("Unknown block encoding:", encoding) 709 | return 710 | } 711 | 712 | blk, err := OpenBlockFile(blockFile.Name()) 713 | if err != nil { 714 | log.Println("Error opening block file", p2pc.conn, err) 715 | return 716 | } 717 | blk.HashSignature, err = hex.DecodeString(hashSignature) 718 | if err != nil { 719 | log.Println("Error decoding hash signature", p2pc.conn, err) 720 | return 721 | } 722 | height, err := checkAcceptBlock(blk) 723 | if err != nil { 724 | log.Println("Cannot import block:", err) 725 | return 726 | } 727 | blk.Height = height 728 | blk.DbBlockchainBlock.TimeAccepted = time.Now() 729 | err = blockchainCopyFile(blockFile.Name(), height) 730 | if err != nil { 731 | log.Println("Cannot copy block file:", err) 732 | return 733 | } 734 | err = dbInsertBlock(blk.DbBlockchainBlock) 735 | if err != nil { 736 | log.Println("Cannot insert block:", err) 737 | return 738 | } 739 | log.Println("Accepted block", blk.Hash, "at height", blk.Height) 740 | blk.Close() 741 | } 742 | 743 | // Connect to a peer. Does everything except starting the handler goroutine. 744 | // Checks if there already is a connection of this type. 745 | func p2pConnectPeer(address string) (*p2pConnection, error) { 746 | addr, err := net.ResolveTCPAddr("tcp", address) 747 | if err != nil { 748 | return nil, err 749 | } 750 | 751 | if p2pPeers.HasAddress(addr.String()) { 752 | return nil, fmt.Errorf("Connection to %s already exists", addr.String()) 753 | } 754 | 755 | localAddresses := getLocalAddresses() 756 | if inStrings(addr.IP.String(), localAddresses) { 757 | return nil, fmt.Errorf("Refusing to connect to myself at %s", addr.IP) 758 | } 759 | 760 | conn, err := net.Dial("tcp", address) 761 | if err != nil { 762 | log.Println("Error connecting to", address, err) 763 | return nil, err 764 | } 765 | return p2pSetupPeer(address, conn) 766 | } 767 | 768 | // Creates the p2pConnection structure for the peer and adds it to the peer list. 769 | // Does not start the handler goroutine. 770 | func p2pSetupPeer(address string, conn net.Conn) (*p2pConnection, error) { 771 | p2pc := p2pConnection{ 772 | conn: conn, 773 | address: address, 774 | chanToPeer: make(chan interface{}, 5), 775 | chanFromPeer: make(chan StrIfMap, 5), 776 | } 777 | p2pPeers.Add(&p2pc) 778 | return &p2pc, nil 779 | } 780 | -------------------------------------------------------------------------------- /blockchain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "math" 12 | "os" 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | // CurrentBlockVersion is the version of the block metadata 18 | const CurrentBlockVersion = 1 19 | 20 | // GenesisBlockPreviousBlockHash is the hard-coded canonical stand-in hash of the non-existent previous block 21 | const GenesisBlockPreviousBlockHash = "1000000000000000000000000000000000000000000000000000000000000001" 22 | 23 | // ChainParams describe the genesis block and other blockchain properties 24 | var defaultChainParams = ChainParams{ 25 | ConsensusType: ChainConsensusPoA, 26 | GenesisBlockHash: "9a0ff19183d1525a36de803047de4b73eb72506be8c81296eb463476a5c2d9e2", 27 | GenesisBlockHashSignature: "30460221008b8b3b3cfee2493ef58f2f6a1f1768b564f4c9e9a341ad42912cbbcf5c3ec82f022100fbcdfd0258fa1a5b073d18f688c2fb3d8f9a7c59204c6777f2bbf1faeb1eb1ed", 28 | GenesisBlockTimestamp: "2017-05-06T10:38:50+02:00", 29 | } 30 | 31 | var chainParams = defaultChainParams 32 | 33 | const chainParamsBaseName = "chainparams.json" 34 | 35 | // Blocks (SQLite databases) are stored as flat files in a directory 36 | const blockchainSubdirectoryBaseName = "blocks" 37 | const rawBlockFilenameFormat = "%s/%04x/block_%08x.db" 38 | const rawBlockDirnameFormat = "%s/%04x" 39 | const genesisBlockHeight = 0 40 | 41 | var blockchainSubdirectory string 42 | 43 | /* 44 | * Block metadata fields: 45 | * 46 | * PreviousBlockHash|1000000000000000000000000000000000000000000000000000000000000001 47 | * Creator|ivoras@gmail.com 48 | * PreviousBlockHashSignature|3046022100db037ae6cb3c6e37cbc8ec592ba7eed2e6d18e6a3caedc4e2e81581eb97acb67022100d46d8ed27b5d78a8509b1eb8549c9b6b8f1c0a134c0c7af23bb93ab8cc842e2d 49 | * CreatorPublicKey|1:a3c07ef6cbee246f231a61ff36bbcd8e8563723e3703eb345ecdd933d7709ae2 50 | * Version|1 51 | * 52 | * Of these, only the Creator field is optional. By default, for new blocks, it is taken 53 | * from the "BlockCreator" field in the pubkey metadata (if it exists). 54 | */ 55 | 56 | // Block is the working representation of a blockchain block 57 | type Block struct { 58 | *DbBlockchainBlock 59 | db *sql.DB 60 | } 61 | 62 | // BlockKeyOp is the representation of a key op record from the blocks' _keys table. 63 | type BlockKeyOp struct { 64 | op string 65 | publicKeyHash string 66 | publicKeyBytes []byte 67 | signatureKeyHash string 68 | signature []byte 69 | metadata map[string]string 70 | } 71 | 72 | func ensureBlockchainSubdirectoryExists() { 73 | blockchainSubdirectory = fmt.Sprintf("%s/%s", cfg.DataDir, blockchainSubdirectoryBaseName) 74 | if _, err := os.Stat(blockchainSubdirectory); err != nil { 75 | // Probably doesn't exist, create it 76 | log.Println("Creating directory", blockchainSubdirectory) 77 | err := os.Mkdir(blockchainSubdirectory, 0700) 78 | if err != nil { 79 | log.Fatalln(err) 80 | } 81 | } 82 | } 83 | 84 | // Initializes the blockchain: creates database entries and the genesis block file 85 | func blockchainInit(createDefault bool) { 86 | ensureBlockchainSubdirectoryExists() 87 | if dbGetBlockchainHeight() == -1 && createDefault { 88 | log.Println("Writing down the default Genesis block. Let there be light.") 89 | 90 | // This is basically testing the crypto code, no real purpose. 91 | keypair, publicKeyHash, err := cryptoGetAPrivateKey() 92 | if err != nil { 93 | log.Panicln(err) 94 | } 95 | signature, err := cryptoSignPublicKeyHash(keypair, publicKeyHash) 96 | if err != nil { 97 | log.Panicln(err) 98 | } 99 | if err = cryptoVerifyPublicKeyHashSignature(&keypair.PublicKey, publicKeyHash, signature); err != nil { 100 | log.Panicln(err) 101 | } 102 | 103 | /* 104 | // Sign the Genesis block's "previous block" hash 105 | genesisPrevBlockHash, err := hex.DecodeString(GenesisBlockPreviousBlockHash) 106 | if err != nil { 107 | log.Panicln(err) 108 | } 109 | signature, err = cryptoSignBytes(keypair, genesisPrevBlockHash) 110 | if err != nil { 111 | log.Panicln(err) 112 | } 113 | log.Println(GenesisBlockPreviousBlockHash) 114 | log.Println(hex.EncodeToString(signature)) 115 | */ 116 | 117 | // Bring the genesis block into existence 118 | genesisBlock := MustAsset("bindata/genesis.db") 119 | if hashBytesToHexString(genesisBlock) != chainParams.GenesisBlockHash { 120 | log.Panicln("Genesis block hash unexpected:", hashBytesToHexString(genesisBlock)) 121 | } 122 | 123 | /* 124 | // Sign the Genesis block's hash 125 | genesisBlockHash, err := hex.DecodeString(GenesisBlockHash) 126 | if err != nil { 127 | log.Panicln(err) 128 | } 129 | signature, err = cryptoSignBytes(keypair, genesisBlockHash) 130 | if err != nil { 131 | log.Panicln(err) 132 | } 133 | log.Println(hex.EncodeToString(signature)) 134 | */ 135 | 136 | if err := blockchainEnsureBlockDir(genesisBlockHeight); err != nil { 137 | log.Panicln(err) 138 | } 139 | genesisBlockFilename := blockchainGetFilename(genesisBlockHeight) 140 | err = ioutil.WriteFile(genesisBlockFilename, genesisBlock, 0644) 141 | if err != nil { 142 | log.Panic(err) 143 | } 144 | b, err := OpenBlockFile(genesisBlockFilename) 145 | if err != nil { 146 | log.Panicln(err) 147 | } 148 | b.Height = 0 149 | b.TimeAccepted, err = time.Parse(time.RFC3339, chainParams.GenesisBlockTimestamp) 150 | if err != nil { 151 | log.Panicln("Error parsing genesis block timestamp", err) 152 | } 153 | b.HashSignature, err = hex.DecodeString(chainParams.GenesisBlockHashSignature) 154 | if err != nil { 155 | log.Panicln(err) 156 | } 157 | blockKeyOps, err := b.dbGetKeyOps() 158 | if err != nil { 159 | log.Panicln(err) 160 | } 161 | for _, keyOps := range blockKeyOps { 162 | for _, keyOp := range keyOps { 163 | if dbPublicKeyExists(keyOp.publicKeyHash) { 164 | continue 165 | } 166 | dbWritePublicKey(keyOp.publicKeyBytes, keyOp.publicKeyHash, 0) 167 | } 168 | } 169 | err = dbInsertBlock(b.DbBlockchainBlock) 170 | if err != nil { 171 | log.Panicln(err) 172 | } 173 | } else { 174 | // The chainparams file will only exist for non-default blockchains 175 | cpFilename := fmt.Sprintf("%s/%s", cfg.DataDir, chainParamsBaseName) 176 | if fileExists(cpFilename) { 177 | log.Println("Loading custom blockchain params from", cpFilename) 178 | cpJSON, err := ioutil.ReadFile(cpFilename) 179 | if err != nil { 180 | log.Fatal("Error reading chainparams file", cpFilename, err) 181 | } 182 | err = json.Unmarshal(cpJSON, &chainParams) 183 | if err != nil { 184 | log.Fatal("Error decoding chainparams file", cpFilename, err) 185 | } 186 | peers := dbGetSavedPeers() 187 | for _, peer := range chainParams.BootstrapPeers { 188 | _, ok := peers[peer] 189 | if !ok { 190 | dbSavePeer(peer) 191 | } 192 | } 193 | } else { 194 | log.Println("Using default blockchain params") 195 | } 196 | log.Println("P2P peers:", dbGetSavedPeers()) 197 | } 198 | err := blockchainVerifyEverything() 199 | if err != nil { 200 | log.Fatalf("blockchainVerifyEverything: %v", err) 201 | } 202 | } 203 | 204 | // Verifies the entire blockchain to see if there are errors. 205 | // TODO: Dynamic adding and revoking of key is not yet checked 206 | func blockchainVerifyEverything() error { 207 | if cfg.faster { 208 | log.Println("Skipping blockchain consistency checks") 209 | return nil 210 | } 211 | log.Println("Verifying all the blocks (use --faster to skip)...") 212 | maxHeight := dbGetBlockchainHeight() 213 | for height := 0; height <= maxHeight; height++ { 214 | if height > 0 && height%1000 == 0 { 215 | log.Println("Verifying block", height) 216 | } 217 | if err := blockchainEnsureBlockDir(height); err != nil { 218 | return err 219 | } 220 | blockFilename := blockchainGetFilename(height) 221 | fileHash, err := hashFileToHexString(blockFilename) 222 | if err != nil { 223 | return fmt.Errorf("block %d: %v", height, err) 224 | } 225 | dbb, err := dbGetBlockByHeight(height) 226 | if err != nil { 227 | return fmt.Errorf("block %d: %v", height, err) 228 | } 229 | if fileHash != dbb.Hash { 230 | return fmt.Errorf("block %d: file hash %s doesn't match db hash %s", height, fileHash, dbb.Hash) 231 | } 232 | if height == 0 && fileHash != chainParams.GenesisBlockHash { 233 | return fmt.Errorf("block %d: it's supposed to be the genesis block but its hash doesn't match %s", 234 | height, chainParams.GenesisBlockHash) 235 | } 236 | dbpk, err := dbGetPublicKey(dbb.SignaturePublicKeyHash) 237 | if err != nil { 238 | return fmt.Errorf("block %d: error getting public key %s", height, dbb.SignaturePublicKeyHash) 239 | } 240 | creatorPublicKey, err := cryptoDecodePublicKeyBytes(dbpk.publicKeyBytes) 241 | if err != nil { 242 | return fmt.Errorf("block %d: cannot decode public key %s", height, dbb.SignaturePublicKeyHash) 243 | } 244 | hashBytes, err := hex.DecodeString(dbb.Hash) 245 | if err != nil { 246 | return fmt.Errorf("block %d: cannot decode hash %s", height, dbb.Hash) 247 | } 248 | err = cryptoVerifyBytes(creatorPublicKey, hashBytes, dbb.HashSignature) 249 | if err != nil { 250 | log.Println(creatorPublicKey, hashBytes, dbb.HashSignature) 251 | return fmt.Errorf("block %d: block hash signature is invalid (%v)", height, err) 252 | } 253 | previousHashBytes, err := hex.DecodeString(dbb.PreviousBlockHash) 254 | if err != nil { 255 | return fmt.Errorf("block %d: cannot decode previous block hash %s", height, dbb.PreviousBlockHash) 256 | } 257 | err = cryptoVerifyBytes(creatorPublicKey, previousHashBytes, dbb.PreviousBlockHashSignature) 258 | if err != nil { 259 | return fmt.Errorf("block %d: previous block hash signature is invalid (%v)", height, err) 260 | } 261 | b, err := OpenBlockByHeight(height) 262 | if err != nil { 263 | return fmt.Errorf("block %d: cannot open block db file: %v", height, err) 264 | } 265 | blockKeyOps, err := b.dbGetKeyOps() 266 | if err != nil { 267 | if err := b.Close(); err != nil { 268 | panic(err) 269 | } 270 | return fmt.Errorf("block %d: cannot get key ops: %v", height, err) 271 | } 272 | if err = b.Close(); err != nil { 273 | panic(err) 274 | } 275 | Q := QuorumForHeight(height) 276 | for keyOpKeyHash, keyOps := range blockKeyOps { 277 | if len(keyOps) != Q { 278 | return fmt.Errorf("block %d: key ops for %s don't have quorum: %d vs Q=%d", 279 | height, keyOpKeyHash, len(keyOps), Q) 280 | } 281 | op := keyOps[0].op 282 | for _, kop := range keyOps { 283 | if kop.op != op { 284 | return fmt.Errorf("block %d: key ops for %s don't match: %s vs %s", 285 | height, keyOpKeyHash, kop.op, op) 286 | } 287 | dbSigningKey, err := dbGetPublicKey(kop.signatureKeyHash) 288 | if err != nil { 289 | return fmt.Errorf("block %d: cannot get public key %s from main db", height, kop.signatureKeyHash) 290 | } 291 | signingKey, err := cryptoDecodePublicKeyBytes(dbSigningKey.publicKeyBytes) 292 | if err != nil { 293 | return fmt.Errorf("block %d: cannot decode public key %s", height, dbSigningKey.publicKeyHash) 294 | } 295 | if err = cryptoVerifyPublicKeyHashSignature(signingKey, kop.publicKeyHash, kop.signature); err != nil { 296 | return fmt.Errorf("block %d: key op signature invalid for signer %s: %v", height, kop.signatureKeyHash, err) 297 | } 298 | } 299 | } 300 | } 301 | return nil 302 | } 303 | 304 | // Checks if a new block can be accepted to extend the blockchain 305 | func checkAcceptBlock(blk *Block) (int, error) { 306 | // Step 1: Does the block fit, i.e. does it extend the chain? 307 | if blk.Version != CurrentBlockVersion { 308 | return 0, fmt.Errorf("Unsupported block version: %d", blk.Version) 309 | } 310 | prevBlk, err := dbGetBlock(blk.PreviousBlockHash) 311 | if err != nil { 312 | return 0, fmt.Errorf("Cannot find previous block %s: %v", blk.PreviousBlockHash, err) 313 | } 314 | thisBlockHeight := prevBlk.Height + 1 315 | if _, err = dbGetBlockByHeight(thisBlockHeight); err == nil { 316 | return 0, fmt.Errorf("The block to accept would replace an existing block, and this is not supported yet (height=%d)", prevBlk.Height+1) 317 | } 318 | // Step 2: Is the block signed by a valid signatory? 319 | signatoryPubKey, err := dbGetPublicKey(blk.SignaturePublicKeyHash) 320 | if err != nil { 321 | return 0, fmt.Errorf("Cannot find an accepted public key %s signing the block", blk.SignaturePublicKeyHash) 322 | } 323 | if signatoryPubKey.isRevoked { 324 | return 0, fmt.Errorf("The public key %s signing the block is revoked on %v", blk.SignaturePublicKeyHash, signatoryPubKey.timeRevoked) 325 | } 326 | sigPubKey, err := cryptoDecodePublicKeyBytes(signatoryPubKey.publicKeyBytes) 327 | if err != nil { 328 | return 0, fmt.Errorf("Cannot decode public key %s: %v", blk.SignaturePublicKeyHash, err) 329 | } 330 | err = cryptoVerifyHexBytes(sigPubKey, blk.PreviousBlockHash, blk.PreviousBlockHashSignature) 331 | if err != nil { 332 | return 0, fmt.Errorf("Verification of previous block hash has failed: %v", err) 333 | } 334 | err = cryptoVerifyHexBytes(sigPubKey, blk.Hash, blk.HashSignature) 335 | if err != nil { 336 | return 0, fmt.Errorf("Verification of block hash has failed: %v", err) 337 | } 338 | allKeyOps, err := blk.dbGetKeyOps() 339 | if err != nil { 340 | return 0, err 341 | } 342 | targetQuorum := QuorumForHeight(thisBlockHeight) 343 | for key, keyOps := range allKeyOps { 344 | if len(keyOps) < targetQuorum { 345 | return 0, fmt.Errorf("Quorum of %d not met for key ops on key %s", targetQuorum, key) 346 | } 347 | for _, keyOp := range keyOps { 348 | signatoryPubKey, err = dbGetPublicKey(keyOp.signatureKeyHash) 349 | if err != nil { 350 | return 0, fmt.Errorf("Error retrieving supposedly key op signatory %s", keyOp.signatureKeyHash) 351 | } 352 | sigPubKey, err := cryptoDecodePublicKeyBytes(signatoryPubKey.publicKeyBytes) 353 | if err != nil { 354 | return 0, fmt.Errorf("Cannot decode public key %s: %v", signatoryPubKey.publicKeyHash, err) 355 | } 356 | err = cryptoVerifyPublicKeyHashSignature(sigPubKey, key, keyOp.signature) 357 | if err != nil { 358 | return 0, fmt.Errorf("Failed verification of key op for %s by %s", key, keyOp.signatureKeyHash) 359 | } 360 | } 361 | // At this point, all required signatures have been verified 362 | if keyOps[0].op == "A" { 363 | // Add the key to the list of valid signatories. But first, check if it already exists. 364 | _, err := dbGetPublicKey(key) 365 | if err == nil { 366 | return 0, fmt.Errorf("Attempt to add an already existing key to the list of signatores") 367 | } 368 | dbWritePublicKey(keyOps[0].publicKeyBytes, key, thisBlockHeight) 369 | } else if keyOps[0].op == "R" { 370 | // Revoke the key. But first, check if it's already revoked. 371 | dbpk, err := dbGetPublicKey(key) 372 | if err != nil { 373 | return 0, fmt.Errorf("Cannot retrieve key to revoke: %s", key) 374 | } 375 | if dbpk.isRevoked { 376 | return 0, fmt.Errorf("Attempt to revoke a key which is already revoked: %s", key) 377 | } 378 | dbRevokePublicKey(key) 379 | } else { 380 | return 0, fmt.Errorf("Invalid key op: %s", keyOps[0].op) 381 | } 382 | } 383 | // Everything's ok, the block is ok to import. 384 | return thisBlockHeight, nil 385 | } 386 | 387 | // QuorumForHeight calculates the required key op quorum for the given block height 388 | func QuorumForHeight(h int) int { 389 | if h < 149 { 390 | return 1 391 | } 392 | return int(math.Log(float64(h)) * 2) 393 | } 394 | 395 | // Formats the block height into a blockchain file (SQLite database) filename 396 | func blockchainGetFilename(h int) string { 397 | return fmt.Sprintf(rawBlockFilenameFormat, blockchainSubdirectory, h/65536, h) 398 | } 399 | 400 | func blockchainEnsureBlockDir(h int) error { 401 | dirName := fmt.Sprintf(rawBlockDirnameFormat, blockchainSubdirectory, h/65536) 402 | return os.MkdirAll(dirName, 0755) 403 | } 404 | 405 | // OpenBlockByHeight opens a block stored in the blockchain at the given height 406 | func OpenBlockByHeight(height int) (*Block, error) { 407 | b := Block{DbBlockchainBlock: &DbBlockchainBlock{Height: height}} 408 | if err := blockchainEnsureBlockDir(height); err != nil { 409 | return nil, err 410 | } 411 | blockFilename := blockchainGetFilename(height) 412 | hash, err := hashFileToHexString(blockFilename) 413 | if err != nil { 414 | return nil, err 415 | } 416 | dbb, err := dbGetBlockByHeight(height) 417 | if err != nil { 418 | return nil, err 419 | } 420 | if hash != dbb.Hash { 421 | return nil, fmt.Errorf("Recorded block hash doesn't match actual: %s vs %s", dbb.Hash, hash) 422 | } 423 | b.DbBlockchainBlock = dbb 424 | b.db, err = dbOpen(blockFilename, true) 425 | if err != nil { 426 | return nil, err 427 | } 428 | return &b, nil 429 | } 430 | 431 | // OpenBlockFile reads block metadata from the given database file. 432 | // Note that it will not fill-in all the fields. Notable, height is not stored in the block db's metadata. 433 | func OpenBlockFile(fileName string) (*Block, error) { 434 | hash, err := hashFileToHexString(fileName) 435 | if err != nil { 436 | return nil, err 437 | } 438 | db, err := dbOpen(fileName, true) 439 | if err != nil { 440 | return nil, err 441 | } 442 | b := Block{DbBlockchainBlock: &DbBlockchainBlock{Hash: hash}, db: db} 443 | if b.Version, err = b.dbGetMetaInt("Version"); err != nil { 444 | return nil, err 445 | } 446 | if b.PreviousBlockHash, err = b.dbGetMetaString("PreviousBlockHash"); err != nil { 447 | return nil, err 448 | } 449 | if b.SignaturePublicKeyHash, err = b.dbGetMetaString("CreatorPublicKey"); err != nil { 450 | return nil, err 451 | } 452 | if b.PreviousBlockHashSignature, err = b.dbGetMetaHexBytes("PreviousBlockHashSignature"); err != nil { 453 | return nil, err 454 | } 455 | if b.TimeAccepted, err = b.dbGetMetaTime("Timestamp"); err != nil { 456 | st, err := os.Stat(fileName) 457 | if err != nil { 458 | return nil, err 459 | } 460 | b.TimeAccepted = st.ModTime() 461 | } 462 | return &b, nil 463 | } 464 | 465 | func (b *Block) Close() error { 466 | return b.db.Close() 467 | } 468 | 469 | // Returns an integer value from the _meta table within the block 470 | func (b *Block) dbGetMetaInt(key string) (int, error) { 471 | var value string 472 | if err := b.db.QueryRow("SELECT value FROM _meta WHERE key=?", key).Scan(&value); err != nil { 473 | return -1, err 474 | } 475 | return strconv.Atoi(value) 476 | } 477 | 478 | // Returns a timestamp value from the _meta table within the block 479 | func (b *Block) dbGetMetaTime(key string) (time.Time, error) { 480 | var value string 481 | if err := b.db.QueryRow("SELECT value FROM _meta WHERE key=?", key).Scan(&value); err != nil { 482 | return time.Time{}, err 483 | } 484 | t, err := time.Parse(time.RFC3339, value) 485 | return t, err 486 | } 487 | 488 | // Returns a string value from the _meta table within the block 489 | func (b *Block) dbGetMetaString(key string) (string, error) { 490 | var value string 491 | if err := b.db.QueryRow("SELECT value FROM _meta WHERE key=?", key).Scan(&value); err != nil { 492 | return "", err 493 | } 494 | return value, nil 495 | } 496 | 497 | // Returns a byte blob value from the _meta table within the block (stored in the db as a hex string) 498 | func (b *Block) dbGetMetaHexBytes(key string) ([]byte, error) { 499 | var value string 500 | if err := b.db.QueryRow("SELECT value FROM _meta WHERE key=?", key).Scan(&value); err != nil { 501 | return nil, err 502 | } 503 | return hex.DecodeString(value) 504 | } 505 | 506 | // Returns a map of key operations stored in the block. Map keys are public key hashes, values are lists of ops. 507 | func (b *Block) dbGetKeyOps() (map[string][]BlockKeyOp, error) { 508 | var count int 509 | if err := b.db.QueryRow("SELECT COUNT(*) FROM _keys").Scan(&count); err != nil { 510 | log.Println("Error reading db _keys") 511 | return nil, err 512 | } 513 | keyOps := make(map[string][]BlockKeyOp) 514 | rows, err := b.db.Query("SELECT op, pubkey_hash, pubkey, sigkey_hash, signature, COALESCE(metadata, '') FROM _keys") 515 | if err != nil { 516 | return nil, err 517 | } 518 | defer rows.Close() 519 | for rows.Next() { 520 | var publicKeyHex string 521 | var signatureHex string 522 | var metadataJSON string 523 | var keyOp BlockKeyOp 524 | if err = rows.Scan(&keyOp.op, &keyOp.publicKeyHash, &publicKeyHex, &keyOp.signatureKeyHash, &signatureHex, &metadataJSON); err != nil { 525 | return nil, err 526 | } 527 | if keyOp.publicKeyBytes, err = hex.DecodeString(publicKeyHex); err != nil { 528 | return nil, err 529 | } 530 | publicKey, err := cryptoDecodePublicKeyBytes(keyOp.publicKeyBytes) 531 | if err != nil { 532 | return nil, err 533 | } 534 | if keyOp.publicKeyHash != cryptoMustGetPublicKeyHash(publicKey) { 535 | return nil, fmt.Errorf("Public key hash doesn't match for %s", keyOp.publicKeyHash) 536 | } 537 | if keyOp.signature, err = hex.DecodeString(signatureHex); err != nil { 538 | return nil, err 539 | } 540 | if metadataJSON != "" { 541 | if err = json.Unmarshal([]byte(metadataJSON), &keyOp.metadata); err != nil { 542 | return nil, err 543 | } 544 | } 545 | if _, ok := keyOps[keyOp.publicKeyHash]; ok { 546 | keyOps[keyOp.publicKeyHash] = append(keyOps[keyOp.publicKeyHash], keyOp) 547 | } else { 548 | keyOps[keyOp.publicKeyHash] = make([]BlockKeyOp, 1) 549 | keyOps[keyOp.publicKeyHash][0] = keyOp 550 | } 551 | } 552 | for hash, oneKeyOps := range keyOps { 553 | for _, keyOp := range oneKeyOps { 554 | if keyOp.op != oneKeyOps[0].op { 555 | return nil, fmt.Errorf("Mixed key ops for a single public key %s", hash) 556 | } 557 | } 558 | } 559 | return keyOps, nil 560 | } 561 | 562 | // Ensures special metadata tables exist in a SQLite database 563 | func dbEnsureBlockchainTables(db *sql.DB) { 564 | if !dbTableExists(db, "_meta") { 565 | _, err := db.Exec(metaTableCreate) 566 | if err != nil { 567 | log.Fatal(err) 568 | } 569 | } 570 | if !dbTableExists(db, "_keys") { 571 | _, err := db.Exec(keysTableCreate) 572 | if err != nil { 573 | log.Fatal(err) 574 | } 575 | } 576 | } 577 | 578 | // Stores a key-value pair into the _meta table in the SQLite database 579 | func dbSetMetaString(db *sql.DB, key string, value string) error { 580 | _, err := db.Exec("INSERT OR REPLACE INTO _meta(key, value) VALUES (?, ?)", key, value) 581 | return err 582 | } 583 | 584 | // Stores a key-value pair into the _meta table in the SQLite database 585 | func dbSetMetaInt(db *sql.DB, key string, value int) error { 586 | _, err := db.Exec("INSERT OR REPLACE INTO _meta(key, value) VALUES (?, ?)", key, value) 587 | return err 588 | } 589 | 590 | // Copies a given file to the blockchain directory and names it as a block with the given height 591 | func blockchainCopyFile(fn string, height int) error { 592 | if err := blockchainEnsureBlockDir(height); err != nil { 593 | return err 594 | } 595 | blockFilename := blockchainGetFilename(height) 596 | in, err := os.Open(fn) 597 | if err != nil { 598 | return err 599 | } 600 | defer func() { 601 | err = in.Close() 602 | if err != nil { 603 | log.Printf("blockchainCopyFile in.Close: %v", err) 604 | } 605 | }() 606 | out, err := os.Create(blockFilename) 607 | if err != nil { 608 | return err 609 | } 610 | _, err = io.Copy(out, in) 611 | if err != nil { 612 | return err 613 | } 614 | return out.Close() 615 | } 616 | --------------------------------------------------------------------------------