Skip to content

Commit

Permalink
Merge go-minimial-pbft code (ethereum#51)
Browse files Browse the repository at this point in the history
* add tendermint code

* add node.go

* use old topics

* remove unused proposerReptition in MakeChainState()

Co-authored-by: Qi Zhou <qzhou64@gmail.com>
  • Loading branch information
qizhou and Qi Zhou authored Mar 16, 2022
1 parent 8800def commit 27675ad
Show file tree
Hide file tree
Showing 24 changed files with 5,109 additions and 185 deletions.
137 changes: 137 additions & 0 deletions cmd/tendermint/default_block_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package main

import (
"encoding/binary"
"fmt"

"github.com/ethereum/go-ethereum/consensus/tendermint/consensus"
"github.com/ethereum/go-ethereum/rlp"
"github.com/syndtr/goleveldb/leveldb"
)

type DefaultBlockStore struct {
db *leveldb.DB
}

func NewDefaultBlockStore(db *leveldb.DB) consensus.BlockStore {
return &DefaultBlockStore{
db: db,
}
}

func (bs *DefaultBlockStore) Height() uint64 {
data, err := bs.db.Get([]byte("height"), nil)
if err != nil {
return 0
}

if len(data) != 8 {
return 0
}

return binary.BigEndian.Uint64(data)
}

func (bs *DefaultBlockStore) Base() uint64 {
return 0
}

func (bs *DefaultBlockStore) Size() uint64 {
height := bs.Height()
if height == 0 {
return 0
}
return height + 1 - bs.Base()
}

func (bs *DefaultBlockStore) LoadBlock(height uint64) *consensus.FullBlock {
hd := make([]byte, 8)
binary.BigEndian.PutUint64(hd, height)
bk := []byte("block")
bk = append(bk, hd...)

blockData, err := bs.db.Get(bk, nil)
if err != nil {
return nil
}

b := &consensus.FullBlock{}
if rlp.DecodeBytes(blockData, b) != nil {
panic(fmt.Errorf("error from block: %w", err))
}
return b
}

func (bs *DefaultBlockStore) LoadBlockCommit(height uint64) *consensus.Commit {
hd := make([]byte, 8)
binary.BigEndian.PutUint64(hd, height)
ck := []byte("commit")
ck = append(ck, hd...)

commitData, err := bs.db.Get(ck, nil)
if err != nil {
return nil
}

c := &consensus.Commit{}
if rlp.DecodeBytes(commitData, c) != nil {
panic(fmt.Errorf("error from block: %w", err))
}
return c
}

func (bs *DefaultBlockStore) SaveBlock(b *consensus.FullBlock, c *consensus.Commit) {
// sanity check?
if b.NumberU64() != bs.Height()+1 {
panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.Height()+1, b.NumberU64()))
}

hd := make([]byte, 8)
binary.BigEndian.PutUint64(hd, b.NumberU64())
bk := []byte("block")
bk = append(bk, hd...)

blockData, err := rlp.EncodeToBytes(b)
if err != nil {
// error?
return
}

if err := bs.db.Put(bk, blockData, nil); err != nil {
return
}

commitData, err := rlp.EncodeToBytes(c)
if err != nil {
return
}

ck := []byte("commit")
ck = append(ck, hd...)
if err := bs.db.Put(ck, commitData, nil); err != nil {
return
}

data := make([]byte, 8)
binary.BigEndian.PutUint64(data, b.NumberU64())
if err := bs.db.Put([]byte("height"), data, nil); err != nil {
return
}
}

// LoadSeenCommit returns the last locally seen Commit before being
// cannonicalized. This is useful when we've seen a commit, but there
// has not yet been a new block at `height + 1` that includes this
// commit in its block.LastCommit.
func (bs *DefaultBlockStore) LoadSeenCommit() *consensus.Commit {
commitData, err := bs.db.Get([]byte("seen_commit"), nil)
if err != nil {
return nil
}

c := &consensus.Commit{}
if rlp.DecodeBytes(commitData, c) != nil {
panic(fmt.Errorf("error from block: %w", err))
}
return c
}
90 changes: 90 additions & 0 deletions cmd/tendermint/keygen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"io/ioutil"
"os"

"github.com/ethereum/go-ethereum/consensus/tendermint/consensus"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/spf13/cobra"
)

var keyDescription *string
var nolock *bool

const (
ValidatorKeyArmoredBlock = "VALIDATOR PRIVATE KEY"
)

var KeygenCmd = &cobra.Command{
Use: "keygen [KEYFILE]",
Short: "Create validator key at the specified path",
Run: runKeygen,
Args: cobra.ExactArgs(1),
}

func init() {
keyDescription = KeygenCmd.Flags().String("desc", "", "Human-readable key description (optional)")
nolock = KeygenCmd.Flags().Bool("nolock", false, "Do not lock memory (less safer)")
}

func runKeygen(cmd *cobra.Command, args []string) {
if !*nolock {
lockMemory()
} else {
log.Info("Skipping lockMemory()")
}
setRestrictiveUmask()

log.Info("Creating new key", "location", args[0])

gk := consensus.GeneratePrivValidatorLocal().(*consensus.PrivValidatorLocal)
pk, err := gk.GetPubKey(context.Background())
if err != nil {
log.Error("Failed to get pub key", "err", err)
return
}

log.Info("Key generated", "address", pk.Address())

err = writeValidatorKey(gk.PrivKey, *keyDescription, args[0], false)
if err != nil {
log.Error("Failed to write key", "err", err)
return
}
}

// loadValidatorKey loads a serialized guardian key from disk.
func loadValidatorKey(filename string) (*ecdsa.PrivateKey, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}

gk, err := crypto.ToECDSA(b)
if err != nil {
return nil, fmt.Errorf("failed to deserialize raw key data: %w", err)
}

return gk, nil
}

// writeValidatorKey serializes a guardian key and writes it to disk.
func writeValidatorKey(key *ecdsa.PrivateKey, description string, filename string, unsafe bool) error {
if _, err := os.Stat(filename); !os.IsNotExist(err) {
return errors.New("refusing to override existing key")
}

b := crypto.FromECDSA(key)

err := ioutil.WriteFile(filename, b, 0600)
if err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
return nil
}
79 changes: 79 additions & 0 deletions cmd/tendermint/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"fmt"
"os"
"syscall"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sys/unix"

homedir "github.com/mitchellh/go-homedir"
)

var cfgFile string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "mpbft",
Short: "Minimal PBFT node server",
}

func init() {
cobra.OnInitialize(initConfig)

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.guardiand.yaml)")
rootCmd.AddCommand(NodeCmd)
rootCmd.AddCommand(KeygenCmd)
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}

// Search config in home directory with name ".guardiand" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".guardiand.yaml")
}

viper.AutomaticEnv() // read in environment variables that match

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}

// lockMemory locks current and future pages in memory to protect secret keys from being swapped out to disk.
// It's possible (and strongly recommended) to deploy Wormhole such that keys are only ever
// stored in memory and never touch the disk. This is a privileged operation and requires CAP_IPC_LOCK.
func lockMemory() {
err := unix.Mlockall(syscall.MCL_CURRENT | syscall.MCL_FUTURE)
if err != nil {
fmt.Printf("Failed to lock memory: %v (CAP_IPC_LOCK missing?)\n", err)
os.Exit(1)
}
}

// setRestrictiveUmask masks the group and world bits. This ensures that key material
// and sockets we create aren't accidentally group- or world-readable.
func setRestrictiveUmask() {
syscall.Umask(0077) // cannot fail
}

func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Loading

0 comments on commit 27675ad

Please sign in to comment.