From e6b5c2368ff7c3e3af807d48d17e8a9f2bf5fd01 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 10 May 2023 10:05:47 +0200 Subject: [PATCH 01/26] Add functionality to automatically run blocks --- cometmock/abci_client/client.go | 11 +++++++++++ cometmock/main.go | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index ea6414c..cc9ff15 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -3,6 +3,7 @@ package abci_client import ( "fmt" "reflect" + "sync" "time" abciclient "github.com/cometbft/cometbft/abci/client" @@ -19,6 +20,9 @@ import ( var GlobalClient *AbciClient +// store a mutex that allows only running one block at a time +var blockMutex = sync.Mutex{} + // AbciClient facilitates calls to the ABCI interface of multiple nodes. // It also tracks the current state and a common logger. type AbciClient struct { @@ -256,7 +260,11 @@ func (a *AbciClient) SendAbciQuery(data []byte, path string, height int64, prove // RunBlock runs a block with a specified transaction through the ABCI application. // It calls BeginBlock, DeliverTx, EndBlock, Commit and then // updates the state. +// RunBlock is safe for use by multiple goroutines simultaneously. func (a *AbciClient) RunBlock(tx *[]byte) (*abcitypes.ResponseBeginBlock, *abcitypes.ResponseDeliverTx, *abcitypes.ResponseEndBlock, *abcitypes.ResponseCommit, error) { + // lock mutex to avoid running two blocks at the same time + blockMutex.Lock() + a.Logger.Info("Running block") resBeginBlock, err := a.SendBeginBlock() @@ -290,6 +298,9 @@ func (a *AbciClient) RunBlock(tx *[]byte) (*abcitypes.ResponseBeginBlock, *abcit return nil, nil, nil, nil, err } + // unlock mutex + blockMutex.Unlock() + return resBeginBlock, resDeliverTx, resEndBlock, resCommit, nil } diff --git a/cometmock/main.go b/cometmock/main.go index 314c86f..ca1bb2c 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -3,6 +3,7 @@ package main import ( "os" "strings" + "time" comet_abciclient "github.com/cometbft/cometbft/abci/client" cometlog "github.com/cometbft/cometbft/libs/log" @@ -61,5 +62,11 @@ func main() { // run an empty block abci_client.GlobalClient.RunBlock(nil) - rpc_server.StartRPCServerWithDefaultConfig(cometMockListenAddress, logger) + go rpc_server.StartRPCServerWithDefaultConfig(cometMockListenAddress, logger) + + // produce a block every 5 seconds + for { + abci_client.GlobalClient.RunBlock(nil) + time.Sleep(5 * time.Second) + } } From 54d42cf359c8aa126e3ada49a0e5bbe5c41f8a62 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 10 May 2023 10:08:04 +0200 Subject: [PATCH 02/26] Decrease block time --- cometmock/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cometmock/main.go b/cometmock/main.go index ca1bb2c..7022196 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -64,9 +64,9 @@ func main() { go rpc_server.StartRPCServerWithDefaultConfig(cometMockListenAddress, logger) - // produce a block every 5 seconds + // produce a block every second for { abci_client.GlobalClient.RunBlock(nil) - time.Sleep(5 * time.Second) + time.Sleep(1 * time.Second) } } From 6d7add1503093efb9f74629a346518095c436a7c Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 10 May 2023 11:41:53 +0200 Subject: [PATCH 03/26] Add logging and make block show the block height --- cometmock/abci_client/client.go | 1 + cometmock/rpc_server/routes.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index cc9ff15..01ebd62 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -266,6 +266,7 @@ func (a *AbciClient) RunBlock(tx *[]byte) (*abcitypes.ResponseBeginBlock, *abcit blockMutex.Lock() a.Logger.Info("Running block") + a.Logger.Info("State at start of block", "state", a.CurState) resBeginBlock, err := a.SendBeginBlock() if err != nil { diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index c24afa1..872dd35 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -187,7 +187,7 @@ func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) blockID := abci_client.GlobalClient.CurState.LastBlockID // TODO: return an actual block if it is needed, for now return en empty block - block := &types.Block{} + block := &types.Block{Header: types.Header{Height: abci_client.GlobalClient.CurState.LastBlockHeight}} return &ctypes.ResultBlock{BlockID: blockID, Block: block}, nil } From 060ac3db6bd49a76a68d5ee6779dd52e8806e7c7 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 10 May 2023 17:24:45 +0200 Subject: [PATCH 04/26] Fix typo Commut to Commit --- cometmock/abci_client/client.go | 12 +++++++++--- cometmock/rpc_server/routes.go | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 01ebd62..53befe0 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -39,7 +39,7 @@ type AbciClient struct { func (a *AbciClient) SendBeginBlock() (*abcitypes.ResponseBeginBlock, error) { a.Logger.Info("Sending BeginBlock to clients") // build the BeginBlock request - beginBlockRequest := CreateBeginBlockRequest(a.CurState, time.Now(), *a.CurState.LastValidators.Proposer) + beginBlockRequest := CreateBeginBlockRequest(a.CurState, time.Now(), a.CurState.LastValidators.Proposer) // send BeginBlock to all clients and collect the responses responses := []*abcitypes.ResponseBeginBlock{} @@ -63,7 +63,13 @@ func (a *AbciClient) SendBeginBlock() (*abcitypes.ResponseBeginBlock, error) { return responses[0], nil } -func CreateBeginBlockRequest(curState state.State, curTime time.Time, proposer types.Validator) *abcitypes.RequestBeginBlock { +func CreateBeginBlockRequest(curState state.State, curTime time.Time, proposer *types.Validator) *abcitypes.RequestBeginBlock { + // special behaviour when proposer is nil + var proposerAddress types.Address + if proposer != nil { + proposerAddress = proposer.Address + } + return &abcitypes.RequestBeginBlock{ LastCommitInfo: abcitypes.CommitInfo{ Round: 0, @@ -76,7 +82,7 @@ func CreateBeginBlockRequest(curState state.State, curTime time.Time, proposer t Time: curTime, LastBlockId: curState.LastBlockID.ToProto(), LastCommitHash: curState.LastResultsHash, - ProposerAddress: proposer.Address, + ProposerAddress: proposerAddress, }, } } diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 872dd35..111a5b2 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -37,7 +37,7 @@ var Routes = map[string]*rpc.RPCFunc{ // then return. func BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { abci_client.GlobalClient.Logger.Info( - "BroadcastTxCommut called", "tx", tx) + "BroadcastTxCommit called", "tx", tx) return BroadcastTx(&tx) } From f6b9c85f0d8935fd0f7142895a216d3cf2b45027 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 11 May 2023 09:45:29 +0200 Subject: [PATCH 05/26] Remove unused func and add handler wrapper --- cometmock/rpc_server/rpc_server.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/cometmock/rpc_server/rpc_server.go b/cometmock/rpc_server/rpc_server.go index c161706..286aa3f 100644 --- a/cometmock/rpc_server/rpc_server.go +++ b/cometmock/rpc_server/rpc_server.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "net" "net/http" "os" "runtime/debug" @@ -37,7 +36,7 @@ func StartRPCServer(listenAddr string, logger log.Logger, config *rpcserver.Conf var rootHandler http.Handler = mux if err := rpcserver.Serve( listener, - rootHandler, + RecoverAndLogHandler(rootHandler, rpcLogger), rpcLogger, config, ); err != nil { @@ -50,20 +49,6 @@ func StartRPCServerWithDefaultConfig(listenAddr string, logger log.Logger) { StartRPCServer(listenAddr, logger, rpcserver.DefaultConfig()) } -func ServeRPC(listener net.Listener, handler http.Handler, logger log.Logger, config *rpcserver.Config) error { - logger.Info("serve", "msg", log.NewLazySprintf("Starting RPC HTTP server on %s", listener.Addr())) - s := &http.Server{ - Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger), - ReadTimeout: config.ReadTimeout, - ReadHeaderTimeout: config.ReadTimeout, - WriteTimeout: config.WriteTimeout, - MaxHeaderBytes: config.MaxHeaderBytes, - } - err := s.Serve(listener) - logger.Info("RPC HTTP server stopped", "err", err) - return err -} - // RecoverAndLogHandler wraps an HTTP handler, adding error logging. // If the inner function panics, the outer function recovers, logs, sends an // HTTP 500 error response. @@ -132,6 +117,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler "status", rww.Status, "duration", durationMS, "remoteAddr", r.RemoteAddr, + "requestURI", r.RequestURI, ) }() From b1453c7dd05a184087fc8244fa3981891a434ae1 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 11 May 2023 09:49:28 +0200 Subject: [PATCH 06/26] Remove wrapped handler --- cometmock/rpc_server/rpc_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cometmock/rpc_server/rpc_server.go b/cometmock/rpc_server/rpc_server.go index 286aa3f..fada2ed 100644 --- a/cometmock/rpc_server/rpc_server.go +++ b/cometmock/rpc_server/rpc_server.go @@ -36,7 +36,7 @@ func StartRPCServer(listenAddr string, logger log.Logger, config *rpcserver.Conf var rootHandler http.Handler = mux if err := rpcserver.Serve( listener, - RecoverAndLogHandler(rootHandler, rpcLogger), + rootHandler, rpcLogger, config, ); err != nil { From ed158137dc19c3b04eca293f78cfd105464533e8 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 11 May 2023 10:12:55 +0200 Subject: [PATCH 07/26] Add logging for request body to write jsonrpc method --- cometmock/rpc_server/rpc_server.go | 89 ++++++------------------------ 1 file changed, 17 insertions(+), 72 deletions(-) diff --git a/cometmock/rpc_server/rpc_server.go b/cometmock/rpc_server/rpc_server.go index fada2ed..a8c5114 100644 --- a/cometmock/rpc_server/rpc_server.go +++ b/cometmock/rpc_server/rpc_server.go @@ -1,13 +1,12 @@ package rpc_server import ( + "bytes" "encoding/json" - "errors" "fmt" + "io" + "io/ioutil" "net/http" - "os" - "runtime/debug" - "time" "github.com/cometbft/cometbft/libs/log" rpcserver "github.com/cometbft/cometbft/rpc/jsonrpc/server" @@ -36,7 +35,7 @@ func StartRPCServer(listenAddr string, logger log.Logger, config *rpcserver.Conf var rootHandler http.Handler = mux if err := rpcserver.Serve( listener, - rootHandler, + ExtraLogHandler(rootHandler, rpcLogger), rpcLogger, config, ); err != nil { @@ -52,76 +51,22 @@ func StartRPCServerWithDefaultConfig(listenAddr string, logger log.Logger) { // RecoverAndLogHandler wraps an HTTP handler, adding error logging. // If the inner function panics, the outer function recovers, logs, sends an // HTTP 500 error response. -func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler { +func ExtraLogHandler(handler http.Handler, logger log.Logger) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Wrap the ResponseWriter to remember the status - rww := &responseWriterWrapper{-1, w} - begin := time.Now() - - rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix())) - - defer func() { - // Handle any panics in the panic handler below. Does not use the logger, since we want - // to avoid any further panics. However, we try to return a 500, since it otherwise - // defaults to 200 and there is no other way to terminate the connection. If that - // should panic for whatever reason then the Go HTTP server will handle it and - // terminate the connection - panicing is the de-facto and only way to get the Go HTTP - // server to terminate the request and close the connection/stream: - // https://github.com/golang/go/issues/17790#issuecomment-258481416 - if e := recover(); e != nil { - fmt.Fprintf(os.Stderr, "Panic during RPC panic recovery: %v\n%v\n", e, string(debug.Stack())) - w.WriteHeader(500) - } - }() - - defer func() { - // Send a 500 error if a panic happens during a handler. - // Without this, Chrome & Firefox were retrying aborted ajax requests, - // at least to my localhost. - if e := recover(); e != nil { - // If RPCResponse - if res, ok := e.(types.RPCResponse); ok { - if wErr := WriteRPCResponseHTTP(rww, res); wErr != nil { - logger.Error("failed to write response", "res", res, "err", wErr) - } - } else { - // Panics can contain anything, attempt to normalize it as an error. - var err error - switch e := e.(type) { - case error: - err = e - case string: - err = errors.New(e) - case fmt.Stringer: - err = errors.New(e.String()) - default: - } - - logger.Error("panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack())) - - res := types.RPCInternalError(types.JSONRPCIntID(-1), err) - if wErr := WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, res); wErr != nil { - logger.Error("failed to write response", "res", res, "err", wErr) - } - } + if r.Body != nil { + body, err := io.ReadAll(r.Body) + if err != nil { + logger.Error("failed to read request body", "err", err) + } else { + logger.Debug("served RPC HTTP request", + "body", string(body), + ) } - // Finally, log. - durationMS := time.Since(begin).Nanoseconds() / 1000000 - if rww.Status == -1 { - rww.Status = 200 - } - logger.Debug("served RPC HTTP response", - "method", r.Method, - "url", r.URL, - "status", rww.Status, - "duration", durationMS, - "remoteAddr", r.RemoteAddr, - "requestURI", r.RequestURI, - ) - }() - - handler.ServeHTTP(rww, r) + r.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + } + + handler.ServeHTTP(w, r) }) } From fbd519e97925e9445b233d52694b0740ae2c4769 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 11 May 2023 11:00:47 +0200 Subject: [PATCH 08/26] Add status and health endpoints --- cometmock/rpc_server/routes.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 111a5b2..dcb0a5a 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -5,6 +5,7 @@ import ( "github.com/cometbft/cometbft/libs/bytes" cmtmath "github.com/cometbft/cometbft/libs/math" + cometp2p "github.com/cometbft/cometbft/p2p" ctypes "github.com/cometbft/cometbft/rpc/core/types" rpc "github.com/cometbft/cometbft/rpc/jsonrpc/server" rpctypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" @@ -19,6 +20,8 @@ const ( var Routes = map[string]*rpc.RPCFunc{ // info API + "health": rpc.NewRPCFunc(Health, ""), + "status": rpc.NewRPCFunc(Status, ""), "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), "block": rpc.NewRPCFunc(Block, "height", rpc.Cacheable("height")), @@ -31,6 +34,28 @@ var Routes = map[string]*rpc.RPCFunc{ "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"), } +// Status returns CometBFT status including node info, pubkey, latest block +// hash, app hash, block height and time. +// More: https://docs.cometbft.com/v0.37/rpc/#/Info/status +func Status(ctx *rpctypes.Context) (*ctypes.ResultStatus, error) { + nodeInfo := cometp2p.DefaultNodeInfo{} + syncInfo := ctypes.SyncInfo{} + validatorInfo := ctypes.ValidatorInfo{} + result := &ctypes.ResultStatus{ + NodeInfo: nodeInfo, + SyncInfo: syncInfo, + ValidatorInfo: validatorInfo, + } + + return result, nil +} + +// Health gets node health. Returns empty result (200 OK) on success, no +// response - in case of an error. +func Health(ctx *rpctypes.Context) (*ctypes.ResultHealth, error) { + return &ctypes.ResultHealth{}, nil +} + // BroadcastTxCommit broadcasts a transaction, // and wait until it is included in a block and and comitted. // In our case, this means running a block with just the the transition, From f9b93db687ab76962095da3f5e4d1bef1ee58149 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 12 May 2023 13:13:00 +0200 Subject: [PATCH 09/26] Utilize MakeBlock to handle much more realistic blocks --- cometmock/abci_client/client.go | 176 +++++++++++++++++++----------- cometmock/main.go | 21 +++- cometmock/rpc_server/routes.go | 66 +++++++++-- cometmock/rpc_server/websocket.go | 125 +++++++++++++++++++++ 4 files changed, 318 insertions(+), 70 deletions(-) create mode 100644 cometmock/rpc_server/websocket.go diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 53befe0..a7c34e2 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -10,10 +10,8 @@ import ( abcitypes "github.com/cometbft/cometbft/abci/types" cryptoenc "github.com/cometbft/cometbft/crypto/encoding" "github.com/cometbft/cometbft/crypto/merkle" - "github.com/cometbft/cometbft/libs/bytes" cometlog "github.com/cometbft/cometbft/libs/log" cmtstate "github.com/cometbft/cometbft/proto/tendermint/state" - ttypes "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/types" ) @@ -29,6 +27,7 @@ type AbciClient struct { Clients []abciclient.Client Logger cometlog.Logger CurState state.State + EventBus types.EventBus // if this is true, then an error will be returned if the responses from the clients are not all equal. // can be used to check for nondeterminism in apps, but also slows down execution a bit, @@ -36,10 +35,10 @@ type AbciClient struct { ErrorOnUnequalResponses bool } -func (a *AbciClient) SendBeginBlock() (*abcitypes.ResponseBeginBlock, error) { +func (a *AbciClient) SendBeginBlock(block *types.Block) (*abcitypes.ResponseBeginBlock, error) { a.Logger.Info("Sending BeginBlock to clients") // build the BeginBlock request - beginBlockRequest := CreateBeginBlockRequest(a.CurState, time.Now(), a.CurState.LastValidators.Proposer) + beginBlockRequest := CreateBeginBlockRequest(&block.Header, block.LastCommit) // send BeginBlock to all clients and collect the responses responses := []*abcitypes.ResponseBeginBlock{} @@ -63,27 +62,11 @@ func (a *AbciClient) SendBeginBlock() (*abcitypes.ResponseBeginBlock, error) { return responses[0], nil } -func CreateBeginBlockRequest(curState state.State, curTime time.Time, proposer *types.Validator) *abcitypes.RequestBeginBlock { - // special behaviour when proposer is nil - var proposerAddress types.Address - if proposer != nil { - proposerAddress = proposer.Address - } - +func CreateBeginBlockRequest(header *types.Header, lastCommit *types.Commit) *abcitypes.RequestBeginBlock { return &abcitypes.RequestBeginBlock{ - LastCommitInfo: abcitypes.CommitInfo{ - Round: 0, - Votes: []abcitypes.VoteInfo{}, - }, - Header: ttypes.Header{ - ChainID: curState.ChainID, - Version: curState.Version.Consensus, - Height: curState.LastBlockHeight + 1, - Time: curTime, - LastBlockId: curState.LastBlockID.ToProto(), - LastCommitHash: curState.LastResultsHash, - ProposerAddress: proposerAddress, - }, + // TODO: fill in Votes + LastCommitInfo: abcitypes.CommitInfo{Round: lastCommit.Round, Votes: []abcitypes.VoteInfo{}}, + Header: *header.ToProto(), } } @@ -263,18 +246,51 @@ func (a *AbciClient) SendAbciQuery(data []byte, path string, height int64, prove return client.QuerySync(request) } +func GetBlockIdFromBlock(block *types.Block) (*types.BlockID, error) { + partSet, error := block.MakePartSet(2) + if error != nil { + return nil, error + } + + partSetHeader := partSet.Header() + blockID := types.BlockID{ + Hash: block.Hash(), + PartSetHeader: partSetHeader, + } + return &blockID, nil +} + // RunBlock runs a block with a specified transaction through the ABCI application. // It calls BeginBlock, DeliverTx, EndBlock, Commit and then // updates the state. // RunBlock is safe for use by multiple goroutines simultaneously. -func (a *AbciClient) RunBlock(tx *[]byte) (*abcitypes.ResponseBeginBlock, *abcitypes.ResponseDeliverTx, *abcitypes.ResponseEndBlock, *abcitypes.ResponseCommit, error) { +func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.Validator) (*abcitypes.ResponseBeginBlock, *abcitypes.ResponseDeliverTx, *abcitypes.ResponseEndBlock, *abcitypes.ResponseCommit, error) { // lock mutex to avoid running two blocks at the same time blockMutex.Lock() a.Logger.Info("Running block") a.Logger.Info("State at start of block", "state", a.CurState) - resBeginBlock, err := a.SendBeginBlock() + txs := make([]types.Tx, 0) + if tx != nil { + txs = append(txs, *tx) + } + + // TODO: handle special case where proposer is nil + var proposerAddress types.Address + if proposer != nil { + proposerAddress = proposer.Address + } + + block := a.CurState.MakeBlock(a.CurState.LastBlockHeight+1, txs, &types.Commit{}, []types.Evidence{}, proposerAddress) + // override the block time, since we do not actually get votes from peers to median the time out of + block.Time = blockTime + blockId, err := GetBlockIdFromBlock(block) + if err != nil { + return nil, nil, nil, nil, err + } + + resBeginBlock, err := a.SendBeginBlock(block) if err != nil { return nil, nil, nil, nil, err } @@ -300,7 +316,7 @@ func (a *AbciClient) RunBlock(tx *[]byte) (*abcitypes.ResponseBeginBlock, *abcit } // updates state as a side effect. returns an error if the state update fails - err = a.UpdateStateFromBlock(resBeginBlock, resEndBlock, resCommit) + err = a.UpdateStateFromBlock(blockId, block, resBeginBlock, resEndBlock, resCommit) if err != nil { return nil, nil, nil, nil, err } @@ -317,33 +333,13 @@ func (a *AbciClient) RunBlock(tx *[]byte) (*abcitypes.ResponseBeginBlock, *abcit // block results hash, validators, consensus // params, and app hash. func (a *AbciClient) UpdateStateFromBlock( + blockId *types.BlockID, + block *types.Block, beginBlockResponse *abcitypes.ResponseBeginBlock, endBlockResponse *abcitypes.ResponseEndBlock, commitResponse *abcitypes.ResponseCommit, ) error { // build components of the state update, then call the update function - - // TODO: if necessary, construct actual block id - blockID := types.BlockID{} - - // TODO: if necessary, refine header to be more true to CometBFT behaviour - header := types.Header{ - Version: a.CurState.Version.Consensus, - ChainID: a.CurState.ChainID, - Height: a.CurState.LastBlockHeight + 1, - Time: time.Now(), - LastBlockID: a.CurState.LastBlockID, - LastCommitHash: a.CurState.LastResultsHash, - DataHash: bytes.HexBytes{}, - ValidatorsHash: a.CurState.Validators.Hash(), - NextValidatorsHash: a.CurState.NextValidators.Hash(), - ConsensusHash: a.CurState.ConsensusParams.Hash(), - AppHash: a.CurState.AppHash, - LastResultsHash: commitResponse.Data, - EvidenceHash: bytes.HexBytes{}, - ProposerAddress: a.CurState.Validators.Proposer.Address, - } - abciResponses := cmtstate.ABCIResponses{ DeliverTxs: []*abcitypes.ResponseDeliverTx{}, EndBlock: endBlockResponse, @@ -363,9 +359,10 @@ func (a *AbciClient) UpdateStateFromBlock( newState, err := UpdateState( a.CurState, - blockID, - &header, + blockId, + &block.Header, &abciResponses, + commitResponse, validatorUpdates, ) if err != nil { @@ -373,6 +370,10 @@ func (a *AbciClient) UpdateStateFromBlock( } a.CurState = newState + + // Events are fired after everything else. + // NOTE: if we crash between Commit and Save, events wont be fired during replay + fireEvents(a.Logger, &a.EventBus, block, &abciResponses, validatorUpdates) return nil } @@ -380,9 +381,10 @@ func (a *AbciClient) UpdateStateFromBlock( // updateState returns a new State updated according to the header and responses. func UpdateState( curState state.State, - blockID types.BlockID, - header *types.Header, + blockId *types.BlockID, + blockHeader *types.Header, abciResponses *cmtstate.ABCIResponses, + commitResponse *abcitypes.ResponseCommit, validatorUpdates []*types.Validator, ) (state.State, error) { // Copy the valset so we can apply changes from EndBlock @@ -397,7 +399,7 @@ func UpdateState( return curState, fmt.Errorf("error changing validator set: %v", err) } // Change results from this height but only applies to the next next height. - lastHeightValsChanged = header.Height + 1 + 1 + lastHeightValsChanged = blockHeader.Height + 1 + 1 } // Update validator proposer priority and set state variables. @@ -417,20 +419,18 @@ func UpdateState( curState.Version.Consensus.App = nextParams.Version.App // Change results from this height but only applies to the next height. - lastHeightParamsChanged = header.Height + 1 + lastHeightParamsChanged = blockHeader.Height + 1 } nextVersion := curState.Version - // NOTE: the AppHash has not been populated. - // It will be filled on state.Save. return state.State{ Version: nextVersion, ChainID: curState.ChainID, InitialHeight: curState.InitialHeight, - LastBlockHeight: header.Height, - LastBlockID: blockID, - LastBlockTime: header.Time, + LastBlockHeight: blockHeader.Height, + LastBlockID: *blockId, + LastBlockTime: blockHeader.Time, NextValidators: nValSet, Validators: curState.NextValidators.Copy(), LastValidators: curState.Validators.Copy(), @@ -438,7 +438,7 @@ func UpdateState( ConsensusParams: nextParams, LastHeightConsensusParamsChanged: lastHeightParamsChanged, LastResultsHash: state.ABCIResponsesResultsHash(abciResponses), - AppHash: nil, + AppHash: commitResponse.Data, }, nil } @@ -469,3 +469,57 @@ func validateValidatorUpdates( } return nil } + +func fireEvents( + logger cometlog.Logger, + eventBus types.BlockEventPublisher, + block *types.Block, + abciResponses *cmtstate.ABCIResponses, + validatorUpdates []*types.Validator, +) { + if err := eventBus.PublishEventNewBlock(types.EventDataNewBlock{ + Block: block, + ResultBeginBlock: *abciResponses.BeginBlock, + ResultEndBlock: *abciResponses.EndBlock, + }); err != nil { + logger.Error("failed publishing new block", "err", err) + } + + if err := eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{ + Header: block.Header, + NumTxs: int64(len(block.Txs)), + ResultBeginBlock: *abciResponses.BeginBlock, + ResultEndBlock: *abciResponses.EndBlock, + }); err != nil { + logger.Error("failed publishing new block header", "err", err) + } + + if len(block.Evidence.Evidence) != 0 { + for _, ev := range block.Evidence.Evidence { + if err := eventBus.PublishEventNewEvidence(types.EventDataNewEvidence{ + Evidence: ev, + Height: block.Height, + }); err != nil { + logger.Error("failed publishing new evidence", "err", err) + } + } + } + + for i, tx := range block.Data.Txs { + if err := eventBus.PublishEventTx(types.EventDataTx{TxResult: abcitypes.TxResult{ + Height: block.Height, + Index: uint32(i), + Tx: tx, + Result: *(abciResponses.DeliverTxs[i]), + }}); err != nil { + logger.Error("failed publishing event TX", "err", err) + } + } + + if len(validatorUpdates) > 0 { + if err := eventBus.PublishEventValidatorSetUpdates( + types.EventDataValidatorSetUpdates{ValidatorUpdates: validatorUpdates}); err != nil { + logger.Error("failed publishing event", "err", err) + } + } +} diff --git a/cometmock/main.go b/cometmock/main.go index 7022196..d8d0142 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -8,10 +8,20 @@ import ( comet_abciclient "github.com/cometbft/cometbft/abci/client" cometlog "github.com/cometbft/cometbft/libs/log" "github.com/cometbft/cometbft/state" + "github.com/cometbft/cometbft/types" "github.com/p-offtermatt/CometMock/cometmock/abci_client" "github.com/p-offtermatt/CometMock/cometmock/rpc_server" ) +func CreateAndStartEventBus(logger cometlog.Logger) (*types.EventBus, error) { + eventBus := types.NewEventBus() + eventBus.SetLogger(logger.With("module", "events")) + if err := eventBus.Start(); err != nil { + return nil, err + } + return eventBus, nil +} + func main() { logger := cometlog.NewTMLogger(cometlog.NewSyncWriter(os.Stdout)) @@ -45,11 +55,18 @@ func main() { clients = append(clients, client) } + eventBus, err := CreateAndStartEventBus(logger) + if err != nil { + logger.Error(err.Error()) + panic(err) + } + abci_client.GlobalClient = &abci_client.AbciClient{ Clients: clients, Logger: logger, CurState: curState, ErrorOnUnequalResponses: true, + EventBus: *eventBus, } // initialize chain @@ -60,13 +77,13 @@ func main() { } // run an empty block - abci_client.GlobalClient.RunBlock(nil) + abci_client.GlobalClient.RunBlock(nil, time.Now(), abci_client.GlobalClient.CurState.LastValidators.Proposer) go rpc_server.StartRPCServerWithDefaultConfig(cometMockListenAddress, logger) // produce a block every second for { - abci_client.GlobalClient.RunBlock(nil) + abci_client.GlobalClient.RunBlock(nil, time.Now(), abci_client.GlobalClient.CurState.LastValidators.Proposer) time.Sleep(1 * time.Second) } } diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index dcb0a5a..adcbeaf 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -2,6 +2,7 @@ package rpc_server import ( "fmt" + "time" "github.com/cometbft/cometbft/libs/bytes" cmtmath "github.com/cometbft/cometbft/libs/math" @@ -19,11 +20,18 @@ const ( ) var Routes = map[string]*rpc.RPCFunc{ + // websocket + "subscribe": rpc.NewWSRPCFunc(Subscribe, "query"), + "unsubscribe": rpc.NewWSRPCFunc(Unsubscribe, "query"), + "unsubscribe_all": rpc.NewWSRPCFunc(UnsubscribeAll, ""), + // info API - "health": rpc.NewRPCFunc(Health, ""), - "status": rpc.NewRPCFunc(Status, ""), - "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), - "block": rpc.NewRPCFunc(Block, "height", rpc.Cacheable("height")), + "health": rpc.NewRPCFunc(Health, ""), + "status": rpc.NewRPCFunc(Status, ""), + "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), + "block": rpc.NewRPCFunc(Block, "height", rpc.Cacheable("height")), + "consensus_params": rpc.NewRPCFunc(ConsensusParams, "height", rpc.Cacheable("height")), + "header": rpc.NewRPCFunc(Header, "height", rpc.Cacheable("height")), // // tx broadcast API "broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"), @@ -34,13 +42,57 @@ var Routes = map[string]*rpc.RPCFunc{ "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"), } +// Header gets block header at a given height. +// If no height is provided, it will fetch the latest header. +// More: https://docs.cometbft.com/v0.37/rpc/#/Info/header +// TODO: currently unfilled +func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, error) { + // only the last height is available, since we do not keep past heights at the moment + if heightPtr != nil { + return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + } + + return &ctypes.ResultHeader{}, nil +} + +// ConsensusParams gets the consensus parameters at the given block height. +// If no height is provided, it will fetch the latest consensus params. +// More: https://docs.cometbft.com/v0.37/rpc/#/Info/consensus_params +func ConsensusParams(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultConsensusParams, error) { + // only the last height is available, since we do not keep past heights at the moment + if heightPtr != nil { + return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + } + + height := abci_client.GlobalClient.CurState.LastBlockHeight + consensusParams := abci_client.GlobalClient.CurState.ConsensusParams + + return &ctypes.ResultConsensusParams{ + BlockHeight: height, + ConsensusParams: consensusParams, + }, nil +} + // Status returns CometBFT status including node info, pubkey, latest block // hash, app hash, block height and time. // More: https://docs.cometbft.com/v0.37/rpc/#/Info/status func Status(ctx *rpctypes.Context) (*ctypes.ResultStatus, error) { - nodeInfo := cometp2p.DefaultNodeInfo{} + // return status as if we are the first validator + validator := abci_client.GlobalClient.CurState.Validators.Validators[0] + + nodeInfo := cometp2p.DefaultNodeInfo{ + DefaultNodeID: cometp2p.PubKeyToID(validator.PubKey), + Network: abci_client.GlobalClient.CurState.ChainID, + Other: cometp2p.DefaultNodeInfoOther{ + TxIndex: "on", + }, + } syncInfo := ctypes.SyncInfo{} - validatorInfo := ctypes.ValidatorInfo{} + validatorInfo := ctypes.ValidatorInfo{ + Address: validator.Address, + PubKey: validator.PubKey, + VotingPower: validator.VotingPower, + } result := &ctypes.ResultStatus{ NodeInfo: nodeInfo, SyncInfo: syncInfo, @@ -104,7 +156,7 @@ func BroadcastTx(tx *types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { byteTx := []byte(*tx) - _, _, _, _, err := abci_client.GlobalClient.RunBlock(&byteTx) + _, _, _, _, err := abci_client.GlobalClient.RunBlock(&byteTx, time.Now(), abci_client.GlobalClient.CurState.LastValidators.Proposer) if err != nil { return nil, err } diff --git a/cometmock/rpc_server/websocket.go b/cometmock/rpc_server/websocket.go new file mode 100644 index 0000000..6a7a0d0 --- /dev/null +++ b/cometmock/rpc_server/websocket.go @@ -0,0 +1,125 @@ +package rpc_server + +import ( + "context" + "errors" + "fmt" + "time" + + cmtpubsub "github.com/cometbft/cometbft/libs/pubsub" + cmtquery "github.com/cometbft/cometbft/libs/pubsub/query" + ctypes "github.com/cometbft/cometbft/rpc/core/types" + rpctypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" + "github.com/p-offtermatt/CometMock/cometmock/abci_client" +) + +const ( + // maxQueryLength is the maximum length of a query string that will be + // accepted. This is just a safety check to avoid outlandish queries. + maxQueryLength = 512 + SubscribeTimeout = 10 * time.Second + SubscriptionBufferSize = 100 +) + +// Subscribe for events via WebSocket. +// More: https://docs.cometbft.com/v0.37/rpc/#/Websocket/subscribe +func Subscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) { + addr := ctx.RemoteAddr() + + abci_client.GlobalClient.Logger.Info("Subscribe to query", "remote", addr, "query", query) + + q, err := cmtquery.New(query) + if err != nil { + return nil, fmt.Errorf("failed to parse query: %w", err) + } + + subCtx, cancel := context.WithTimeout(ctx.Context(), SubscribeTimeout) + defer cancel() + + sub, err := abci_client.GlobalClient.EventBus.Subscribe(subCtx, addr, q, SubscriptionBufferSize) + if err != nil { + return nil, err + } + + closeIfSlow := false + + // Capture the current ID, since it can change in the future. + subscriptionID := ctx.JSONReq.ID + go func() { + for { + select { + case msg := <-sub.Out(): + var ( + resultEvent = &ctypes.ResultEvent{Query: query, Data: msg.Data(), Events: msg.Events()} + resp = rpctypes.NewRPCSuccessResponse(subscriptionID, resultEvent) + ) + writeCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := ctx.WSConn.WriteRPCResponse(writeCtx, resp); err != nil { + abci_client.GlobalClient.Logger.Info("Can't write response (slow client)", + "to", addr, "subscriptionID", subscriptionID, "err", err) + + if closeIfSlow { + var ( + err = errors.New("subscription was canceled (reason: slow client)") + resp = rpctypes.RPCServerError(subscriptionID, err) + ) + if !ctx.WSConn.TryWriteRPCResponse(resp) { + abci_client.GlobalClient.Logger.Info("Can't write response (slow client)", + "to", addr, "subscriptionID", subscriptionID, "err", err) + } + return + } + } + case <-sub.Cancelled(): + if sub.Err() != cmtpubsub.ErrUnsubscribed { + var reason string + if sub.Err() == nil { + reason = "CometBFT exited" + } else { + reason = sub.Err().Error() + } + var ( + err = fmt.Errorf("subscription was canceled (reason: %s)", reason) + resp = rpctypes.RPCServerError(subscriptionID, err) + ) + if !ctx.WSConn.TryWriteRPCResponse(resp) { + abci_client.GlobalClient.Logger.Info("Can't write response (slow client)", + "to", addr, "subscriptionID", subscriptionID, "err", err) + } + } + return + } + } + }() + + return &ctypes.ResultSubscribe{}, nil +} + +// Unsubscribe from events via WebSocket. +// More: https://docs.cometbft.com/v0.37/rpc/#/Websocket/unsubscribe +func Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) { + addr := ctx.RemoteAddr() + abci_client.GlobalClient.Logger.Info("Unsubscribe from query", "remote", addr, "query", query) + q, err := cmtquery.New(query) + if err != nil { + return nil, fmt.Errorf("failed to parse query: %w", err) + } + err = abci_client.GlobalClient.EventBus.Unsubscribe(context.Background(), addr, q) + if err != nil { + return nil, err + } + return &ctypes.ResultUnsubscribe{}, nil +} + +// UnsubscribeAll from all events via WebSocket. +// More: https://docs.cometbft.com/v0.37/rpc/#/Websocket/unsubscribe_all +func UnsubscribeAll(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) { + addr := ctx.RemoteAddr() + abci_client.GlobalClient.Logger.Info("Unsubscribe from all", "remote", addr) + err := abci_client.GlobalClient.EventBus.UnsubscribeAll(context.Background(), addr) + if err != nil { + return nil, err + } + return &ctypes.ResultUnsubscribe{}, nil +} From 39858c0ab81e88b0b9d37170d6ae07f277e2714b Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 12 May 2023 13:47:06 +0200 Subject: [PATCH 10/26] Add example run command to README --- README.md | 5 +- genesis.json | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 genesis.json diff --git a/README.md b/README.md index f597bb3..d214a57 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -# CometMock \ No newline at end of file +# CometMock + +To run CometMock: +`go run ./cometmock localhost:36658,localhost:26658 genesis.json tcp://localhost:26657` \ No newline at end of file diff --git a/genesis.json b/genesis.json new file mode 100644 index 0000000..d212185 --- /dev/null +++ b/genesis.json @@ -0,0 +1,270 @@ +{ + "genesis_time": "2022-09-27T14:43:24.802423659Z", + "chain_id": "tendermock", + "initial_height": "1", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "-1", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age_num_blocks": "100000", + "max_age_duration": "172800", + "max_bytes": "1048576" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + }, + "version": {} + }, + "app_hash": "", + "app_state": { + "auth": { + "params": { + "max_memo_characters": "256", + "tx_sig_limit": "7", + "tx_size_cost_per_byte": "10", + "sig_verify_cost_ed25519": "590", + "sig_verify_cost_secp256k1": "1000" + }, + "accounts": [ + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos134r9s82qv8fprz3y7fw5lv40yuvsh285vxev02", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos153rpdnp3jcq4kpac8njlyf4gmf724hm6repu72", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos1x63y2p7wzsyf9ln0at56vdpe3x66jaf9qzh86t", + "pub_key": null, + "account_number": "0", + "sequence": "0" + } + ] + }, + "authz": { + "authorization": [] + }, + "bank": { + "params": { + "send_enabled": [], + "default_send_enabled": true + }, + "balances": [ + { + "address": "cosmos1x63y2p7wzsyf9ln0at56vdpe3x66jaf9qzh86t", + "coins": [ + { + "denom": "stake", + "amount": "5000000000" + } + ] + }, + { + "address": "cosmos134r9s82qv8fprz3y7fw5lv40yuvsh285vxev02", + "coins": [ + { + "denom": "stake", + "amount": "5000000000" + } + ] + }, + { + "address": "cosmos153rpdnp3jcq4kpac8njlyf4gmf724hm6repu72", + "coins": [ + { + "denom": "stake", + "amount": "5000000000" + } + ] + } + ], + "supply": [ + { + "denom": "stake", + "amount": "15000000000" + } + ], + "denom_metadata": [] + }, + "capability": { + "index": "1", + "owners": [] + }, + "crisis": { + "constant_fee": { + "denom": "stake", + "amount": "1000" + } + }, + "distribution": { + "params": { + "community_tax": "0.020000000000000000", + "base_proposer_reward": "0.010000000000000000", + "bonus_proposer_reward": "0.040000000000000000", + "withdraw_addr_enabled": true + }, + "fee_pool": { + "community_pool": [] + }, + "delegator_withdraw_infos": [], + "previous_proposer": "", + "outstanding_rewards": [], + "validator_accumulated_commissions": [], + "validator_historical_rewards": [], + "validator_current_rewards": [], + "delegator_starting_infos": [], + "validator_slash_events": [] + }, + "evidence": { + "evidence": [] + }, + "feegrant": { + "allowances": [] + }, + "genutil": { + "gen_txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgCreateValidator", + "description": { + "moniker": "node", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": { + "rate": "0.100000000000000000", + "max_rate": "0.200000000000000000", + "max_change_rate": "0.010000000000000000" + }, + "min_self_delegation": "1", + "delegator_address": "cosmos1x63y2p7wzsyf9ln0at56vdpe3x66jaf9qzh86t", + "validator_address": "cosmosvaloper1x63y2p7wzsyf9ln0at56vdpe3x66jaf99krjkc", + "pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": "pZR7fq8nmVbyJaUhV9jvlzHOG01vJQjCHi8Pb5k8m/8=" + }, + "value": { + "denom": "stake", + "amount": "5000000000" + } + } + ], + "memo": "919555b1567d03bc0ff2a6a885cc9a3e8098db49@172.17.0.2:26656", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AsBiSVZ2Ht/+o6qgBrjlRbkfRw3sJDkb7ew1GedJCbsM" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [], + "gas_limit": "200000", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "LT99RZPm6rpPsJ0ERVQkCnkI85pk57QYUkoPoTAYnX8QghrUFRIAIqFHx/SgxiRpq6XBl3hZTusNRgyqxweyNA==" + ] + } + ] + }, + "gov": { + "starting_proposal_id": "1", + "deposits": [], + "votes": [], + "proposals": [], + "deposit_params": { + "min_deposit": [ + { + "denom": "stake", + "amount": "10000000" + } + ], + "max_deposit_period": "172800s" + }, + "voting_params": { + "voting_period": "172800s" + }, + "tally_params": { + "quorum": "0.334000000000000000", + "threshold": "0.500000000000000000", + "veto_threshold": "0.334000000000000000" + } + }, + "mint": { + "minter": { + "inflation": "0.130000000000000000", + "annual_provisions": "0.000000000000000000" + }, + "params": { + "mint_denom": "stake", + "inflation_rate_change": "0.130000000000000000", + "inflation_max": "0.200000000000000000", + "inflation_min": "0.070000000000000000", + "goal_bonded": "0.670000000000000000", + "blocks_per_year": "6311520" + } + }, + "params": null, + "slashing": { + "params": { + "signed_blocks_window": "100", + "min_signed_per_window": "0.500000000000000000", + "downtime_jail_duration": "600s", + "slash_fraction_double_sign": "0.050000000000000000", + "slash_fraction_downtime": "0.010000000000000000" + }, + "signing_infos": [], + "missed_blocks": [] + }, + "staking": { + "params": { + "unbonding_time": "1814400s", + "max_validators": 100, + "max_entries": 7, + "historical_entries": 10000, + "bond_denom": "stake" + }, + "last_total_power": "0", + "last_validator_powers": [], + "validators": [], + "delegations": [], + "unbonding_delegations": [], + "redelegations": [], + "exported": false + }, + "upgrade": {}, + "vesting": {} + } +} \ No newline at end of file From ccba3a2b57033c3903abfb9618daf3d803824c83 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 15 May 2023 17:26:02 +0200 Subject: [PATCH 11/26] Keep info about last block and use in responses --- cometmock/abci_client/client.go | 45 ++++++++++++++++++++++----------- cometmock/main.go | 1 + cometmock/rpc_server/routes.go | 13 ++++++---- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index a7c34e2..6e5bc48 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -24,10 +24,12 @@ var blockMutex = sync.Mutex{} // AbciClient facilitates calls to the ABCI interface of multiple nodes. // It also tracks the current state and a common logger. type AbciClient struct { - Clients []abciclient.Client - Logger cometlog.Logger - CurState state.State - EventBus types.EventBus + Clients []abciclient.Client + Logger cometlog.Logger + CurState state.State + EventBus types.EventBus + LastBlock *types.Block + LastCommit *types.Commit // if this is true, then an error will be returned if the responses from the clients are not all equal. // can be used to check for nondeterminism in apps, but also slows down execution a bit, @@ -282,7 +284,7 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V proposerAddress = proposer.Address } - block := a.CurState.MakeBlock(a.CurState.LastBlockHeight+1, txs, &types.Commit{}, []types.Evidence{}, proposerAddress) + block := a.CurState.MakeBlock(a.CurState.LastBlockHeight+1, txs, a.LastCommit, []types.Evidence{}, proposerAddress) // override the block time, since we do not actually get votes from peers to median the time out of block.Time = blockTime blockId, err := GetBlockIdFromBlock(block) @@ -315,12 +317,32 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V return nil, nil, nil, nil, err } + deliverTxResponses := []*abcitypes.ResponseDeliverTx{} + if tx != nil { + deliverTxResponses = append(deliverTxResponses, resDeliverTx) + } + + // build components of the state update, then call the update function + abciResponses := cmtstate.ABCIResponses{ + DeliverTxs: deliverTxResponses, + EndBlock: resEndBlock, + BeginBlock: resBeginBlock, + } + // updates state as a side effect. returns an error if the state update fails - err = a.UpdateStateFromBlock(blockId, block, resBeginBlock, resEndBlock, resCommit) + err = a.UpdateStateFromBlock(blockId, block, abciResponses, resCommit) if err != nil { return nil, nil, nil, nil, err } + a.LastBlock = block + a.LastCommit = types.NewCommit( + block.Height, + 1, + *blockId, + []types.CommitSig{}, + ) + // unlock mutex blockMutex.Unlock() @@ -335,18 +357,11 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V func (a *AbciClient) UpdateStateFromBlock( blockId *types.BlockID, block *types.Block, - beginBlockResponse *abcitypes.ResponseBeginBlock, - endBlockResponse *abcitypes.ResponseEndBlock, + abciResponses cmtstate.ABCIResponses, commitResponse *abcitypes.ResponseCommit, ) error { // build components of the state update, then call the update function - abciResponses := cmtstate.ABCIResponses{ - DeliverTxs: []*abcitypes.ResponseDeliverTx{}, - EndBlock: endBlockResponse, - BeginBlock: beginBlockResponse, - } - - abciValidatorUpdates := endBlockResponse.ValidatorUpdates + abciValidatorUpdates := abciResponses.EndBlock.ValidatorUpdates err := validateValidatorUpdates(abciValidatorUpdates, a.CurState.ConsensusParams.Validator) if err != nil { return fmt.Errorf("error in validator updates: %v", err) diff --git a/cometmock/main.go b/cometmock/main.go index d8d0142..095802b 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -67,6 +67,7 @@ func main() { CurState: curState, ErrorOnUnequalResponses: true, EventBus: *eventBus, + LastCommit: &types.Commit{}, } // initialize chain diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index adcbeaf..2c9a4ae 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -87,7 +87,13 @@ func Status(ctx *rpctypes.Context) (*ctypes.ResultStatus, error) { TxIndex: "on", }, } - syncInfo := ctypes.SyncInfo{} + syncInfo := ctypes.SyncInfo{ + LatestBlockHash: abci_client.GlobalClient.LastBlock.Hash(), + LatestAppHash: abci_client.GlobalClient.LastBlock.AppHash, + LatestBlockHeight: abci_client.GlobalClient.LastBlock.Height, + LatestBlockTime: abci_client.GlobalClient.CurState.LastBlockTime, + CatchingUp: false, + } validatorInfo := ctypes.ValidatorInfo{ Address: validator.Address, PubKey: validator.PubKey, @@ -263,8 +269,5 @@ func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) blockID := abci_client.GlobalClient.CurState.LastBlockID - // TODO: return an actual block if it is needed, for now return en empty block - block := &types.Block{Header: types.Header{Height: abci_client.GlobalClient.CurState.LastBlockHeight}} - - return &ctypes.ResultBlock{BlockID: blockID, Block: block}, nil + return &ctypes.ResultBlock{BlockID: blockID, Block: abci_client.GlobalClient.LastBlock}, nil } From 2705bf84cc926d9eb3d40cad16f997be84614dee Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 15 May 2023 17:43:06 +0200 Subject: [PATCH 12/26] Implement Commit endpoint --- cometmock/rpc_server/routes.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 2c9a4ae..4f72b84 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -32,6 +32,7 @@ var Routes = map[string]*rpc.RPCFunc{ "block": rpc.NewRPCFunc(Block, "height", rpc.Cacheable("height")), "consensus_params": rpc.NewRPCFunc(ConsensusParams, "height", rpc.Cacheable("height")), "header": rpc.NewRPCFunc(Header, "height", rpc.Cacheable("height")), + "commit": rpc.NewRPCFunc(Commit, "height", rpc.Cacheable("height")), // // tx broadcast API "broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"), @@ -55,6 +56,23 @@ func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, erro return &ctypes.ResultHeader{}, nil } +// Commit gets block commit at a given height. +// If no height is provided, it will fetch the commit for the latest block. +// More: https://docs.cometbft.com/main/rpc/#/Info/commit +func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) { + // only the last height is available, since we do not keep past heights at the moment + if heightPtr != nil { + return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + } + + // If the next block has not been committed yet, + // use a non-canonical commit + lastCommit := abci_client.GlobalClient.LastCommit + lastHeader := abci_client.GlobalClient.LastBlock.Header + + return ctypes.NewResultCommit(&lastHeader, lastCommit, true), nil +} + // ConsensusParams gets the consensus parameters at the given block height. // If no height is provided, it will fetch the latest consensus params. // More: https://docs.cometbft.com/v0.37/rpc/#/Info/consensus_params From 40bf1bf44865808c95677e20208120fc30f488d1 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 19 May 2023 14:03:37 +0200 Subject: [PATCH 13/26] Add docker info to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d214a57..7984ecb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # CometMock To run CometMock: +`docker stop simapp; docker rm simapp; docker run --add-host=host.docker.internal:host-gateway --name simapp -p 26658:26658 -ti informalofftermatt/testnet:tendermock simd start --transport=grpc --with-tendermint=false --grpc-only --rpc.laddr=tcp://host.docker.internal:99999` +`docker stop simapp2; docker rm simapp2; docker run --add-host=host.docker.internal:host-gateway --name simapp2 -p 36658:26658 -ti informalofftermatt/testnet:tendermock simd start --transport=grpc --with-tendermint=false --grpc-only --rpc.laddr=tcp://host.docker.internal:99999` `go run ./cometmock localhost:36658,localhost:26658 genesis.json tcp://localhost:26657` \ No newline at end of file From 575f9234fad9e716dcaba928e1eab05010644ebb Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 19 May 2023 14:37:04 +0200 Subject: [PATCH 14/26] Store and output infos for previous heights --- cometmock/abci_client/client.go | 41 ++++-- cometmock/main.go | 2 + cometmock/rpc_server/routes.go | 111 +++++++++++--- cometmock/storage/storage.go | 124 ++++++++++++++++ cometmock/test_utils/helpers.go | 248 ++++++++++++++++++++++++++++++++ cometmock/utils/blocks.go | 17 +++ 6 files changed, 505 insertions(+), 38 deletions(-) create mode 100644 cometmock/storage/storage.go create mode 100644 cometmock/test_utils/helpers.go create mode 100644 cometmock/utils/blocks.go diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 6e5bc48..06161ff 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -14,6 +14,8 @@ import ( cmtstate "github.com/cometbft/cometbft/proto/tendermint/state" "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/types" + "github.com/p-offtermatt/CometMock/cometmock/storage" + "github.com/p-offtermatt/CometMock/cometmock/utils" ) var GlobalClient *AbciClient @@ -30,6 +32,7 @@ type AbciClient struct { EventBus types.EventBus LastBlock *types.Block LastCommit *types.Commit + Storage storage.Storage // if this is true, then an error will be returned if the responses from the clients are not all equal. // can be used to check for nondeterminism in apps, but also slows down execution a bit, @@ -248,20 +251,6 @@ func (a *AbciClient) SendAbciQuery(data []byte, path string, height int64, prove return client.QuerySync(request) } -func GetBlockIdFromBlock(block *types.Block) (*types.BlockID, error) { - partSet, error := block.MakePartSet(2) - if error != nil { - return nil, error - } - - partSetHeader := partSet.Header() - blockID := types.BlockID{ - Hash: block.Hash(), - PartSetHeader: partSetHeader, - } - return &blockID, nil -} - // RunBlock runs a block with a specified transaction through the ABCI application. // It calls BeginBlock, DeliverTx, EndBlock, Commit and then // updates the state. @@ -273,6 +262,8 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V a.Logger.Info("Running block") a.Logger.Info("State at start of block", "state", a.CurState) + newHeight := a.CurState.LastBlockHeight + 1 + txs := make([]types.Tx, 0) if tx != nil { txs = append(txs, *tx) @@ -287,7 +278,7 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V block := a.CurState.MakeBlock(a.CurState.LastBlockHeight+1, txs, a.LastCommit, []types.Evidence{}, proposerAddress) // override the block time, since we do not actually get votes from peers to median the time out of block.Time = blockTime - blockId, err := GetBlockIdFromBlock(block) + blockId, err := utils.GetBlockIdFromBlock(block) if err != nil { return nil, nil, nil, nil, err } @@ -343,6 +334,26 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V []types.CommitSig{}, ) + err = a.Storage.InsertBlock(newHeight, block) + if err != nil { + return nil, nil, nil, nil, err + } + + err = a.Storage.InsertCommit(newHeight, a.LastCommit) + if err != nil { + return nil, nil, nil, nil, err + } + + err = a.Storage.InsertState(newHeight, &a.CurState) + if err != nil { + return nil, nil, nil, nil, err + } + + err = a.Storage.InsertResponses(newHeight, &abciResponses) + if err != nil { + return nil, nil, nil, nil, err + } + // unlock mutex blockMutex.Unlock() diff --git a/cometmock/main.go b/cometmock/main.go index 095802b..deebb7a 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -11,6 +11,7 @@ import ( "github.com/cometbft/cometbft/types" "github.com/p-offtermatt/CometMock/cometmock/abci_client" "github.com/p-offtermatt/CometMock/cometmock/rpc_server" + "github.com/p-offtermatt/CometMock/cometmock/storage" ) func CreateAndStartEventBus(logger cometlog.Logger) (*types.EventBus, error) { @@ -68,6 +69,7 @@ func main() { ErrorOnUnequalResponses: true, EventBus: *eventBus, LastCommit: &types.Commit{}, + Storage: &storage.MapStorage{}, } // initialize chain diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 4f72b84..01448ef 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -12,6 +12,7 @@ import ( rpctypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" "github.com/cometbft/cometbft/types" "github.com/p-offtermatt/CometMock/cometmock/abci_client" + "github.com/p-offtermatt/CometMock/cometmock/utils" ) const ( @@ -33,6 +34,7 @@ var Routes = map[string]*rpc.RPCFunc{ "consensus_params": rpc.NewRPCFunc(ConsensusParams, "height", rpc.Cacheable("height")), "header": rpc.NewRPCFunc(Header, "height", rpc.Cacheable("height")), "commit": rpc.NewRPCFunc(Commit, "height", rpc.Cacheable("height")), + "block_results": rpc.NewRPCFunc(BlockResults, "height", rpc.Cacheable("height")), // // tx broadcast API "broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"), @@ -43,47 +45,74 @@ var Routes = map[string]*rpc.RPCFunc{ "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"), } +func getHeight(latestHeight int64, heightPtr *int64) (int64, error) { + if heightPtr != nil { + height := *heightPtr + if height <= 0 { + return 0, fmt.Errorf("height must be greater than 0, but got %d", height) + } + if height > latestHeight { + return 0, fmt.Errorf("height %d must be less than or equal to the current blockchain height %d", + height, latestHeight) + } + } + return latestHeight, nil +} + // Header gets block header at a given height. // If no height is provided, it will fetch the latest header. // More: https://docs.cometbft.com/v0.37/rpc/#/Info/header -// TODO: currently unfilled func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, error) { - // only the last height is available, since we do not keep past heights at the moment - if heightPtr != nil { - return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) + if err != nil { + return nil, err } - return &ctypes.ResultHeader{}, nil + block, err := abci_client.GlobalClient.Storage.GetBlock(height) + if err != nil { + return nil, err + } + + return &ctypes.ResultHeader{Header: &block.Header}, nil } // Commit gets block commit at a given height. // If no height is provided, it will fetch the commit for the latest block. // More: https://docs.cometbft.com/main/rpc/#/Info/commit func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) { - // only the last height is available, since we do not keep past heights at the moment - if heightPtr != nil { - return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) + if err != nil { + return nil, err + } + + commit, err := abci_client.GlobalClient.Storage.GetCommit(height) + if err != nil { + return nil, err } - // If the next block has not been committed yet, - // use a non-canonical commit - lastCommit := abci_client.GlobalClient.LastCommit - lastHeader := abci_client.GlobalClient.LastBlock.Header + block, err := abci_client.GlobalClient.Storage.GetBlock(height) + if err != nil { + return nil, err + } - return ctypes.NewResultCommit(&lastHeader, lastCommit, true), nil + return ctypes.NewResultCommit(&block.Header, commit, true), nil } // ConsensusParams gets the consensus parameters at the given block height. // If no height is provided, it will fetch the latest consensus params. // More: https://docs.cometbft.com/v0.37/rpc/#/Info/consensus_params func ConsensusParams(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultConsensusParams, error) { - // only the last height is available, since we do not keep past heights at the moment - if heightPtr != nil { - return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) + if err != nil { + return nil, err } - height := abci_client.GlobalClient.CurState.LastBlockHeight - consensusParams := abci_client.GlobalClient.CurState.ConsensusParams + stateForHeight, err := abci_client.GlobalClient.Storage.GetState(height) + if err != nil { + return nil, err + } + + consensusParams := stateForHeight.ConsensusParams return &ctypes.ResultConsensusParams{ BlockHeight: height, @@ -280,12 +309,48 @@ func validateSkipCount(page, perPage int) int { } func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) { - // only the last height is available, since we do not keep past heights at the moment - if heightPtr != nil { - return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) + if err != nil { + return nil, err } - blockID := abci_client.GlobalClient.CurState.LastBlockID + block, err := abci_client.GlobalClient.Storage.GetBlock(height) + if err != nil { + return nil, err + } - return &ctypes.ResultBlock{BlockID: blockID, Block: abci_client.GlobalClient.LastBlock}, nil + blockID, err := utils.GetBlockIdFromBlock(block) + if err != nil { + return nil, err + } + + return &ctypes.ResultBlock{BlockID: *blockID, Block: abci_client.GlobalClient.LastBlock}, nil +} + +// BlockResults gets ABCIResults at a given height. +// If no height is provided, it will fetch results for the latest block. +// +// Results are for the height of the block containing the txs. +// Thus response.results.deliver_tx[5] is the results of executing +// getBlock(h).Txs[5] +// More: https://docs.cometbft.com/v0.37/rpc/#/Info/block_results +func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) { + height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) + if err != nil { + return nil, err + } + + results, err := abci_client.GlobalClient.Storage.GetResponses(height) + if err != nil { + return nil, err + } + + return &ctypes.ResultBlockResults{ + Height: height, + TxsResults: results.DeliverTxs, + BeginBlockEvents: results.BeginBlock.Events, + EndBlockEvents: results.EndBlock.Events, + ValidatorUpdates: results.EndBlock.ValidatorUpdates, + ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates, + }, nil } diff --git a/cometmock/storage/storage.go b/cometmock/storage/storage.go new file mode 100644 index 0000000..1a0ba59 --- /dev/null +++ b/cometmock/storage/storage.go @@ -0,0 +1,124 @@ +package storage + +import ( + "fmt" + + protostate "github.com/cometbft/cometbft/proto/tendermint/state" + cometstate "github.com/cometbft/cometbft/state" + "github.com/cometbft/cometbft/types" +) + +// Storage is an interface for storing blocks, commits and states by height. +type Storage interface { + // InsertBlock inserts a block at a given height. + // If there is already a block at that height, it should be overwritten. + InsertBlock(height int64, block *types.Block) error + // GetBlock returns the block at a given height. + GetBlock(height int64) (*types.Block, error) + + // InsertCommit inserts a commit at a given height. + // If there is already a commit at that height, it should be overwritten. + InsertCommit(height int64, commit *types.Commit) error + // GetCommit returns the commit at a given height. + GetCommit(height int64) (*types.Commit, error) + + // InsertState inserts a state at a given height. This is the state after + // applying the block at that height. + // If there is already a state at that height, it should be overwritten. + InsertState(height int64, state *cometstate.State) error + // GetState returns the state at a given height. This is the state after + // applying the block at that height. + GetState(height int64) (*cometstate.State, error) + + // InsertResponses inserts the ABCI responses from a given height. + // If there are already responses at that height, they should be overwritten. + InsertResponses(height int64, responses *protostate.ABCIResponses) error + // GetResponses returns the ABCI responses from a given height. + GetResponses(height int64) (*protostate.ABCIResponses, error) +} + +// MapStorage is a simple in-memory implementation of Storage. +type MapStorage struct { + blocks map[int64]*types.Block + commits map[int64]*types.Commit + states map[int64]*cometstate.State + responses map[int64]*protostate.ABCIResponses +} + +func (m *MapStorage) InsertBlock(height int64, block *types.Block) error { + if m.blocks == nil { + m.blocks = make(map[int64]*types.Block) + } + m.blocks[height] = block + return nil +} + +func (m *MapStorage) GetBlock(height int64) (*types.Block, error) { + if m.blocks == nil { + m.blocks = make(map[int64]*types.Block) + } + if block, ok := m.blocks[height]; ok { + return block, nil + } + return nil, fmt.Errorf("block for height %v not found", height) +} + +func (m *MapStorage) InsertCommit(height int64, commit *types.Commit) error { + if m.commits == nil { + m.commits = make(map[int64]*types.Commit) + } + + m.commits[height] = commit + return nil +} + +func (m *MapStorage) GetCommit(height int64) (*types.Commit, error) { + if m.commits == nil { + m.commits = make(map[int64]*types.Commit) + } + + if commit, ok := m.commits[height]; ok { + return commit, nil + } + return nil, fmt.Errorf("commit for height %v not found", height) +} + +func (m *MapStorage) InsertState(height int64, state *cometstate.State) error { + if m.states == nil { + m.states = make(map[int64]*cometstate.State) + } + + m.states[height] = state + return nil +} + +func (m *MapStorage) GetState(height int64) (*cometstate.State, error) { + if m.states == nil { + m.states = make(map[int64]*cometstate.State) + } + + if state, ok := m.states[height]; ok { + return state, nil + } + return nil, fmt.Errorf("state for height %v not found", height) +} + +func (m *MapStorage) InsertResponses(height int64, responses *protostate.ABCIResponses) error { + if m.responses == nil { + m.responses = make(map[int64]*protostate.ABCIResponses) + } + + m.responses[height] = responses + return nil +} + +func (m *MapStorage) GetResponses(height int64) (*protostate.ABCIResponses, error) { + if m.responses == nil { + m.responses = make(map[int64]*protostate.ABCIResponses) + } + + if responses, ok := m.responses[height]; ok { + return responses, nil + } + return nil, fmt.Errorf("responses for height %v not found", height) +} diff --git a/cometmock/test_utils/helpers.go b/cometmock/test_utils/helpers.go new file mode 100644 index 0000000..fa3140b --- /dev/null +++ b/cometmock/test_utils/helpers.go @@ -0,0 +1,248 @@ +package test_utils + +import ( + "time" + + "github.com/cometbft/cometbft/crypto" + "github.com/cometbft/cometbft/crypto/ed25519" + "github.com/cometbft/cometbft/crypto/tmhash" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmtversion "github.com/cometbft/cometbft/proto/tendermint/version" + "github.com/cometbft/cometbft/types" + cmttime "github.com/cometbft/cometbft/types/time" + "github.com/cometbft/cometbft/version" +) + +// file is adapted from https://github.com/cometbft/cometbft/blob/9267594e0a17c01cc4a97b399ada5eaa8a734db5/light/helpers_test.go#L16 + +// privKeys is a helper type for testing. +// +// It lets us simulate signing with many keys. The main use case is to create +// a set, and call GenSignedHeader to get properly signed header for testing. +// +// You can set different weights of validators each time you call ToValidators, +// and can optionally extend the validator set later with Extend. +type privKeys []crypto.PrivKey + +// genPrivKeys produces an array of private keys to generate commits. +func GenPrivKeys(n int) privKeys { + res := make(privKeys, n) + for i := range res { + res[i] = ed25519.GenPrivKey() + } + return res +} + +// // Change replaces the key at index i. +// func (pkz privKeys) Change(i int) privKeys { +// res := make(privKeys, len(pkz)) +// copy(res, pkz) +// res[i] = ed25519.GenPrivKey() +// return res +// } + +// Extend adds n more keys (to remove, just take a slice). +func (pkz privKeys) Extend(n int) privKeys { + extra := GenPrivKeys(n) + return append(pkz, extra...) +} + +// // GenSecpPrivKeys produces an array of secp256k1 private keys to generate commits. +// func GenSecpPrivKeys(n int) privKeys { +// res := make(privKeys, n) +// for i := range res { +// res[i] = secp256k1.GenPrivKey() +// } +// return res +// } + +// // ExtendSecp adds n more secp256k1 keys (to remove, just take a slice). +// func (pkz privKeys) ExtendSecp(n int) privKeys { +// extra := GenSecpPrivKeys(n) +// return append(pkz, extra...) +// } + +// ToValidators produces a valset from the set of keys. +// The first key has weight `init` and it increases by `inc` every step +// so we can have all the same weight, or a simple linear distribution +// (should be enough for testing). +func (pkz privKeys) ToValidators(init, inc int64) *types.ValidatorSet { + res := make([]*types.Validator, len(pkz)) + for i, k := range pkz { + res[i] = types.NewValidator(k.PubKey(), init+int64(i)*inc) + } + return types.NewValidatorSet(res) +} + +// signHeader properly signs the header with all keys from first to last exclusive. +func (pkz privKeys) signHeader(header *types.Header, valSet *types.ValidatorSet, first, last int) *types.Commit { + commitSigs := make([]types.CommitSig, len(pkz)) + for i := 0; i < len(pkz); i++ { + commitSigs[i] = types.NewCommitSigAbsent() + } + + blockID := types.BlockID{ + Hash: header.Hash(), + PartSetHeader: types.PartSetHeader{Total: 1, Hash: crypto.CRandBytes(32)}, + } + + // Fill in the votes we want. + for i := first; i < last && i < len(pkz); i++ { + vote := makeVote(header, valSet, pkz[i], blockID) + commitSigs[vote.ValidatorIndex] = vote.CommitSig() + } + + return types.NewCommit(header.Height, 1, blockID, commitSigs) +} + +func makeVote(header *types.Header, valset *types.ValidatorSet, + key crypto.PrivKey, blockID types.BlockID, +) *types.Vote { + addr := key.PubKey().Address() + idx, _ := valset.GetByAddress(addr) + vote := &types.Vote{ + ValidatorAddress: addr, + ValidatorIndex: idx, + Height: header.Height, + Round: 1, + Timestamp: cmttime.Now(), + Type: cmtproto.PrecommitType, + BlockID: blockID, + } + + v := vote.ToProto() + // Sign it + signBytes := types.VoteSignBytes(header.ChainID, v) + sig, err := key.Sign(signBytes) + if err != nil { + panic(err) + } + + vote.Signature = sig + + return vote +} + +func genHeader(chainID string, height int64, bTime time.Time, txs types.Txs, + valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, +) *types.Header { + return &types.Header{ + Version: cmtversion.Consensus{Block: version.BlockProtocol, App: 0}, + ChainID: chainID, + Height: height, + Time: bTime, + // LastBlockID + // LastCommitHash + ValidatorsHash: valset.Hash(), + NextValidatorsHash: nextValset.Hash(), + DataHash: txs.Hash(), + AppHash: appHash, + ConsensusHash: consHash, + LastResultsHash: resHash, + ProposerAddress: valset.Validators[0].Address, + } +} + +// GenSignedHeader calls genHeader and signHeader and combines them into a SignedHeader. +func (pkz privKeys) GenSignedHeader(chainID string, height int64, bTime time.Time, txs types.Txs, + valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int, +) *types.SignedHeader { + header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash) + return &types.SignedHeader{ + Header: header, + Commit: pkz.signHeader(header, valset, first, last), + } +} + +// GenSignedHeaderLastBlockID calls genHeader and signHeader and combines them into a SignedHeader. +func (pkz privKeys) GenSignedHeaderLastBlockID(chainID string, height int64, bTime time.Time, txs types.Txs, + valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int, + lastBlockID types.BlockID, +) *types.SignedHeader { + header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash) + header.LastBlockID = lastBlockID + return &types.SignedHeader{ + Header: header, + Commit: pkz.signHeader(header, valset, first, last), + } +} + +func (pkz privKeys) ChangeKeys(delta int) privKeys { + newKeys := pkz[delta:] + return newKeys.Extend(delta) +} + +// Generates the header and validator set to create a full entire mock node with blocks to height ( +// blockSize) and with variation in validator sets. BlockIntervals are in per minute. +// NOTE: Expected to have a large validator set size ~ 100 validators. +func genMockNodeWithKeys( + chainID string, + blockSize int64, + valSize int, + valVariation float32, + bTime time.Time) ( + map[int64]*types.SignedHeader, + map[int64]*types.ValidatorSet, + map[int64]privKeys, +) { + var ( + headers = make(map[int64]*types.SignedHeader, blockSize) + valset = make(map[int64]*types.ValidatorSet, blockSize+1) + keymap = make(map[int64]privKeys, blockSize+1) + keys = GenPrivKeys(valSize) + totalVariation = valVariation + valVariationInt int + newKeys privKeys + ) + + valVariationInt = int(totalVariation) + totalVariation = -float32(valVariationInt) + newKeys = keys.ChangeKeys(valVariationInt) + keymap[1] = keys + keymap[2] = newKeys + + // genesis header and vals + lastHeader := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Minute), nil, + keys.ToValidators(2, 0), newKeys.ToValidators(2, 0), hash("app_hash"), hash("cons_hash"), + hash("results_hash"), 0, len(keys)) + currentHeader := lastHeader + headers[1] = currentHeader + valset[1] = keys.ToValidators(2, 0) + keys = newKeys + + for height := int64(2); height <= blockSize; height++ { + totalVariation += valVariation + valVariationInt = int(totalVariation) + totalVariation = -float32(valVariationInt) + newKeys = keys.ChangeKeys(valVariationInt) + currentHeader = keys.GenSignedHeaderLastBlockID(chainID, height, bTime.Add(time.Duration(height)*time.Minute), + nil, + keys.ToValidators(2, 0), newKeys.ToValidators(2, 0), hash("app_hash"), hash("cons_hash"), + hash("results_hash"), 0, len(keys), types.BlockID{Hash: lastHeader.Hash()}) + headers[height] = currentHeader + valset[height] = keys.ToValidators(2, 0) + lastHeader = currentHeader + keys = newKeys + keymap[height+1] = keys + } + + return headers, valset, keymap +} + +func genMockNode( + chainID string, + blockSize int64, + valSize int, + valVariation float32, + bTime time.Time) ( + string, + map[int64]*types.SignedHeader, + map[int64]*types.ValidatorSet, +) { + headers, valset, _ := genMockNodeWithKeys(chainID, blockSize, valSize, valVariation, bTime) + return chainID, headers, valset +} + +func hash(s string) []byte { + return tmhash.Sum([]byte(s)) +} diff --git a/cometmock/utils/blocks.go b/cometmock/utils/blocks.go new file mode 100644 index 0000000..6540254 --- /dev/null +++ b/cometmock/utils/blocks.go @@ -0,0 +1,17 @@ +package utils + +import "github.com/cometbft/cometbft/types" + +func GetBlockIdFromBlock(block *types.Block) (*types.BlockID, error) { + partSet, error := block.MakePartSet(2) + if error != nil { + return nil, error + } + + partSetHeader := partSet.Header() + blockID := types.BlockID{ + Hash: block.Hash(), + PartSetHeader: partSetHeader, + } + return &blockID, nil +} From 4aebcd69631882763029aafbabd875394d3bdfdb Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 22 May 2023 14:43:04 +0200 Subject: [PATCH 15/26] Add signing commits --- README.md | 3 ++- cometmock/abci_client/client.go | 40 ++++++++++++++++++++++++------ cometmock/main.go | 26 +++++++++++++++++++ cometmock/rpc_server/routes.go | 14 +++++++---- cometmock/storage/storage.go | 3 +++ cometmock/utils/votes.go | 44 +++++++++++++++++++++++++++++++++ go.mod | 4 +++ go.sum | 10 ++++++++ 8 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 cometmock/utils/votes.go diff --git a/README.md b/README.md index 7984ecb..7e3722e 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,5 @@ To run CometMock: `docker stop simapp; docker rm simapp; docker run --add-host=host.docker.internal:host-gateway --name simapp -p 26658:26658 -ti informalofftermatt/testnet:tendermock simd start --transport=grpc --with-tendermint=false --grpc-only --rpc.laddr=tcp://host.docker.internal:99999` `docker stop simapp2; docker rm simapp2; docker run --add-host=host.docker.internal:host-gateway --name simapp2 -p 36658:26658 -ti informalofftermatt/testnet:tendermock simd start --transport=grpc --with-tendermint=false --grpc-only --rpc.laddr=tcp://host.docker.internal:99999` -`go run ./cometmock localhost:36658,localhost:26658 genesis.json tcp://localhost:26657` \ No newline at end of file +`go run ./cometmock localhost:36658,localhost:26658 genesis.json tcp://localhost:26657` +`curl -H 'Content-Type: application/json' -H 'Accept:application/json' --data '{"jsonrpc":"2.0","method":"commit","params":{},"id":1}' 127.0.0.1:26657` \ No newline at end of file diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 06161ff..792f209 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -12,6 +12,7 @@ import ( "github.com/cometbft/cometbft/crypto/merkle" cometlog "github.com/cometbft/cometbft/libs/log" cmtstate "github.com/cometbft/cometbft/proto/tendermint/state" + cmttypes "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/types" "github.com/p-offtermatt/CometMock/cometmock/storage" @@ -26,13 +27,14 @@ var blockMutex = sync.Mutex{} // AbciClient facilitates calls to the ABCI interface of multiple nodes. // It also tracks the current state and a common logger. type AbciClient struct { - Clients []abciclient.Client - Logger cometlog.Logger - CurState state.State - EventBus types.EventBus - LastBlock *types.Block - LastCommit *types.Commit - Storage storage.Storage + Clients []abciclient.Client + Logger cometlog.Logger + CurState state.State + EventBus types.EventBus + LastBlock *types.Block + LastCommit *types.Commit + Storage storage.Storage + PrivValidators []types.PrivValidator // if this is true, then an error will be returned if the responses from the clients are not all equal. // can be used to check for nondeterminism in apps, but also slows down execution a bit, @@ -327,11 +329,33 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V } a.LastBlock = block + + commitSigs := []types.CommitSig{} + + precommitType := cmttypes.SignedMsgType_value["SIGNED_MSG_TYPE_PRECOMMIT"] + for index, pv := range a.PrivValidators { + // create and sign a precommit + vote, err := utils.MakeVote( + pv, + a.CurState.ChainID, + int32(index), + block.Height, + 2, // round to consensus - can be arbitrary + int(precommitType), // for which step the vote is - we use precommit + *blockId, + time.Now(), + ) + if err != nil { + return nil, nil, nil, nil, err + } + commitSigs = append(commitSigs, vote.CommitSig()) + } + a.LastCommit = types.NewCommit( block.Height, 1, *blockId, - []types.CommitSig{}, + commitSigs, ) err = a.Storage.InsertBlock(newHeight, block) diff --git a/cometmock/main.go b/cometmock/main.go index deebb7a..670d92e 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -7,6 +7,7 @@ import ( comet_abciclient "github.com/cometbft/cometbft/abci/client" cometlog "github.com/cometbft/cometbft/libs/log" + "github.com/cometbft/cometbft/privval" "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/types" "github.com/p-offtermatt/CometMock/cometmock/abci_client" @@ -23,6 +24,23 @@ func CreateAndStartEventBus(logger cometlog.Logger) (*types.EventBus, error) { return eventBus, nil } +// GetMockPVsFromNodeHomes returns a list of MockPVs, created with the priv_validator_key's from the specified node homes +// We use MockPV because they do not do sanity checks that would e.g. prevent double signing +func GetMockPVsFromNodeHomes(nodeHomes []string) []types.PrivValidator { + mockPVs := make([]types.PrivValidator, 0) + + for _, nodeHome := range nodeHomes { + privValidatorKeyFile := nodeHome + "/config/priv_validator_key.json" + privValidatorStateFile := nodeHome + "/data/priv_validator_state.json" + validator := privval.LoadFilePV(privValidatorKeyFile, privValidatorStateFile) + + mockPV := types.NewMockPVWithParams(validator.Key.PrivKey, false, false) + mockPVs = append(mockPVs, mockPV) + } + + return mockPVs +} + func main() { logger := cometlog.NewTMLogger(cometlog.NewSyncWriter(os.Stdout)) @@ -35,6 +53,13 @@ func main() { appAddresses := strings.Split(args[0], ",") genesisFile := args[1] cometMockListenAddress := args[2] + nodeHomesString := args[3] + + // read node homes from args + nodeHomes := strings.Split(nodeHomesString, ",") + + // get priv validators from node Homes + privVals := GetMockPVsFromNodeHomes(nodeHomes) genesisDoc, err := state.MakeGenesisDocFromFile(genesisFile) if err != nil { @@ -70,6 +95,7 @@ func main() { EventBus: *eventBus, LastCommit: &types.Commit{}, Storage: &storage.MapStorage{}, + PrivValidators: privVals, } // initialize chain diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 01448ef..4d95c58 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -55,6 +55,7 @@ func getHeight(latestHeight int64, heightPtr *int64) (int64, error) { return 0, fmt.Errorf("height %d must be less than or equal to the current blockchain height %d", height, latestHeight) } + return height, nil } return latestHeight, nil } @@ -233,14 +234,17 @@ func ABCIQuery( } func Validators(ctx *rpctypes.Context, heightPtr *int64, pagePtr, perPagePtr *int) (*ctypes.ResultValidators, error) { - // only the last height is available, since we do not keep past heights at the moment - if heightPtr != nil { - return nil, fmt.Errorf("height parameter is not supported, use version of the function without height") + height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) + if err != nil { + return nil, err } - height := abci_client.GlobalClient.CurState.LastBlockHeight + pastState, err := abci_client.GlobalClient.Storage.GetState(height) + if err != nil { + return nil, err + } - validators := abci_client.GlobalClient.CurState.LastValidators + validators := pastState.Validators totalCount := len(validators.Validators) perPage := validatePerPage(perPagePtr) diff --git a/cometmock/storage/storage.go b/cometmock/storage/storage.go index 1a0ba59..38ab2e7 100644 --- a/cometmock/storage/storage.go +++ b/cometmock/storage/storage.go @@ -45,6 +45,9 @@ type MapStorage struct { responses map[int64]*protostate.ABCIResponses } +// ensure MapStorage implements Storage +var _ Storage = (*MapStorage)(nil) + func (m *MapStorage) InsertBlock(height int64, block *types.Block) error { if m.blocks == nil { m.blocks = make(map[int64]*types.Block) diff --git a/cometmock/utils/votes.go b/cometmock/utils/votes.go new file mode 100644 index 0000000..4a10b0d --- /dev/null +++ b/cometmock/utils/votes.go @@ -0,0 +1,44 @@ +package utils + +import ( + "time" + + cmttypes "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cometbft/cometbft/types" +) + +// MakeVote creates a signed vote. +// Adapted from https://github.com/cometbft/cometbft/blob/9267594e0a17c01cc4a97b399ada5eaa8a734db5/internal/test/vote.go#L10. +func MakeVote( + val types.PrivValidator, // PrivValidator is the validator that will sign the vote. + chainID string, + valIndex int32, + height int64, + round int32, + step int, // StepType is the step in the consensus process, see https://github.com/cometbft/cometbft/blob/9267594e0a17c01cc4a97b399ada5eaa8a734db5/proto/tendermint/types/types.pb.go#L68 + blockID types.BlockID, + time time.Time, +) (*types.Vote, error) { + pubKey, err := val.GetPubKey() + if err != nil { + return nil, err + } + + v := &types.Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: valIndex, + Height: height, + Round: round, + Type: cmttypes.SignedMsgType(step), + BlockID: blockID, + Timestamp: time, + } + + vpb := v.ToProto() + if err := val.SignVote(chainID, vpb); err != nil { + return nil, err + } + + v.Signature = vpb.Signature + return v, nil +} diff --git a/go.mod b/go.mod index 828dfec..1385c55 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cometbft/cometbft-db v0.7.0 // indirect github.com/cosmos/gogoproto v1.4.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect @@ -31,11 +32,13 @@ require ( github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/stretchr/testify v1.8.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect go.etcd.io/bbolt v1.3.6 // indirect @@ -46,4 +49,5 @@ require ( google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect google.golang.org/grpc v1.52.0 // indirect google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0d30300..76de9d0 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,10 @@ github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -287,11 +289,16 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= @@ -591,6 +598,7 @@ google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QO gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -603,7 +611,9 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 5de2fd6ae3371855fefc7d0b327df3abb25d5cf0 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 22 May 2023 14:44:38 +0200 Subject: [PATCH 16/26] Adjust expected params --- cometmock/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cometmock/main.go b/cometmock/main.go index 670d92e..0b7ca40 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -44,8 +44,8 @@ func GetMockPVsFromNodeHomes(nodeHomes []string) []types.PrivValidator { func main() { logger := cometlog.NewTMLogger(cometlog.NewSyncWriter(os.Stdout)) - if len(os.Args) != 4 { - logger.Error("Usage: ") + if len(os.Args) != 5 { + logger.Error("Usage: ") } args := os.Args[1:] From b6c64d0ce2b0868a60e18a770408cff7076daf2d Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 23 May 2023 13:20:44 +0200 Subject: [PATCH 17/26] Return updated node version in Status --- cometmock/rpc_server/routes.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 4d95c58..99df9b1 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -6,11 +6,13 @@ import ( "github.com/cometbft/cometbft/libs/bytes" cmtmath "github.com/cometbft/cometbft/libs/math" + "github.com/cometbft/cometbft/p2p" cometp2p "github.com/cometbft/cometbft/p2p" ctypes "github.com/cometbft/cometbft/rpc/core/types" rpc "github.com/cometbft/cometbft/rpc/jsonrpc/server" rpctypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" "github.com/cometbft/cometbft/types" + "github.com/cometbft/cometbft/version" "github.com/p-offtermatt/CometMock/cometmock/abci_client" "github.com/p-offtermatt/CometMock/cometmock/utils" ) @@ -126,7 +128,8 @@ func ConsensusParams(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCon // More: https://docs.cometbft.com/v0.37/rpc/#/Info/status func Status(ctx *rpctypes.Context) (*ctypes.ResultStatus, error) { // return status as if we are the first validator - validator := abci_client.GlobalClient.CurState.Validators.Validators[0] + curState := abci_client.GlobalClient.CurState + validator := curState.Validators.Validators[0] nodeInfo := cometp2p.DefaultNodeInfo{ DefaultNodeID: cometp2p.PubKeyToID(validator.PubKey), @@ -134,6 +137,12 @@ func Status(ctx *rpctypes.Context) (*ctypes.ResultStatus, error) { Other: cometp2p.DefaultNodeInfoOther{ TxIndex: "on", }, + Version: "v0.37.1", + ProtocolVersion: p2p.NewProtocolVersion( + version.P2PProtocol, // global + curState.Version.Consensus.Block, + curState.Version.Consensus.App, + ), } syncInfo := ctypes.SyncInfo{ LatestBlockHash: abci_client.GlobalClient.LastBlock.Hash(), From 93ecb8081500785abab2e4755a04b5021e267db5 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 23 May 2023 13:56:30 +0200 Subject: [PATCH 18/26] Add return values for broadcasts --- cometmock/abci_client/client.go | 59 +++++++++++++++++++++++++-------- cometmock/rpc_server/routes.go | 19 ++++++++--- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 792f209..dce3b62 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -214,6 +214,34 @@ func (a *AbciClient) SendCommit() (*abcitypes.ResponseCommit, error) { return responses[0], nil } +func (a *AbciClient) SendCheckTx(tx *[]byte) (*abcitypes.ResponseCheckTx, error) { + // build the CheckTx request + checkTxRequest := abcitypes.RequestCheckTx{ + Tx: *tx, + } + + // send CheckTx to all clients and collect the responses + responses := []*abcitypes.ResponseCheckTx{} + for _, client := range a.Clients { + response, err := client.CheckTxSync(checkTxRequest) + if err != nil { + return nil, err + } + responses = append(responses, response) + } + + if a.ErrorOnUnequalResponses { + // return an error if the responses are not all equal + for i := 1; i < len(responses); i++ { + if !reflect.DeepEqual(responses[i], responses[0]) { + return nil, fmt.Errorf("responses are not all equal: %v is not equal to %v", responses[i], responses[0]) + } + } + } + + return responses[0], nil +} + func (a *AbciClient) SendDeliverTx(tx *[]byte) (*abcitypes.ResponseDeliverTx, error) { // build the DeliverTx request deliverTxRequest := abcitypes.RequestDeliverTx{ @@ -257,7 +285,7 @@ func (a *AbciClient) SendAbciQuery(data []byte, path string, height int64, prove // It calls BeginBlock, DeliverTx, EndBlock, Commit and then // updates the state. // RunBlock is safe for use by multiple goroutines simultaneously. -func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.Validator) (*abcitypes.ResponseBeginBlock, *abcitypes.ResponseDeliverTx, *abcitypes.ResponseEndBlock, *abcitypes.ResponseCommit, error) { +func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.Validator) (*abcitypes.ResponseBeginBlock, *abcitypes.ResponseCheckTx, *abcitypes.ResponseDeliverTx, *abcitypes.ResponseEndBlock, *abcitypes.ResponseCommit, error) { // lock mutex to avoid running two blocks at the same time blockMutex.Lock() @@ -271,6 +299,11 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V txs = append(txs, *tx) } + resCheckTx, err := a.SendCheckTx(tx) + if err != nil { + return nil, nil, nil, nil, nil, err + } + // TODO: handle special case where proposer is nil var proposerAddress types.Address if proposer != nil { @@ -282,19 +315,19 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V block.Time = blockTime blockId, err := utils.GetBlockIdFromBlock(block) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } resBeginBlock, err := a.SendBeginBlock(block) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } var resDeliverTx *abcitypes.ResponseDeliverTx if tx != nil { resDeliverTx, err = a.SendDeliverTx(tx) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } } else { resDeliverTx = nil @@ -302,12 +335,12 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V resEndBlock, err := a.SendEndBlock() if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } resCommit, err := a.SendCommit() if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } deliverTxResponses := []*abcitypes.ResponseDeliverTx{} @@ -325,7 +358,7 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V // updates state as a side effect. returns an error if the state update fails err = a.UpdateStateFromBlock(blockId, block, abciResponses, resCommit) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } a.LastBlock = block @@ -346,7 +379,7 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V time.Now(), ) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } commitSigs = append(commitSigs, vote.CommitSig()) } @@ -360,28 +393,28 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V err = a.Storage.InsertBlock(newHeight, block) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } err = a.Storage.InsertCommit(newHeight, a.LastCommit) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } err = a.Storage.InsertState(newHeight, &a.CurState) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } err = a.Storage.InsertResponses(newHeight, &abciResponses) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } // unlock mutex blockMutex.Unlock() - return resBeginBlock, resDeliverTx, resEndBlock, resCommit, nil + return resBeginBlock, resCheckTx, resDeliverTx, resEndBlock, resCommit, nil } // UpdateStateFromBlock updates the AbciClients state diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 99df9b1..f844a2f 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -189,12 +189,18 @@ func BroadcastTxSync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcas abci_client.GlobalClient.Logger.Info( "BroadcastTxSync called", "tx", tx) - _, err := BroadcastTx(&tx) + resBroadcastTx, err := BroadcastTx(&tx) if err != nil { return nil, err } - return &ctypes.ResultBroadcastTx{}, nil + return &ctypes.ResultBroadcastTx{ + Code: resBroadcastTx.CheckTx.Code, + Data: resBroadcastTx.CheckTx.Data, + Log: resBroadcastTx.CheckTx.Log, + Hash: resBroadcastTx.Hash, + Codespace: resBroadcastTx.CheckTx.Codespace, + }, nil } // BroadcastTxAsync would normally broadcast a transaction and return immediately. @@ -219,13 +225,18 @@ func BroadcastTx(tx *types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { byteTx := []byte(*tx) - _, _, _, _, err := abci_client.GlobalClient.RunBlock(&byteTx, time.Now(), abci_client.GlobalClient.CurState.LastValidators.Proposer) + _, responseCheckTx, responseDeliverTx, _, _, err := abci_client.GlobalClient.RunBlock(&byteTx, time.Now(), abci_client.GlobalClient.CurState.LastValidators.Proposer) if err != nil { return nil, err } // TODO: fill the return value if necessary - return &ctypes.ResultBroadcastTxCommit{}, nil + return &ctypes.ResultBroadcastTxCommit{ + CheckTx: *responseCheckTx, + DeliverTx: *responseDeliverTx, + Height: abci_client.GlobalClient.LastBlock.Height, + Hash: tx.Hash(), + }, nil } func ABCIQuery( From 49933f8febfc50fa2d786a4b1dda0021c8fd872f Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 23 May 2023 18:22:51 +0200 Subject: [PATCH 19/26] Drop comet to v0.34.27-alpha for compatibility --- README.md | 2 +- cometmock/abci_client/client.go | 30 ++++++++++++++------------ cometmock/rpc_server/routes.go | 37 +++++++++++++++++---------------- cometmock/utils/blocks.go | 5 +---- go.mod | 10 ++++----- go.sum | 37 ++++++++++++++++++--------------- 6 files changed, 62 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 7e3722e..f7b5bc6 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,5 @@ To run CometMock: `docker stop simapp; docker rm simapp; docker run --add-host=host.docker.internal:host-gateway --name simapp -p 26658:26658 -ti informalofftermatt/testnet:tendermock simd start --transport=grpc --with-tendermint=false --grpc-only --rpc.laddr=tcp://host.docker.internal:99999` `docker stop simapp2; docker rm simapp2; docker run --add-host=host.docker.internal:host-gateway --name simapp2 -p 36658:26658 -ti informalofftermatt/testnet:tendermock simd start --transport=grpc --with-tendermint=false --grpc-only --rpc.laddr=tcp://host.docker.internal:99999` -`go run ./cometmock localhost:36658,localhost:26658 genesis.json tcp://localhost:26657` +`go run ./cometmock localhost:36658,localhost:26658 genesis.json tcp://localhost:26657 /Users/offtermatt/simapp1,/Users/offtermatt/simapp2` `curl -H 'Content-Type: application/json' -H 'Accept:application/json' --data '{"jsonrpc":"2.0","method":"commit","params":{},"id":1}' 127.0.0.1:26657` \ No newline at end of file diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index dce3b62..669fa74 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -72,7 +72,7 @@ func (a *AbciClient) SendBeginBlock(block *types.Block) (*abcitypes.ResponseBegi func CreateBeginBlockRequest(header *types.Header, lastCommit *types.Commit) *abcitypes.RequestBeginBlock { return &abcitypes.RequestBeginBlock{ // TODO: fill in Votes - LastCommitInfo: abcitypes.CommitInfo{Round: lastCommit.Round, Votes: []abcitypes.VoteInfo{}}, + LastCommitInfo: abcitypes.LastCommitInfo{Round: lastCommit.Round, Votes: []abcitypes.VoteInfo{}}, Header: *header.ToProto(), } } @@ -111,7 +111,7 @@ func (a *AbciClient) SendInitChain(genesisState state.State, genesisDoc *types.G } func CreateInitChainRequest(genesisState state.State, genesisDoc *types.GenesisDoc) *abcitypes.RequestInitChain { - consensusParams := genesisState.ConsensusParams.ToProto() + consensusParams := types.TM2PB.ConsensusParams(&genesisState.ConsensusParams) genesisValidators := genesisDoc.Validators @@ -127,7 +127,7 @@ func CreateInitChainRequest(genesisState state.State, genesisDoc *types.GenesisD InitialHeight: genesisState.InitialHeight, Time: genesisDoc.GenesisTime, ChainId: genesisState.ChainID, - ConsensusParams: &consensusParams, + ConsensusParams: consensusParams, AppStateBytes: genesisDoc.AppState, } return &initChainRequest @@ -153,8 +153,8 @@ func (a *AbciClient) UpdateStateFromInit(res *abcitypes.ResponseInitChain) error // if response specified consensus params, update the consensus params, otherwise we keep the ones from the genesis file if res.ConsensusParams != nil { - a.CurState.ConsensusParams = a.CurState.ConsensusParams.Update(res.ConsensusParams) - a.CurState.Version.Consensus.App = a.CurState.ConsensusParams.Version.App + a.CurState.ConsensusParams = types.UpdateConsensusParams(a.CurState.ConsensusParams, res.ConsensusParams) + a.CurState.Version.Consensus.App = a.CurState.ConsensusParams.Version.AppVersion } // to conform with RFC-6962 @@ -299,9 +299,13 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V txs = append(txs, *tx) } - resCheckTx, err := a.SendCheckTx(tx) - if err != nil { - return nil, nil, nil, nil, nil, err + var resCheckTx *abcitypes.ResponseCheckTx + var err error + if tx != nil { + resCheckTx, err = a.SendCheckTx(tx) + if err != nil { + return nil, nil, nil, nil, nil, err + } } // TODO: handle special case where proposer is nil @@ -310,7 +314,7 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V proposerAddress = proposer.Address } - block := a.CurState.MakeBlock(a.CurState.LastBlockHeight+1, txs, a.LastCommit, []types.Evidence{}, proposerAddress) + block, _ := a.CurState.MakeBlock(a.CurState.LastBlockHeight+1, txs, a.LastCommit, []types.Evidence{}, proposerAddress) // override the block time, since we do not actually get votes from peers to median the time out of block.Time = blockTime blockId, err := utils.GetBlockIdFromBlock(block) @@ -493,13 +497,13 @@ func UpdateState( lastHeightParamsChanged := curState.LastHeightConsensusParamsChanged if abciResponses.EndBlock.ConsensusParamUpdates != nil { // NOTE: must not mutate s.ConsensusParams - nextParams = curState.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates) - err := nextParams.ValidateBasic() + nextParams = types.UpdateConsensusParams(curState.ConsensusParams, abciResponses.EndBlock.ConsensusParamUpdates) + err := types.ValidateConsensusParams(nextParams) if err != nil { return curState, fmt.Errorf("error updating consensus params: %v", err) } - curState.Version.Consensus.App = nextParams.Version.App + curState.Version.Consensus.App = nextParams.Version.AppVersion // Change results from this height but only applies to the next height. lastHeightParamsChanged = blockHeader.Height + 1 @@ -528,7 +532,7 @@ func UpdateState( // adapted from https://github.com/cometbft/cometbft/blob/9267594e0a17c01cc4a97b399ada5eaa8a734db5/state/execution.go#L452 func validateValidatorUpdates( abciUpdates []abcitypes.ValidatorUpdate, - params types.ValidatorParams, + params cmttypes.ValidatorParams, ) error { for _, valUpdate := range abciUpdates { if valUpdate.GetPower() < 0 { diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index f844a2f..2539188 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -34,9 +34,9 @@ var Routes = map[string]*rpc.RPCFunc{ "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), "block": rpc.NewRPCFunc(Block, "height", rpc.Cacheable("height")), "consensus_params": rpc.NewRPCFunc(ConsensusParams, "height", rpc.Cacheable("height")), - "header": rpc.NewRPCFunc(Header, "height", rpc.Cacheable("height")), - "commit": rpc.NewRPCFunc(Commit, "height", rpc.Cacheable("height")), - "block_results": rpc.NewRPCFunc(BlockResults, "height", rpc.Cacheable("height")), + // "header": rpc.NewRPCFunc(Header, "height", rpc.Cacheable("height")), // not available in 0.34.x + "commit": rpc.NewRPCFunc(Commit, "height", rpc.Cacheable("height")), + "block_results": rpc.NewRPCFunc(BlockResults, "height", rpc.Cacheable("height")), // // tx broadcast API "broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"), @@ -62,22 +62,22 @@ func getHeight(latestHeight int64, heightPtr *int64) (int64, error) { return latestHeight, nil } -// Header gets block header at a given height. -// If no height is provided, it will fetch the latest header. -// More: https://docs.cometbft.com/v0.37/rpc/#/Info/header -func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, error) { - height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) - if err != nil { - return nil, err - } +// // Header gets block header at a given height. +// // If no height is provided, it will fetch the latest header. +// // More: https://docs.cometbft.com/v0.37/rpc/#/Info/header +// func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, error) { +// height, err := getHeight(abci_client.GlobalClient.LastBlock.Height, heightPtr) +// if err != nil { +// return nil, err +// } - block, err := abci_client.GlobalClient.Storage.GetBlock(height) - if err != nil { - return nil, err - } +// block, err := abci_client.GlobalClient.Storage.GetBlock(height) +// if err != nil { +// return nil, err +// } - return &ctypes.ResultHeader{Header: &block.Header}, nil -} +// return &ctypes.ResultHeader{Header: &block.Header}, nil +// } // Commit gets block commit at a given height. // If no height is provided, it will fetch the commit for the latest block. @@ -179,7 +179,8 @@ func BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadc abci_client.GlobalClient.Logger.Info( "BroadcastTxCommit called", "tx", tx) - return BroadcastTx(&tx) + res, err := BroadcastTx(&tx) + return res, err } // BroadcastTxSync would normally broadcast a transaction and wait until it gets the result from CheckTx. diff --git a/cometmock/utils/blocks.go b/cometmock/utils/blocks.go index 6540254..458cc03 100644 --- a/cometmock/utils/blocks.go +++ b/cometmock/utils/blocks.go @@ -3,10 +3,7 @@ package utils import "github.com/cometbft/cometbft/types" func GetBlockIdFromBlock(block *types.Block) (*types.BlockID, error) { - partSet, error := block.MakePartSet(2) - if error != nil { - return nil, error - } + partSet := block.MakePartSet(2) partSetHeader := partSet.Header() blockID := types.BlockID{ diff --git a/go.mod b/go.mod index 1385c55..aae678c 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/p-offtermatt/CometMock go 1.20 -require github.com/cometbft/cometbft v0.37.1 +require github.com/cometbft/cometbft v0.34.27-alpha.1 require ( github.com/beorn7/perks v1.0.1 // indirect @@ -10,8 +10,6 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cometbft/cometbft-db v0.7.0 // indirect - github.com/cosmos/gogoproto v1.4.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect @@ -20,6 +18,7 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect @@ -30,15 +29,15 @@ require ( github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect + github.com/onsi/ginkgo v1.16.4 // indirect + github.com/onsi/gomega v1.19.0 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/stretchr/testify v1.8.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect go.etcd.io/bbolt v1.3.6 // indirect @@ -49,5 +48,4 @@ require ( google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect google.golang.org/grpc v1.52.0 // indirect google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 76de9d0..7476198 100644 --- a/go.sum +++ b/go.sum @@ -62,16 +62,14 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cometbft/cometbft v0.37.1 h1:KLxkQTK2hICXYq21U2hn1W5hOVYUdQgDQ1uB+90xPIg= -github.com/cometbft/cometbft v0.37.1/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs= +github.com/cometbft/cometbft v0.34.27-alpha.1 h1:GvmlB422TYhj+6a/A7VV33iRxp/F2IT3aiqExc3s2Ew= +github.com/cometbft/cometbft v0.34.27-alpha.1/go.mod h1:hct3hasQ2hIF3HoD7foVw4RaqTNSSeJ/lgcrVK6uDvs= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= -github.com/cosmos/gogoproto v1.4.1 h1:WoyH+0/jbCTzpKNvyav5FL1ZTWsp1im1MxEpJEzKUB8= -github.com/cosmos/gogoproto v1.4.1/go.mod h1:Ac9lzL4vFpBMcptJROQ6dQ4M3pOEK5Z/l0Q9p+LoCr4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -116,7 +114,10 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -197,6 +198,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= @@ -204,10 +206,8 @@ github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -227,15 +227,18 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= @@ -289,16 +292,12 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= @@ -308,6 +307,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -385,6 +385,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -405,6 +406,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -446,7 +448,9 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -506,13 +510,15 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -598,7 +604,6 @@ google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QO gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -611,9 +616,7 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c20dc7e7e19260a1c45598de6b70883ee555aa0f Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 25 May 2023 09:39:25 +0200 Subject: [PATCH 20/26] Add indexers --- cometmock/abci_client/client.go | 12 +- cometmock/main.go | 23 ++++ cometmock/rpc_server/routes.go | 193 ++++++++++++++++++++++++++++++++ go.mod | 7 +- go.sum | 2 + 5 files changed, 233 insertions(+), 4 deletions(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 669fa74..1905cd5 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -14,6 +14,9 @@ import ( cmtstate "github.com/cometbft/cometbft/proto/tendermint/state" cmttypes "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cometbft/cometbft/state" + blockindexkv "github.com/cometbft/cometbft/state/indexer/block/kv" + "github.com/cometbft/cometbft/state/txindex" + indexerkv "github.com/cometbft/cometbft/state/txindex/kv" "github.com/cometbft/cometbft/types" "github.com/p-offtermatt/CometMock/cometmock/storage" "github.com/p-offtermatt/CometMock/cometmock/utils" @@ -35,6 +38,9 @@ type AbciClient struct { LastCommit *types.Commit Storage storage.Storage PrivValidators []types.PrivValidator + IndexerService *txindex.IndexerService + TxIndex *indexerkv.TxIndex + BlockIndex *blockindexkv.BlockerIndexer // if this is true, then an error will be returned if the responses from the clients are not all equal. // can be used to check for nondeterminism in apps, but also slows down execution a bit, @@ -572,12 +578,14 @@ func fireEvents( logger.Error("failed publishing new block", "err", err) } - if err := eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{ + eventDataNewBlockHeader := types.EventDataNewBlockHeader{ Header: block.Header, NumTxs: int64(len(block.Txs)), ResultBeginBlock: *abciResponses.BeginBlock, ResultEndBlock: *abciResponses.EndBlock, - }); err != nil { + } + + if err := eventBus.PublishEventNewBlockHeader(eventDataNewBlockHeader); err != nil { logger.Error("failed publishing new block header", "err", err) } diff --git a/cometmock/main.go b/cometmock/main.go index 0b7ca40..f9f217b 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -5,10 +5,14 @@ import ( "strings" "time" + db "github.com/cometbft/cometbft-db" comet_abciclient "github.com/cometbft/cometbft/abci/client" cometlog "github.com/cometbft/cometbft/libs/log" "github.com/cometbft/cometbft/privval" "github.com/cometbft/cometbft/state" + blockindexkv "github.com/cometbft/cometbft/state/indexer/block/kv" + "github.com/cometbft/cometbft/state/txindex" + indexerkv "github.com/cometbft/cometbft/state/txindex/kv" "github.com/cometbft/cometbft/types" "github.com/p-offtermatt/CometMock/cometmock/abci_client" "github.com/p-offtermatt/CometMock/cometmock/rpc_server" @@ -41,6 +45,16 @@ func GetMockPVsFromNodeHomes(nodeHomes []string) []types.PrivValidator { return mockPVs } +func CreateAndStartIndexerService(eventBus *types.EventBus, logger cometlog.Logger) (*txindex.IndexerService, *indexerkv.TxIndex, *blockindexkv.BlockerIndexer, error) { + txIndexer := indexerkv.NewTxIndex(db.NewMemDB()) + blockIndexer := blockindexkv.New(db.NewMemDB()) + + indexerService := txindex.NewIndexerService(txIndexer, blockIndexer, eventBus, false) + indexerService.SetLogger(logger.With("module", "txindex")) + + return indexerService, txIndexer, blockIndexer, indexerService.Start() +} + func main() { logger := cometlog.NewTMLogger(cometlog.NewSyncWriter(os.Stdout)) @@ -87,6 +101,12 @@ func main() { panic(err) } + indexerService, txIndex, blockIndex, err := CreateAndStartIndexerService(eventBus, logger) + if err != nil { + logger.Error(err.Error()) + panic(err) + } + abci_client.GlobalClient = &abci_client.AbciClient{ Clients: clients, Logger: logger, @@ -96,6 +116,9 @@ func main() { LastCommit: &types.Commit{}, Storage: &storage.MapStorage{}, PrivValidators: privVals, + IndexerService: indexerService, + TxIndex: txIndex, + BlockIndex: blockIndex, } // initialize chain diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index 2539188..d62c74f 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -1,11 +1,14 @@ package rpc_server import ( + "errors" "fmt" + "sort" "time" "github.com/cometbft/cometbft/libs/bytes" cmtmath "github.com/cometbft/cometbft/libs/math" + cmtquery "github.com/cometbft/cometbft/libs/pubsub/query" "github.com/cometbft/cometbft/p2p" cometp2p "github.com/cometbft/cometbft/p2p" ctypes "github.com/cometbft/cometbft/rpc/core/types" @@ -37,6 +40,9 @@ var Routes = map[string]*rpc.RPCFunc{ // "header": rpc.NewRPCFunc(Header, "height", rpc.Cacheable("height")), // not available in 0.34.x "commit": rpc.NewRPCFunc(Commit, "height", rpc.Cacheable("height")), "block_results": rpc.NewRPCFunc(BlockResults, "height", rpc.Cacheable("height")), + "tx": rpc.NewRPCFunc(Tx, "hash,prove", rpc.Cacheable()), + "tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page,order_by"), + "block_search": rpc.NewRPCFunc(BlockSearch, "query,page,per_page,order_by"), // // tx broadcast API "broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"), @@ -47,6 +53,193 @@ var Routes = map[string]*rpc.RPCFunc{ "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"), } +// BlockSearch searches for a paginated set of blocks matching BeginBlock and +// EndBlock event search criteria. +func BlockSearch( + ctx *rpctypes.Context, + query string, + pagePtr, perPagePtr *int, + orderBy string, +) (*ctypes.ResultBlockSearch, error) { + q, err := cmtquery.New(query) + if err != nil { + return nil, err + } + + results, err := abci_client.GlobalClient.BlockIndex.Search(ctx.Context(), q) + if err != nil { + return nil, err + } + + // sort results (must be done before pagination) + switch orderBy { + case "desc", "": + sort.Slice(results, func(i, j int) bool { return results[i] > results[j] }) + + case "asc": + sort.Slice(results, func(i, j int) bool { return results[i] < results[j] }) + + default: + return nil, errors.New("expected order_by to be either `asc` or `desc` or empty") + } + + // paginate results + totalCount := len(results) + perPage := validatePerPage(perPagePtr) + + page, err := validatePage(pagePtr, perPage, totalCount) + if err != nil { + return nil, err + } + + skipCount := validateSkipCount(page, perPage) + pageSize := cmtmath.MinInt(perPage, totalCount-skipCount) + + apiResults := make([]*ctypes.ResultBlock, 0, pageSize) + for i := skipCount; i < skipCount+pageSize; i++ { + block, err := abci_client.GlobalClient.Storage.GetBlock(results[i]) + if err != nil { + return nil, err + } + if block != nil { + if err != nil { + return nil, err + } + blockId, err := utils.GetBlockIdFromBlock(block) + if err != nil { + return nil, err + } + + apiResults = append(apiResults, &ctypes.ResultBlock{ + Block: block, + BlockID: *blockId, + }) + } + } + + return &ctypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil +} + +// Tx allows you to query the transaction results. `nil` could mean the +// transaction is in the mempool, invalidated, or was not sent in the first +// place. +// More: https://docs.tendermint.com/v0.34/rpc/#/Info/tx +func Tx(ctx *rpctypes.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) { + txIndexer := abci_client.GlobalClient.TxIndex + + r, err := txIndexer.Get(hash) + if err != nil { + return nil, err + } + + if r == nil { + return nil, fmt.Errorf("tx (%X) not found", hash) + } + + height := r.Height + index := r.Index + + var proof types.TxProof + if prove { + block, err := abci_client.GlobalClient.Storage.GetBlock(height) + if err != nil { + return nil, err + } + proof = block.Data.Txs.Proof(int(index)) // XXX: overflow on 32-bit machines + } + + return &ctypes.ResultTx{ + Hash: hash, + Height: height, + Index: index, + TxResult: r.Result, + Tx: r.Tx, + Proof: proof, + }, nil +} + +// TxSearch allows you to query for multiple transactions results. It returns a +// list of transactions (maximum ?per_page entries) and the total count. +// More: https://docs.tendermint.com/v0.34/rpc/#/Info/tx_search +func TxSearch( + ctx *rpctypes.Context, + query string, + prove bool, + pagePtr, perPagePtr *int, + orderBy string, +) (*ctypes.ResultTxSearch, error) { + if len(query) > maxQueryLength { + return nil, errors.New("maximum query length exceeded") + } + + q, err := cmtquery.New(query) + if err != nil { + return nil, err + } + + results, err := abci_client.GlobalClient.TxIndex.Search(ctx.Context(), q) + if err != nil { + return nil, err + } + + // sort results (must be done before pagination) + switch orderBy { + case "desc": + sort.Slice(results, func(i, j int) bool { + if results[i].Height == results[j].Height { + return results[i].Index > results[j].Index + } + return results[i].Height > results[j].Height + }) + case "asc", "": + sort.Slice(results, func(i, j int) bool { + if results[i].Height == results[j].Height { + return results[i].Index < results[j].Index + } + return results[i].Height < results[j].Height + }) + default: + return nil, errors.New("expected order_by to be either `asc` or `desc` or empty") + } + + // paginate results + totalCount := len(results) + perPage := validatePerPage(perPagePtr) + + page, err := validatePage(pagePtr, perPage, totalCount) + if err != nil { + return nil, err + } + + skipCount := validateSkipCount(page, perPage) + pageSize := cmtmath.MinInt(perPage, totalCount-skipCount) + + apiResults := make([]*ctypes.ResultTx, 0, pageSize) + for i := skipCount; i < skipCount+pageSize; i++ { + r := results[i] + + var proof types.TxProof + if prove { + block, err := abci_client.GlobalClient.Storage.GetBlock(r.Height) + if err != nil { + return nil, err + } + proof = block.Data.Txs.Proof(int(r.Index)) // XXX: overflow on 32-bit machines + } + + apiResults = append(apiResults, &ctypes.ResultTx{ + Hash: types.Tx(r.Tx).Hash(), + Height: r.Height, + Index: r.Index, + TxResult: r.Result, + Tx: r.Tx, + Proof: proof, + }) + } + + return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil +} + func getHeight(latestHeight int64, heightPtr *int64) (int64, error) { if heightPtr != nil { height := *heightPtr diff --git a/go.mod b/go.mod index aae678c..49a5b73 100644 --- a/go.mod +++ b/go.mod @@ -2,14 +2,16 @@ module github.com/p-offtermatt/CometMock go 1.20 -require github.com/cometbft/cometbft v0.34.27-alpha.1 +require ( + github.com/cometbft/cometbft v0.34.27-alpha.1 + github.com/cometbft/cometbft-db v0.7.0 +) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/cometbft/cometbft-db v0.7.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect @@ -22,6 +24,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/jmhodges/levigo v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7476198..f2560b7 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= From 0d26b164580cf729627a6b0d0f1fbb05fa2d4e59 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 25 May 2023 14:23:13 +0200 Subject: [PATCH 21/26] Fix signing --- cometmock/abci_client/client.go | 41 ++++++++++++++++++++++----------- cometmock/main.go | 20 ++++++++++++++-- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 1905cd5..12dabe1 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -11,6 +11,7 @@ import ( cryptoenc "github.com/cometbft/cometbft/crypto/encoding" "github.com/cometbft/cometbft/crypto/merkle" cometlog "github.com/cometbft/cometbft/libs/log" + cmtmath "github.com/cometbft/cometbft/libs/math" cmtstate "github.com/cometbft/cometbft/proto/tendermint/state" cmttypes "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cometbft/cometbft/state" @@ -37,7 +38,7 @@ type AbciClient struct { LastBlock *types.Block LastCommit *types.Commit Storage storage.Storage - PrivValidators []types.PrivValidator + PrivValidators map[string]types.PrivValidator IndexerService *txindex.IndexerService TxIndex *indexerkv.TxIndex BlockIndex *blockindexkv.BlockerIndexer @@ -375,23 +376,30 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V commitSigs := []types.CommitSig{} - precommitType := cmttypes.SignedMsgType_value["SIGNED_MSG_TYPE_PRECOMMIT"] - for index, pv := range a.PrivValidators { + for index, val := range a.CurState.Validators.Validators { + privVal := a.PrivValidators[val.Address.String()] + // create and sign a precommit - vote, err := utils.MakeVote( - pv, - a.CurState.ChainID, - int32(index), - block.Height, - 2, // round to consensus - can be arbitrary - int(precommitType), // for which step the vote is - we use precommit - *blockId, - time.Now(), - ) + vote := &cmttypes.Vote{ + ValidatorAddress: val.Address, + ValidatorIndex: int32(index), + Height: a.LastBlock.Height, + Round: 1, + Timestamp: time.Now(), + Type: cmttypes.PrecommitType, + BlockID: blockId.ToProto(), + } + + privVal.SignVote(a.CurState.ChainID, vote) + + convertedVote, err := types.VoteFromProto(vote) if err != nil { return nil, nil, nil, nil, nil, err } - commitSigs = append(commitSigs, vote.CommitSig()) + + commitSig := convertedVote.CommitSig() + + commitSigs = append(commitSigs, commitSig) } a.LastCommit = types.NewCommit( @@ -401,6 +409,11 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V commitSigs, ) + err = a.CurState.Validators.VerifyCommitLightTrusting(a.CurState.ChainID, a.LastCommit, cmtmath.Fraction{Numerator: 1, Denominator: 3}) + if err != nil { + return nil, nil, nil, nil, nil, err + } + err = a.Storage.InsertBlock(newHeight, block) if err != nil { return nil, nil, nil, nil, nil, err diff --git a/cometmock/main.go b/cometmock/main.go index f9f217b..2216869 100644 --- a/cometmock/main.go +++ b/cometmock/main.go @@ -107,6 +107,18 @@ func main() { panic(err) } + privValsMap := make(map[string]types.PrivValidator) + for _, privVal := range privVals { + pubkey, err := privVal.GetPubKey() + if err != nil { + logger.Error(err.Error()) + panic(err) + } + addr := pubkey.Address() + + privValsMap[addr.String()] = privVal + } + abci_client.GlobalClient = &abci_client.AbciClient{ Clients: clients, Logger: logger, @@ -115,7 +127,7 @@ func main() { EventBus: *eventBus, LastCommit: &types.Commit{}, Storage: &storage.MapStorage{}, - PrivValidators: privVals, + PrivValidators: privValsMap, IndexerService: indexerService, TxIndex: txIndex, BlockIndex: blockIndex, @@ -129,7 +141,11 @@ func main() { } // run an empty block - abci_client.GlobalClient.RunBlock(nil, time.Now(), abci_client.GlobalClient.CurState.LastValidators.Proposer) + _, _, _, _, _, err = abci_client.GlobalClient.RunBlock(nil, time.Now(), abci_client.GlobalClient.CurState.LastValidators.Proposer) + if err != nil { + logger.Error(err.Error()) + panic(err) + } go rpc_server.StartRPCServerWithDefaultConfig(cometMockListenAddress, logger) From 0af84efcd372fea5a7cbbc5275fd1dd2f3df191b Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 25 May 2023 14:26:50 +0200 Subject: [PATCH 22/26] Add comment about commit verification --- cometmock/abci_client/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 12dabe1..c7d7853 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -409,6 +409,7 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V commitSigs, ) + // sanity check that the commit is signed correctly err = a.CurState.Validators.VerifyCommitLightTrusting(a.CurState.ChainID, a.LastCommit, cmtmath.Fraction{Numerator: 1, Denominator: 3}) if err != nil { return nil, nil, nil, nil, nil, err From b292a364e8ce9be0813aff3ba6563cce4327e998 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 31 May 2023 15:07:03 +0200 Subject: [PATCH 23/26] Add light block verification --- cometmock/abci_client/client.go | 14 ++++++++++++++ cometmock/rpc_server/routes.go | 3 +++ 2 files changed, 17 insertions(+) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index c7d7853..91e2516 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -415,6 +415,20 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V return nil, nil, nil, nil, nil, err } + // sanity check that the commit makes a proper light block + signedHeader := types.SignedHeader{ + Header: &block.Header, + Commit: a.LastCommit, + } + + lightBlock := types.LightBlock{ + SignedHeader: &signedHeader, + ValidatorSet: a.CurState.Validators, + } + + lightBlock.ValidateBasic(a.CurState.ChainID) + + // insert entries into the storage err = a.Storage.InsertBlock(newHeight, block) if err != nil { return nil, nil, nil, nil, nil, err diff --git a/cometmock/rpc_server/routes.go b/cometmock/rpc_server/routes.go index d62c74f..83beed3 100644 --- a/cometmock/rpc_server/routes.go +++ b/cometmock/rpc_server/routes.go @@ -444,6 +444,9 @@ func ABCIQuery( "ABCIQuery called", "path", "data", "height", "prove", path, data, height, prove) response, err := abci_client.GlobalClient.SendAbciQuery(data, path, height, prove) + + abci_client.GlobalClient.Logger.Info( + "Response to ABCI query", response.String()) return &ctypes.ResultABCIQuery{Response: *response}, err } From 4eea082206ba1db7cad3eaaba04867a632bf4fed Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 31 May 2023 15:48:02 +0200 Subject: [PATCH 24/26] Put block validation before abci calls --- cometmock/abci_client/client.go | 100 +++++++++++++++++--------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 91e2516..918d800 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -329,49 +329,6 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V return nil, nil, nil, nil, nil, err } - resBeginBlock, err := a.SendBeginBlock(block) - if err != nil { - return nil, nil, nil, nil, nil, err - } - - var resDeliverTx *abcitypes.ResponseDeliverTx - if tx != nil { - resDeliverTx, err = a.SendDeliverTx(tx) - if err != nil { - return nil, nil, nil, nil, nil, err - } - } else { - resDeliverTx = nil - } - - resEndBlock, err := a.SendEndBlock() - if err != nil { - return nil, nil, nil, nil, nil, err - } - - resCommit, err := a.SendCommit() - if err != nil { - return nil, nil, nil, nil, nil, err - } - - deliverTxResponses := []*abcitypes.ResponseDeliverTx{} - if tx != nil { - deliverTxResponses = append(deliverTxResponses, resDeliverTx) - } - - // build components of the state update, then call the update function - abciResponses := cmtstate.ABCIResponses{ - DeliverTxs: deliverTxResponses, - EndBlock: resEndBlock, - BeginBlock: resBeginBlock, - } - - // updates state as a side effect. returns an error if the state update fails - err = a.UpdateStateFromBlock(blockId, block, abciResponses, resCommit) - if err != nil { - return nil, nil, nil, nil, nil, err - } - a.LastBlock = block commitSigs := []types.CommitSig{} @@ -426,7 +383,56 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V ValidatorSet: a.CurState.Validators, } - lightBlock.ValidateBasic(a.CurState.ChainID) + err = lightBlock.ValidateBasic(a.CurState.ChainID) + if err != nil { + a.Logger.Error("Light block validation failed", "err", err) + return nil, nil, nil, nil, nil, err + } + + resBeginBlock, err := a.SendBeginBlock(block) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + var resDeliverTx *abcitypes.ResponseDeliverTx + if tx != nil { + resDeliverTx, err = a.SendDeliverTx(tx) + if err != nil { + return nil, nil, nil, nil, nil, err + } + } else { + resDeliverTx = nil + } + + resEndBlock, err := a.SendEndBlock() + if err != nil { + return nil, nil, nil, nil, nil, err + } + + deliverTxResponses := []*abcitypes.ResponseDeliverTx{} + if tx != nil { + deliverTxResponses = append(deliverTxResponses, resDeliverTx) + } + + // build components of the state update, then call the update function + abciResponses := cmtstate.ABCIResponses{ + DeliverTxs: deliverTxResponses, + EndBlock: resEndBlock, + BeginBlock: resBeginBlock, + } + + // updates state as a side effect. returns an error if the state update fails + err = a.UpdateStateFromBlock(blockId, block, abciResponses) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + resCommit, err := a.SendCommit() + if err != nil { + return nil, nil, nil, nil, nil, err + } + + a.CurState.AppHash = resCommit.Data // insert entries into the storage err = a.Storage.InsertBlock(newHeight, block) @@ -464,7 +470,6 @@ func (a *AbciClient) UpdateStateFromBlock( blockId *types.BlockID, block *types.Block, abciResponses cmtstate.ABCIResponses, - commitResponse *abcitypes.ResponseCommit, ) error { // build components of the state update, then call the update function abciValidatorUpdates := abciResponses.EndBlock.ValidatorUpdates @@ -483,7 +488,6 @@ func (a *AbciClient) UpdateStateFromBlock( blockId, &block.Header, &abciResponses, - commitResponse, validatorUpdates, ) if err != nil { @@ -505,7 +509,6 @@ func UpdateState( blockId *types.BlockID, blockHeader *types.Header, abciResponses *cmtstate.ABCIResponses, - commitResponse *abcitypes.ResponseCommit, validatorUpdates []*types.Validator, ) (state.State, error) { // Copy the valset so we can apply changes from EndBlock @@ -559,7 +562,8 @@ func UpdateState( ConsensusParams: nextParams, LastHeightConsensusParamsChanged: lastHeightParamsChanged, LastResultsHash: state.ABCIResponsesResultsHash(abciResponses), - AppHash: commitResponse.Data, + // app hash will be populated after commit + AppHash: nil, }, nil } From 151c50c00a64d370fd3213cd82453f53ab6d493b Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 31 May 2023 15:48:52 +0200 Subject: [PATCH 25/26] Use block instead of a.LastBlock to be clearer --- cometmock/abci_client/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index 918d800..f8ff586 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -340,7 +340,7 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V vote := &cmttypes.Vote{ ValidatorAddress: val.Address, ValidatorIndex: int32(index), - Height: a.LastBlock.Height, + Height: block.Height, Round: 1, Timestamp: time.Now(), Type: cmttypes.PrecommitType, From a5ddcee2faddfec0f02f8fc7b854f7ed48dea573 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 1 Jun 2023 10:45:57 +0200 Subject: [PATCH 26/26] Fix storing right state for right height --- cometmock/abci_client/client.go | 52 ++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/cometmock/abci_client/client.go b/cometmock/abci_client/client.go index f8ff586..8261162 100644 --- a/cometmock/abci_client/client.go +++ b/cometmock/abci_client/client.go @@ -28,6 +28,8 @@ var GlobalClient *AbciClient // store a mutex that allows only running one block at a time var blockMutex = sync.Mutex{} +var verbose = false + // AbciClient facilitates calls to the ABCI interface of multiple nodes. // It also tracks the current state and a common logger. type AbciClient struct { @@ -50,7 +52,9 @@ type AbciClient struct { } func (a *AbciClient) SendBeginBlock(block *types.Block) (*abcitypes.ResponseBeginBlock, error) { - a.Logger.Info("Sending BeginBlock to clients") + if verbose { + a.Logger.Info("Sending BeginBlock to clients") + } // build the BeginBlock request beginBlockRequest := CreateBeginBlockRequest(&block.Header, block.LastCommit) @@ -85,7 +89,9 @@ func CreateBeginBlockRequest(header *types.Header, lastCommit *types.Commit) *ab } func (a *AbciClient) SendInitChain(genesisState state.State, genesisDoc *types.GenesisDoc) error { - a.Logger.Info("Sending InitChain to clients") + if verbose { + a.Logger.Info("Sending InitChain to clients") + } // build the InitChain request initChainRequest := CreateInitChainRequest(genesisState, genesisDoc) @@ -171,7 +177,9 @@ func (a *AbciClient) UpdateStateFromInit(res *abcitypes.ResponseInitChain) error } func (a *AbciClient) SendEndBlock() (*abcitypes.ResponseEndBlock, error) { - a.Logger.Info("Sending EndBlock to clients") + if verbose { + a.Logger.Info("Sending EndBlock to clients") + } // build the EndBlock request endBlockRequest := abcitypes.RequestEndBlock{ Height: a.CurState.LastBlockHeight + 1, @@ -297,7 +305,9 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V blockMutex.Lock() a.Logger.Info("Running block") - a.Logger.Info("State at start of block", "state", a.CurState) + if verbose { + a.Logger.Info("State at start of block", "state", a.CurState) + } newHeight := a.CurState.LastBlockHeight + 1 @@ -414,46 +424,48 @@ func (a *AbciClient) RunBlock(tx *[]byte, blockTime time.Time, proposer *types.V deliverTxResponses = append(deliverTxResponses, resDeliverTx) } - // build components of the state update, then call the update function - abciResponses := cmtstate.ABCIResponses{ - DeliverTxs: deliverTxResponses, - EndBlock: resEndBlock, - BeginBlock: resBeginBlock, - } - - // updates state as a side effect. returns an error if the state update fails - err = a.UpdateStateFromBlock(blockId, block, abciResponses) + // insert entries into the storage + err = a.Storage.InsertBlock(newHeight, block) if err != nil { return nil, nil, nil, nil, nil, err } - resCommit, err := a.SendCommit() + err = a.Storage.InsertCommit(newHeight, a.LastCommit) if err != nil { return nil, nil, nil, nil, nil, err } - a.CurState.AppHash = resCommit.Data + // copy state so that the historical state is not mutated + state := a.CurState.Copy() - // insert entries into the storage - err = a.Storage.InsertBlock(newHeight, block) + err = a.Storage.InsertState(newHeight, &state) if err != nil { return nil, nil, nil, nil, nil, err } - err = a.Storage.InsertCommit(newHeight, a.LastCommit) + // build components of the state update, then call the update function + abciResponses := cmtstate.ABCIResponses{ + DeliverTxs: deliverTxResponses, + EndBlock: resEndBlock, + BeginBlock: resBeginBlock, + } + + err = a.Storage.InsertResponses(newHeight, &abciResponses) if err != nil { return nil, nil, nil, nil, nil, err } - err = a.Storage.InsertState(newHeight, &a.CurState) + // updates state as a side effect. returns an error if the state update fails + err = a.UpdateStateFromBlock(blockId, block, abciResponses) if err != nil { return nil, nil, nil, nil, nil, err } - err = a.Storage.InsertResponses(newHeight, &abciResponses) + resCommit, err := a.SendCommit() if err != nil { return nil, nil, nil, nil, nil, err } + a.CurState.AppHash = resCommit.Data // unlock mutex blockMutex.Unlock()