Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ module github.com/lazyledger/optimint
go 1.15

require (
github.com/fortytw2/leaktest v1.3.0
github.com/dgraph-io/badger/v3 v3.2011.1
github.com/go-kit/kit v0.10.0
github.com/gogo/protobuf v1.3.2
github.com/ipfs/go-log v1.0.4
github.com/lazyledger/lazyledger-core v0.0.0-20210219190522-0eccfb24e2aa
github.com/libp2p/go-libp2p v0.13.0
github.com/libp2p/go-libp2p-core v0.8.5
github.com/libp2p/go-libp2p-crypto v0.1.0
github.com/libp2p/go-libp2p-discovery v0.5.0
github.com/libp2p/go-libp2p-kad-dht v0.11.1
github.com/libp2p/go-libp2p-pubsub v0.4.1
github.com/minio/sha256-simd v0.1.1
github.com/multiformats/go-multiaddr v0.3.1
github.com/prometheus/client_golang v1.8.0
github.com/stretchr/testify v1.7.0
go.uber.org/multierr v1.6.0
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc // indirect
)
36 changes: 30 additions & 6 deletions go.sum

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions mocks/BlockStore.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/lazyledger/optimint/config"
"github.com/lazyledger/optimint/mempool"
"github.com/lazyledger/optimint/p2p"
"github.com/lazyledger/optimint/store"
)

