Skip to content

Commit

Permalink
Block RPC (dymensionxyz#187)
Browse files Browse the repository at this point in the history
* Block RPC implementation

Co-authored-by: Ismail Khoffi <[email protected]>
  • Loading branch information
tzdybal and liamsi authored Nov 22, 2021
1 parent cf13d7d commit f64d8e4
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 32 deletions.
28 changes: 22 additions & 6 deletions block/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/libp2p/go-libp2p-core/crypto"
abci "github.com/tendermint/tendermint/abci/types"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/proxy"
tmtypes "github.com/tendermint/tendermint/types"
Expand Down Expand Up @@ -71,7 +72,7 @@ func NewManager(
return nil, err
}

proposerAddress, err := proposerKey.GetPublic().Raw()
proposerAddress, err := getAddress(proposerKey)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -109,13 +110,21 @@ func NewManager(
return agg, nil
}

func getAddress(key crypto.PrivKey) ([]byte, error) {
rawKey, err := key.GetPublic().Raw()
if err != nil {
return nil, err
}
return tmcrypto.AddressHash(rawKey), nil
}

func (m *Manager) SetDALC(dalc da.DataAvailabilityLayerClient) {
m.dalc = dalc
m.retriever = dalc.(da.BlockRetriever)
}

func (m *Manager) AggregationLoop(ctx context.Context) {
timer := time.NewTimer(m.conf.BlockTime)
timer := time.NewTimer(0)
for {
select {
case <-ctx.Done():
Expand Down Expand Up @@ -237,22 +246,29 @@ func (m *Manager) getRemainingSleep(start time.Time) time.Duration {

func (m *Manager) publishBlock(ctx context.Context) error {
var lastCommit *types.Commit
var lastHeaderHash [32]byte
var err error
newHeight := m.store.Height() + 1
height := m.store.Height()
newHeight := height + 1

// this is a special case, when first block is produced - there is no previous commit
if newHeight == uint64(m.genesis.InitialHeight) {
lastCommit = &types.Commit{Height: m.store.Height(), HeaderHash: [32]byte{}}
lastCommit = &types.Commit{Height: height, HeaderHash: [32]byte{}}
} else {
lastCommit, err = m.store.LoadCommit(m.store.Height())
lastCommit, err = m.store.LoadCommit(height)
if err != nil {
return fmt.Errorf("error while loading last commit: %w", err)
}
lastBlock, err := m.store.LoadBlock(height)
if err != nil {
return fmt.Errorf("error while loading last block: %w", err)
}
lastHeaderHash = lastBlock.Header.Hash()
}

m.logger.Info("Creating and publishing block", "height", newHeight)

block := m.executor.CreateBlock(newHeight, lastCommit, m.lastState)
block := m.executor.CreateBlock(newHeight, lastCommit, lastHeaderHash, m.lastState)
m.logger.Debug("block info", "num_tx", len(block.Data.Txs))
newState, _, err := m.executor.ApplyBlock(ctx, m.lastState, block)
if err != nil {
Expand Down
88 changes: 85 additions & 3 deletions conv/abci/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import (

tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/celestiaorg/optimint/types"
)

// ToABCIHeader converts Optimint header to Header format defined in ABCI.
// ToABCIHeaderPB converts Optimint header to Header format defined in ABCI.
// Caller should fill all the fields that are not available in Optimint header (like ChainID).
func ToABCIHeader(header *types.Header) (tmproto.Header, error) {
func ToABCIHeaderPB(header *types.Header) (tmproto.Header, error) {
hash := header.Hash()
return tmproto.Header{
Version: tmversion.Consensus{
Expand All @@ -34,7 +35,88 @@ func ToABCIHeader(header *types.Header) (tmproto.Header, error) {
ConsensusHash: header.ConsensusHash[:],
AppHash: header.AppHash[:],
LastResultsHash: header.LastResultsHash[:],
EvidenceHash: nil,
EvidenceHash: tmtypes.EvidenceList{}.Hash(),
ProposerAddress: header.ProposerAddress,
}, nil
}

// ToABCIHeader converts Optimint header to Header format defined in ABCI.
// Caller should fill all the fields that are not available in Optimint header (like ChainID).
func ToABCIHeader(header *types.Header) (tmtypes.Header, error) {
hash := header.Hash()
return tmtypes.Header{
Version: tmversion.Consensus{
Block: header.Version.Block,
App: header.Version.App,
},
Height: int64(header.Height),
Time: time.Unix(int64(header.Time), 0),
LastBlockID: tmtypes.BlockID{
Hash: hash[:],
PartSetHeader: tmtypes.PartSetHeader{
Total: 0,
Hash: nil,
},
},
LastCommitHash: header.LastCommitHash[:],
DataHash: header.DataHash[:],
ValidatorsHash: nil,
NextValidatorsHash: nil,
ConsensusHash: header.ConsensusHash[:],
AppHash: header.AppHash[:],
LastResultsHash: header.LastResultsHash[:],
EvidenceHash: tmtypes.EvidenceList{}.Hash(),
ProposerAddress: header.ProposerAddress,
}, nil
}

// ToABCIBlock converts Optimint block into block format defined by ABCI.
// Returned block should pass `ValidateBasic`.
func ToABCIBlock(block *types.Block) (*tmtypes.Block, error) {
abciHeader, err := ToABCIHeader(&block.Header)
if err != nil {
return nil, err
}
abciCommit := ToABCICommit(&block.LastCommit)
// This assumes that we have only one signature
if len(abciCommit.Signatures) == 1 {
abciCommit.Signatures[0].ValidatorAddress = block.Header.ProposerAddress
}
abciBlock := tmtypes.Block{
Header: abciHeader,
Evidence: tmtypes.EvidenceData{
Evidence: nil,
},
LastCommit: abciCommit,
}
abciBlock.Data.Txs = make([]tmtypes.Tx, len(block.Data.Txs))
for i := range block.Data.Txs {
abciBlock.Data.Txs[i] = tmtypes.Tx(block.Data.Txs[i])
}
abciBlock.Header.DataHash = abciBlock.Data.Txs.Hash()

return &abciBlock, nil
}

// ToABCICommit converts Optimint commit into commit format defined by ABCI.
// This function only converts fields that are available in Optimint commit.
// Other fields (especially ValidatorAddress and Timestamp of Signature) has to be filled by caller.
func ToABCICommit(commit *types.Commit) *tmtypes.Commit {
tmCommit := tmtypes.Commit{
Height: int64(commit.Height),
Round: 0,
BlockID: tmtypes.BlockID{
Hash: commit.HeaderHash[:],
PartSetHeader: tmtypes.PartSetHeader{},
},
}
for _, sig := range commit.Signatures {
commitSig := tmtypes.CommitSig{
BlockIDFlag: tmtypes.BlockIDFlagCommit,
Signature: sig,
}
tmCommit.Signatures = append(tmCommit.Signatures, commitSig)
}

return &tmCommit
}
28 changes: 27 additions & 1 deletion rpc/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/types"

abciconv "github.com/celestiaorg/optimint/conv/abci"
"github.com/celestiaorg/optimint/mempool"
"github.com/celestiaorg/optimint/node"
)
Expand Down Expand Up @@ -308,7 +309,32 @@ func (c *Client) Health(ctx context.Context) (*ctypes.ResultHealth, error) {

func (c *Client) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) {
// needs block store
panic("Block - not implemented!")
var h uint64
if height == nil {
h = c.node.Store.Height()
} else {
h = uint64(*height)
}

block, err := c.node.Store.LoadBlock(h)
if err != nil {
return nil, err
}
hash := block.Hash()
abciBlock, err := abciconv.ToABCIBlock(block)
if err != nil {
return nil, err
}
return &ctypes.ResultBlock{
BlockID: types.BlockID{
Hash: hash[:],
PartSetHeader: types.PartSetHeader{
Total: 0,
Hash: nil,
},
},
Block: abciBlock,
}, nil
}

func (c *Client) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) {
Expand Down
96 changes: 87 additions & 9 deletions rpc/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package client

import (
"context"
"crypto/rand"
crand "crypto/rand"
"math/rand"
"testing"
"time"

Expand All @@ -15,12 +16,15 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/types"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/celestiaorg/optimint/config"
"github.com/celestiaorg/optimint/mocks"
"github.com/celestiaorg/optimint/node"
"github.com/celestiaorg/optimint/state"
"github.com/celestiaorg/optimint/types"
)

var expectedInfo = abci.ResponseInfo{
Expand Down Expand Up @@ -164,7 +168,7 @@ func TestBroadcastTxCommit(t *testing.T) {

go func() {
time.Sleep(mockTxProcessingTime)
err := rpc.node.EventBus().PublishEventTx(types.EventDataTx{TxResult: abci.TxResult{
err := rpc.node.EventBus().PublishEventTx(tmtypes.EventDataTx{TxResult: abci.TxResult{
Height: 1,
Index: 0,
Tx: expectedTx,
Expand All @@ -184,13 +188,87 @@ func TestBroadcastTxCommit(t *testing.T) {
require.NoError(err)
}

func TestGetBlock(t *testing.T) {
assert := assert.New(t)
require := require.New(t)

mockApp, rpc := getRPC(t)
mockApp.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{})
mockApp.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{})
mockApp.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{})
mockApp.On("Commit", mock.Anything).Return(abci.ResponseCommit{})

err := rpc.node.Start()
require.NoError(err)

err = rpc.node.Store.SaveBlock(getRandomBlock(1, 10), &types.Commit{})
require.NoError(err)

blockResp, err := rpc.Block(context.Background(), nil)
require.NoError(err)
require.NotNil(blockResp)

assert.NotNil(blockResp.Block)

err = rpc.node.Stop()
require.NoError(err)
}

// copy-pasted from store/store_test.go
func getRandomBlock(height uint64, nTxs int) *types.Block {
block := &types.Block{
Header: types.Header{
Height: height,
Version: types.Version{Block: state.InitStateVersion.Consensus.Block},
ProposerAddress: getRandomBytes(20),
},
Data: types.Data{
Txs: make(types.Txs, nTxs),
IntermediateStateRoots: types.IntermediateStateRoots{
RawRootsList: make([][]byte, nTxs),
},
},
}
copy(block.Header.AppHash[:], getRandomBytes(32))

for i := 0; i < nTxs; i++ {
block.Data.Txs[i] = getRandomTx()
block.Data.IntermediateStateRoots.RawRootsList[i] = getRandomBytes(32)
}

// TODO(tzdybal): see https://github.com/celestiaorg/optimint/issues/143
if nTxs == 0 {
block.Data.Txs = nil
block.Data.IntermediateStateRoots.RawRootsList = nil
}

tmprotoLC, err := tmtypes.CommitFromProto(&tmproto.Commit{})
if err != nil {
return nil
}
copy(block.Header.LastCommitHash[:], tmprotoLC.Hash())

return block
}

func getRandomTx() types.Tx {
size := rand.Int()%100 + 100
return types.Tx(getRandomBytes(size))
}

func getRandomBytes(n int) []byte {
data := make([]byte, n)
_, _ = rand.Read(data)
return data
}

func getRPC(t *testing.T) (*mocks.Application, *Client) {
t.Helper()
require := require.New(t)
app := &mocks.Application{}
app.On("InitChain", mock.Anything).Return(abci.ResponseInitChain{})
key, _, _ := crypto.GenerateEd25519Key(rand.Reader)
node, err := node.NewNode(context.Background(), config.NodeConfig{DALayer: "mock"}, key, proxy.NewLocalClientCreator(app), &types.GenesisDoc{ChainID: "test"}, log.TestingLogger())
key, _, _ := crypto.GenerateEd25519Key(crand.Reader)
node, err := node.NewNode(context.Background(), config.NodeConfig{DALayer: "mock"}, key, proxy.NewLocalClientCreator(app), &tmtypes.GenesisDoc{ChainID: "test"}, log.TestingLogger())
require.NoError(err)
require.NotNil(node)

Expand All @@ -208,8 +286,8 @@ func TestMempool2Nodes(t *testing.T) {
app.On("InitChain", mock.Anything).Return(abci.ResponseInitChain{})
app.On("CheckTx", abci.RequestCheckTx{Tx: []byte("bad")}).Return(abci.ResponseCheckTx{Code: 1})
app.On("CheckTx", abci.RequestCheckTx{Tx: []byte("good")}).Return(abci.ResponseCheckTx{Code: 0})
key1, _, _ := crypto.GenerateEd25519Key(rand.Reader)
key2, _, _ := crypto.GenerateEd25519Key(rand.Reader)
key1, _, _ := crypto.GenerateEd25519Key(crand.Reader)
key2, _, _ := crypto.GenerateEd25519Key(crand.Reader)

id1, err := peer.IDFromPrivateKey(key1)
require.NoError(err)
Expand All @@ -219,7 +297,7 @@ func TestMempool2Nodes(t *testing.T) {
P2P: config.P2PConfig{
ListenAddress: "/ip4/127.0.0.1/tcp/9001",
},
}, key1, proxy.NewLocalClientCreator(app), &types.GenesisDoc{ChainID: "test"}, log.TestingLogger())
}, key1, proxy.NewLocalClientCreator(app), &tmtypes.GenesisDoc{ChainID: "test"}, log.TestingLogger())
require.NoError(err)
require.NotNil(node1)

Expand All @@ -229,7 +307,7 @@ func TestMempool2Nodes(t *testing.T) {
ListenAddress: "/ip4/127.0.0.1/tcp/9002",
Seeds: "/ip4/127.0.0.1/tcp/9001/p2p/" + id1.Pretty(),
},
}, key2, proxy.NewLocalClientCreator(app), &types.GenesisDoc{ChainID: "test"}, log.TestingLogger())
}, key2, proxy.NewLocalClientCreator(app), &tmtypes.GenesisDoc{ChainID: "test"}, log.TestingLogger())
require.NoError(err)
require.NotNil(node1)

Expand Down
Loading

0 comments on commit f64d8e4

Please sign in to comment.