type Node struct {
Expand All @@ -34,6 +35,8 @@ type Node struct {
mempoolIDs *mempoolIDs
incomingTxCh chan *p2p.Tx

BlockStore store.BlockStore

// keep context here only because of API compatibility
// - it's used in `OnStart` (defined in service.Service interface)
ctx context.Context
Expand Down Expand Up @@ -68,6 +71,7 @@ func NewNode(ctx context.Context, conf config.NodeConfig, nodeKey crypto.PrivKey
Mempool: mp,
mempoolIDs: newMempoolIDs(),
incomingTxCh: make(chan *p2p.Tx),
BlockStore: store.NewBlockStore(),
ctx: ctx,
}
node.BaseService = *service.NewBaseService(logger, "Node", node)
Expand Down
29 changes: 27 additions & 2 deletions rpcclient/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,33 @@ func (l *Local) TxSearch(ctx context.Context, query string, prove bool, page, pe
}

func (l *Local) Status(ctx context.Context) (*ctypes.ResultStatus, error) {
// needs block store, P2P layer
panic("Status - not implemented!")
latest, err := l.node.BlockStore.LoadBlock(l.node.BlockStore.Height())
if err != nil {
// TODO(tzdybal): extract error
return nil, fmt.Errorf("failed to find latest block: %w", err)
}

latestBlockHash := latest.Header.DataHash
latestAppHash := latest.Header.AppHash
latestHeight := latest.Header.Height
latestBlockTimeNano := latest.Header.Time

result := &ctypes.ResultStatus{
// TODO(tzdybal): NodeInfo, ValidatorInfo
SyncInfo: ctypes.SyncInfo{
LatestBlockHash: latestBlockHash[:],
LatestAppHash: latestAppHash[:],
LatestBlockHeight: int64(latestHeight),
LatestBlockTime: time.Unix(0, int64(latestBlockTimeNano)),
// TODO(tzdybal): add missing fields
//EarliestBlockHash: earliestBlockHash,
//EarliestAppHash: earliestAppHash,
//EarliestBlockHeight: earliestBlockHeight,
//EarliestBlockTime: time.Unix(0, earliestBlockTimeNano),
//CatchingUp: env.ConsensusReactor.WaitSync(),
},
}
return result, nil
}

func (l *Local) BroadcastEvidence(ctx context.Context, evidence types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
Expand Down
39 changes: 39 additions & 0 deletions store/badger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package store

import "github.com/dgraph-io/badger/v3"

var _ KVStore = &BadgerKV{}

type BadgerKV struct {
db *badger.DB
}

func (b *BadgerKV) Get(key []byte) ([]byte, error) {
txn := b.db.NewTransaction(false)
defer txn.Discard()
item, err := txn.Get(key)
if err != nil {
return nil, err
}
return item.ValueCopy(nil)
}

func (b *BadgerKV) Set(key []byte, value []byte) error {
txn := b.db.NewTransaction(true)
err := txn.Set(key, value)
if err != nil {
txn.Discard()
return err
}
return txn.Commit()
}

func (b *BadgerKV) Delete(key []byte) error {
txn := b.db.NewTransaction(true)
err := txn.Delete(key)
if err != nil {
txn.Discard()
return err
}
return txn.Commit()
}
121 changes: 121 additions & 0 deletions store/blockstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package store

import (
"bytes"
"encoding/binary"
"encoding/gob"
"sync"

"github.com/lazyledger/optimint/types"
"github.com/minio/sha256-simd"
"go.uber.org/multierr"
)

var (
blockPrefix = [1]byte{1}
indexPrefix = [1]byte{2}
)

type DefaultBlockStore struct {
db KVStore

height uint64

// mtx protects height
mtx sync.RWMutex
}

var _ BlockStore = &DefaultBlockStore{}

func NewBlockStore() BlockStore {
return &DefaultBlockStore{db: NewInMemoryKVStore()}
}

func (bs *DefaultBlockStore) Height() uint64 {
bs.mtx.RLock()
defer bs.mtx.RUnlock()
return bs.height
}

func (bs *DefaultBlockStore) SaveBlock(block *types.Block) error {
// TODO(tzdybal): proper serialization & hashing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make it proper already?
If we building things from scratch here, sticking with easy and performant solutions like sha256-simd from the beginning makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Goal of this PR is to introduce block store that works. Using sha256-simd actually makes sense as it's drop-in replacement. By this comment I mostly mean that the serialization and hashing code will definitely be modified in the future.

hash, err := getHash(block)
if err != nil {
return err
}
key := append(blockPrefix[:], hash[:]...)

height := make([]byte, 8)
binary.LittleEndian.PutUint64(height, block.Header.Height)
ikey := append(indexPrefix[:], height[:]...)

var value bytes.Buffer
enc := gob.NewEncoder(&value)
err = enc.Encode(block)
if err != nil {
return err
}

bs.mtx.Lock()
defer bs.mtx.Unlock()
// TODO(tzdybal): use transaction for consistency of DB
err = multierr.Append(err, bs.db.Set(key, value.Bytes()))
err = multierr.Append(err, bs.db.Set(ikey, hash[:]))

if block.Header.Height > bs.height {
bs.height = block.Header.Height
}

return err
}

// TODO(tzdybal): what is more common access pattern? by height or by hash?
// currently, we're indexing height->hash, and store blocks by hash, but we might as well store by height
// and index hash->height
Comment on lines +72 to +74
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need both. In optimint you can dictate the access pattern, on the app-side (e.g. via RPC), you can't really know (but you could look into some typical cosmos "apps").

func (bs *DefaultBlockStore) LoadBlock(height uint64) (*types.Block, error) {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, height)
ikey := append(indexPrefix[:], buf[:]...)

hash, err := bs.db.Get(ikey)

if err != nil {
return nil, err
}

// TODO(tzdybal): any better way to convert slice to array?
var h [32]byte
copy(h[:], hash)
Comment on lines +86 to +88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is the most common way. You could add a helper method (or methods) if you have to convert back and forth.

return bs.LoadBlockByHash(h)
}

func (bs *DefaultBlockStore) LoadBlockByHash(hash [32]byte) (*types.Block, error) {
key := append(blockPrefix[:], hash[:]...)

blockData, err := bs.db.Get(key)

if err != nil {
return nil, err
}

dec := gob.NewDecoder(bytes.NewReader(blockData))
var block types.Block
err = dec.Decode(&block)
if err != nil {
return nil, err
}

return &block, nil
}

// TODO(tzdybal): replace with proper hashing mechanism
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to decide on all cryptographic primitives in ADR I think.

func getHash(block *types.Block) ([32]byte, error) {
var header bytes.Buffer
enc := gob.NewEncoder(&header)
err := enc.Encode(block.Header)
if err != nil {
return [32]byte{}, err
}

return sha256.Sum256(header.Bytes()), nil
}
Loading