diff --git a/app/node/build.go b/app/node/build.go index 59e4d8e89..12f67d605 100644 --- a/app/node/build.go +++ b/app/node/build.go @@ -34,6 +34,7 @@ import ( rpcserver "github.com/kwilteam/kwil-db/node/services/jsonrpc" "github.com/kwilteam/kwil-db/node/services/jsonrpc/adminsvc" + "github.com/kwilteam/kwil-db/node/services/jsonrpc/chainsvc" "github.com/kwilteam/kwil-db/node/services/jsonrpc/funcsvc" "github.com/kwilteam/kwil-db/node/services/jsonrpc/usersvc" ) @@ -94,7 +95,7 @@ func buildServer(ctx context.Context, d *coreDependencies) *server { usersvc.WithPrivateMode(d.cfg.RPC.Private), usersvc.WithChallengeExpiry(d.cfg.RPC.ChallengeExpiry), usersvc.WithChallengeRateLimit(d.cfg.RPC.ChallengeRateLimit), - // usersvc.WithBlockAgeHealth(6*totalConsensusTimeouts.Dur()), + // usersvc.WithBlockAgeHealth(6*totalConsensusTimeouts.Dur()), ) rpcServerLogger := d.logger.New("RPC") @@ -125,6 +126,10 @@ func buildServer(ctx context.Context, d *coreDependencies) *server { jsonRPCAdminServer.RegisterSvc(&funcsvc.Service{}) } + chainRpcSvcLogger := d.logger.New("CHAIN") + jsonChainSvc := chainsvc.NewService(chainRpcSvcLogger, node, d.genesisCfg) + jsonRPCServer.RegisterSvc(jsonChainSvc) + s := &server{ cfg: d.cfg, closers: closers, diff --git a/config/config.go b/config/config.go index e3afe42fe..569707651 100644 --- a/config/config.go +++ b/config/config.go @@ -6,8 +6,7 @@ import ( "time" "github.com/kwilteam/kwil-db/core/log" - ktypes "github.com/kwilteam/kwil-db/core/types" - "github.com/kwilteam/kwil-db/node/types" + "github.com/kwilteam/kwil-db/core/types" "github.com/pelletier/go-toml/v2" ) @@ -41,7 +40,7 @@ type GenesisConfig struct { // Leader is the leader's public key. Leader types.HexBytes `json:"leader"` // Validators is the list of genesis validators (including the leader). - Validators []*ktypes.Validator `json:"validators"` + Validators []*types.Validator `json:"validators"` // MaxBlockSize is the maximum size of a block in bytes. MaxBlockSize int64 `json:"max_block_size"` @@ -89,7 +88,7 @@ func DefaultGenesisConfig() *GenesisConfig { ChainID: "kwil-test-chain", InitialHeight: 0, Leader: types.HexBytes{}, - Validators: []*ktypes.Validator{}, + Validators: []*types.Validator{}, DisabledGasCosts: true, JoinExpiry: 14400, VoteExpiry: 108000, diff --git a/contrib/docker/compose/postgres/docker-compose.yml b/contrib/docker/compose/postgres/docker-compose.yml index 94d5e7a00..16840b200 100644 --- a/contrib/docker/compose/postgres/docker-compose.yml +++ b/contrib/docker/compose/postgres/docker-compose.yml @@ -12,9 +12,10 @@ services: # POSTGRES_USER: kwild # POSTGRES_PASSWORD: kwild # POSTGRES_DB: kwild - volumes: + volumes: - kwildb:/var/lib/postgresql/data volumes: kwildb: driver: local + diff --git a/core/rpc/json/chain/commands.go b/core/rpc/json/chain/commands.go new file mode 100644 index 000000000..433700026 --- /dev/null +++ b/core/rpc/json/chain/commands.go @@ -0,0 +1,31 @@ +package chain + +import ( + "github.com/kwilteam/kwil-db/core/types" +) + +type HealthRequest struct{} + +type BlockRequest struct { + Height int64 `json:"height"` + // Hash is the block hash. If both Height and Hash are provided, hash will be used + Hash types.Hash `json:"hash"` +} + +type BlockResultRequest struct { + Height int64 `json:"height"` + // Hash is the block hash. If both Height and Hash are provided, hash will be used + Hash types.Hash `json:"hash"` +} + +type TxRequest struct { + Hash types.Hash `json:"hash"` +} + +type GenesisRequest struct{} +type ValidatorsRequest struct { + Height int64 `json:"height"` +} +type UnconfirmedTxsRequest struct { + Limit int `json:"limit"` +} diff --git a/core/rpc/json/chain/methods.go b/core/rpc/json/chain/methods.go new file mode 100644 index 000000000..4d10844b6 --- /dev/null +++ b/core/rpc/json/chain/methods.go @@ -0,0 +1,16 @@ +package chain + +import ( + jsonrpc "github.com/kwilteam/kwil-db/core/rpc/json" +) + +const ( + MethodVersion jsonrpc.Method = "chain.version" + MethodHealth jsonrpc.Method = "chain.health" + MethodBlock jsonrpc.Method = "chain.block" + MethodBlockResult jsonrpc.Method = "chain.block_result" + MethodTx jsonrpc.Method = "chain.tx" + MethodGenesis jsonrpc.Method = "chain.genesis" + MethodValidators jsonrpc.Method = "chain.validators" + MethodUnconfirmedTxs jsonrpc.Method = "chain.unconfirmed_txs" +) diff --git a/core/rpc/json/chain/responses.go b/core/rpc/json/chain/responses.go new file mode 100644 index 000000000..29a476b76 --- /dev/null +++ b/core/rpc/json/chain/responses.go @@ -0,0 +1,87 @@ +package chain + +import ( + "encoding/json" + "github.com/kwilteam/kwil-db/core/types" +) + +// HealthResponse is the health check response. +type HealthResponse struct { + ChainID string `json:"chain_id"` + Height int64 `json:"height"` + Healthy bool `json:"healthy"` +} + +type BlockHeader types.BlockHeader + +func (b BlockHeader) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Version uint16 `json:"version"` + Height int64 `json:"height"` + NumTxns uint32 `json:"num_txns"` + PrevHash types.Hash `json:"prev_hash"` + PrevAppHash types.Hash `json:"prev_app_hash"` + // Timestamp is the unix millisecond timestamp + Timestamp int64 `json:"timestamp"` + MerkleRoot types.Hash `json:"merkle_root"` + ValidatorSetHash types.Hash `json:"validator_set_hash"` + }{ + Version: b.Version, + Height: b.Height, + NumTxns: b.NumTxns, + PrevHash: b.PrevHash, + PrevAppHash: b.PrevAppHash, + Timestamp: b.Timestamp.UnixMilli(), + MerkleRoot: b.MerkleRoot, + ValidatorSetHash: b.ValidatorSetHash, + }) +} + +// BlockResponse is the block information +type BlockResponse struct { + Header *BlockHeader `json:"header"` + Txns [][]byte `json:"txns"` + Signature []byte `json:"signature"` + Hash types.Hash `json:"hash"` + AppHash types.Hash `json:"app_hash"` +} + +type BlockResultResponse struct { + Height int64 `json:"height"` + TxResults []types.TxResult `json:"tx_results"` +} + +type GenesisResponse struct { + ChainID string `json:"chain_id"` + // Leader is the leader's public key. + Leader types.HexBytes `json:"leader"` + // Validators is the list of genesis validators (including the leader). + Validators []*types.Validator `json:"validators"` + // MaxBlockSize is the maximum size of a block in bytes. + MaxBlockSize int64 `json:"max_block_size"` + // JoinExpiry is the number of blocks after which the validators + // join request expires if not approved. + JoinExpiry int64 `json:"join_expiry"` + // VoteExpiry is the default number of blocks after which the validators + // vote expires if not approved. + VoteExpiry int64 `json:"vote_expiry"` + // DisabledGasCosts dictates whether gas costs are disabled. + DisabledGasCosts bool `json:"disabled_gas_costs"` + // MaxVotesPerTx is the maximum number of votes that can be included in a + // single transaction. + MaxVotesPerTx int64 `json:"max_votes_per_tx"` +} +type ValidatorsResponse struct { + Height int64 `json:"height"` + Validators []*types.Validator `json:"validators"` +} + +type NamedTx struct { + Hash types.Hash `json:"hash"` + Tx []byte `json:"tx"` +} + +type UnconfirmedTxsResponse struct { + Total int `json:"total"` + Txs []NamedTx `json:"txs"` +} diff --git a/core/types/chain/types.go b/core/types/chain/types.go new file mode 100644 index 000000000..11a3e5bb3 --- /dev/null +++ b/core/types/chain/types.go @@ -0,0 +1,12 @@ +// Package types contains the type used by the chain stat RPC client and servers. +package types + +import "github.com/kwilteam/kwil-db/core/types" + +type ChainTx struct { + Hash types.Hash `json:"hash"` + Height int64 `json:"height"` + Index uint32 `json:"index"` + Tx []byte `json:"tx"` + TxResult *types.TxResult `json:"tx_result"` +} diff --git a/node/block.go b/node/block.go index d41f22591..51272ee1d 100644 --- a/node/block.go +++ b/node/block.go @@ -299,3 +299,22 @@ func (n *Node) getBlkHeight(ctx context.Context, height int64) (types.Hash, type } return types.Hash{}, types.Hash{}, nil, ErrBlkNotFound } + +// BlockByHeight returns the block by height. If height <= 0, the latest block +// will be returned. +func (n *Node) BlockByHeight(_ context.Context, height int64) (types.Hash, *ktypes.Block, types.Hash, error) { + if height <= 0 { // I think this is correct since block height starts from 1 + height, _, _ = n.bki.Best() + } + return n.bki.GetByHeight(height) +} + +// BlockByHash returns the block by block hash. +func (n *Node) BlockByHash(_ context.Context, hash types.Hash) (*ktypes.Block, types.Hash, error) { + return n.bki.Get(hash) +} + +// BlockResultByHash returns the block result by block hash. +func (n *Node) BlockResultByHash(_ context.Context, hash types.Hash) ([]ktypes.TxResult, error) { + return n.bki.Results(hash) +} diff --git a/node/node.go b/node/node.go index 109fb9651..f3b37c4fa 100644 --- a/node/node.go +++ b/node/node.go @@ -22,6 +22,7 @@ import ( "github.com/kwilteam/kwil-db/core/log" ktypes "github.com/kwilteam/kwil-db/core/types" adminTypes "github.com/kwilteam/kwil-db/core/types/admin" + chainTypes "github.com/kwilteam/kwil-db/core/types/chain" "github.com/kwilteam/kwil-db/node/peers" "github.com/kwilteam/kwil-db/node/types" @@ -515,6 +516,43 @@ func (n *Node) BroadcastTx(ctx context.Context, tx *ktypes.Transaction, _ /*sync }, nil } +// ChainTx return tx info that is used in Chain rpc. +func (n *Node) ChainTx(_ context.Context, hash types.Hash) (*chainTypes.ChainTx, error) { + raw, height, blkHash, blkIdx, err := n.bki.GetTx(hash) + if err != nil { + return nil, err + } + blkResults, err := n.bki.Results(blkHash) + if err != nil { + return nil, err + } + if int(blkIdx) >= len(blkResults) { + return nil, errors.New("invalid block index") + } + res := blkResults[blkIdx] + return &chainTypes.ChainTx{ + Hash: hash, + Height: height, + Index: blkIdx, + Tx: raw, + TxResult: &res, + }, nil +} + +// ChainUnconfirmedTx return unconfirmed tx info that is used in Chain rpc. +func (n *Node) ChainUnconfirmedTx(_ context.Context, limit int) (int, []types.NamedTx) { + total := n.mp.Size() + if limit <= 0 { + return total, nil + } + return n.mp.Size(), n.mp.PeekN(limit) +} + +func (n *Node) BlockHeight(ctx context.Context) int64 { + height, _, _ := n.bki.Best() + return height +} + var RequiredStreamProtocols = []protocol.ID{ ProtocolIDDiscover, ProtocolIDTx, diff --git a/node/services/jsonrpc/chainsvc/service.go b/node/services/jsonrpc/chainsvc/service.go new file mode 100644 index 000000000..a7234a6fa --- /dev/null +++ b/node/services/jsonrpc/chainsvc/service.go @@ -0,0 +1,297 @@ +package chainsvc + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/kwilteam/kwil-db/config" + "github.com/kwilteam/kwil-db/core/log" + jsonrpc "github.com/kwilteam/kwil-db/core/rpc/json" + chainjson "github.com/kwilteam/kwil-db/core/rpc/json/chain" + userjson "github.com/kwilteam/kwil-db/core/rpc/json/user" + ktypes "github.com/kwilteam/kwil-db/core/types" + chaintypes "github.com/kwilteam/kwil-db/core/types/chain" + rpcserver "github.com/kwilteam/kwil-db/node/services/jsonrpc" + nodetypes "github.com/kwilteam/kwil-db/node/types" + "github.com/kwilteam/kwil-db/version" +) + +const ( + apiVerMajor = 0 + apiVerMinor = 1 // should it be 2 ? + apiVerPatch = 0 + + serviceName = "chain" +) + +// API version log +// +// apiVerMinor = 1 initial api + +var ( + apiSemver = fmt.Sprintf("%d.%d.%d", apiVerMajor, apiVerMinor, apiVerPatch) +) + +// Node specifies the methods required for chain service to interact with the blockchain. +type Node interface { + // BlockByHeight returns block info at height. If height=0, the latest block will be returned. + BlockByHeight(ctx context.Context, height int64) (ktypes.Hash, *ktypes.Block, ktypes.Hash, error) + BlockByHash(ctx context.Context, hash ktypes.Hash) (*ktypes.Block, ktypes.Hash, error) + BlockResultByHash(ctx context.Context, hash ktypes.Hash) ([]ktypes.TxResult, error) + ChainTx(ctx context.Context, hash ktypes.Hash) (*chaintypes.ChainTx, error) + BlockHeight(ctx context.Context) int64 + ChainUnconfirmedTx(ctx context.Context, limit int) (int, []nodetypes.NamedTx) +} + +type Validators interface { + GetValidators() []*ktypes.Validator +} + +type Service struct { + log log.Logger + genesisCfg *config.GenesisConfig + voting Validators + blockchain Node // node is the local node that can accept transactions. +} + +func NewService(log log.Logger, blockchain Node, genesisCfg *config.GenesisConfig) *Service { + return &Service{ + log: log, + genesisCfg: genesisCfg, + //voting: voting, // TODO + blockchain: blockchain, + } +} + +func (svc *Service) Name() string { + return serviceName +} + +func (svc *Service) Health(ctx context.Context) (detail json.RawMessage, happy bool) { + healthResp, jsonErr := svc.HealthMethod(ctx, &chainjson.HealthRequest{}) + if jsonErr != nil { // unable to even perform the health check + // This is not for a JSON-RPC client. + svc.log.Error("health check failure", "error", jsonErr) + resp, _ := json.Marshal(struct { + Healthy bool `json:"healthy"` + }{}) // omit everything else since + return resp, false + } + + resp, _ := json.Marshal(healthResp) + + return resp, healthResp.Healthy +} + +func (svc *Service) Methods() map[jsonrpc.Method]rpcserver.MethodDef { + return map[jsonrpc.Method]rpcserver.MethodDef{ + chainjson.MethodVersion: rpcserver.MakeMethodDef(verHandler, + "retrieve the API version of the chain service", + "service info including semver and kwild version"), + chainjson.MethodHealth: rpcserver.MakeMethodDef(svc.HealthMethod, + "retrieve the health status of the chain service", + "health status of the service"), + chainjson.MethodBlock: rpcserver.MakeMethodDef(svc.Block, + "retrieve certain block info", + "block information at a certain height"), + chainjson.MethodBlockResult: rpcserver.MakeMethodDef(svc.BlockResult, + "retrieve certain block result info", + "block result information at a certain height"), + chainjson.MethodTx: rpcserver.MakeMethodDef(svc.Tx, + "retrieve certain transaction info", + "transaction information at a certain hash"), + chainjson.MethodGenesis: rpcserver.MakeMethodDef(svc.Genesis, + "retrieve the genesis info", + "genesis information"), + chainjson.MethodValidators: rpcserver.MakeMethodDef(svc.Validators, + "retrieve validator info at certain height", + "validator information at certain height"), + chainjson.MethodUnconfirmedTxs: rpcserver.MakeMethodDef(svc.UnconfirmedTxs, + "retrieve unconfirmed txs", + "unconfirmed txs"), + } +} + +func (svc *Service) HealthMethod(ctx context.Context, _ *chainjson.HealthRequest) (*chainjson.HealthResponse, *jsonrpc.Error) { + return &chainjson.HealthResponse{ + ChainID: svc.genesisCfg.ChainID, + Height: svc.blockchain.BlockHeight(ctx), + Healthy: true, + }, nil +} + +func verHandler(context.Context, *userjson.VersionRequest) (*userjson.VersionResponse, *jsonrpc.Error) { + return &userjson.VersionResponse{ + Service: serviceName, + Version: apiSemver, + Major: apiVerMajor, + Minor: apiVerMinor, + Patch: apiVerPatch, + KwilVersion: version.KwilVersion, + }, nil +} + +// Block returns block information either by block height or block hash. +// If both provided, block hash will be used. +func (svc *Service) Block(ctx context.Context, req *chainjson.BlockRequest) (*chainjson.BlockResponse, *jsonrpc.Error) { + if req.Height < 0 { + return nil, jsonrpc.NewError(jsonrpc.ErrorInvalidParams, "height cannot be negative", nil) + } + + // prioritize req.Hash over req.Height + if !req.Hash.IsZero() { + block, appHash, err := svc.blockchain.BlockByHash(ctx, req.Hash) + if err != nil { + svc.log.Error("block by hash", "hash", req.Hash, "error", err) + return nil, jsonrpc.NewError(jsonrpc.ErrorNodeInternal, "failed to get block", nil) + } + + return &chainjson.BlockResponse{ + Header: (*chainjson.BlockHeader)(block.Header), + Txns: block.Txns, + Signature: block.Signature, + Hash: req.Hash, + AppHash: appHash, + }, nil + } + + blockHash, block, appHash, err := svc.blockchain.BlockByHeight(ctx, req.Height) + svc.log.Error("block by height", "height", req.Height, "hash", req.Hash, "error", err) + if err != nil { + return nil, jsonrpc.NewError(jsonrpc.ErrorNodeInternal, "failed to get block", nil) + } + + return &chainjson.BlockResponse{ + Header: (*chainjson.BlockHeader)(block.Header), + Txns: block.Txns, + Signature: block.Signature, + Hash: blockHash, + AppHash: appHash, + }, nil +} + +// BlockResult returns block result either by block height or bloch hash. +// If both provided, block hash will be used. +func (svc *Service) BlockResult(ctx context.Context, req *chainjson.BlockResultRequest) (*chainjson.BlockResultResponse, *jsonrpc.Error) { + if req.Height < 0 { + return nil, jsonrpc.NewError(jsonrpc.ErrorInvalidParams, "height cannot be negative", nil) + } + + if !req.Hash.IsZero() { + block, _, err := svc.blockchain.BlockByHash(ctx, req.Hash) + if err != nil { + svc.log.Error("block by hash", "hash", req.Hash, "error", err) + return nil, jsonrpc.NewError(jsonrpc.ErrorNodeInternal, "failed to get block: "+err.Error(), nil) + } + + txResults, err := svc.blockchain.BlockResultByHash(ctx, req.Hash) + if err != nil { + svc.log.Error("block result by hash", "hash", req.Hash, "error", err) + return nil, jsonrpc.NewError(jsonrpc.ErrorNodeInternal, "failed to get block result: "+err.Error(), nil) + } + + return &chainjson.BlockResultResponse{ + Height: block.Header.Height, + TxResults: txResults, + }, nil + } + + blockHash, block, _, err := svc.blockchain.BlockByHeight(ctx, req.Height) + svc.log.Error("block by height", "height", req.Height, "hash", req.Hash, "error", err) + if err != nil { + return nil, jsonrpc.NewError(jsonrpc.ErrorNodeInternal, "failed to get block", nil) + } + + txResults, err := svc.blockchain.BlockResultByHash(ctx, blockHash) + if err != nil { + svc.log.Error("block result by hash", "hash", req.Hash, "error", err) + return nil, jsonrpc.NewError(jsonrpc.ErrorNodeInternal, "failed to get block result: "+err.Error(), nil) + } + + return &chainjson.BlockResultResponse{ + Height: block.Header.Height, + TxResults: txResults, + }, nil +} + +// Tx returns a transaction by hash. +func (svc *Service) Tx(ctx context.Context, req *chainjson.TxRequest) (*chaintypes.ChainTx, *jsonrpc.Error) { + if req.Hash.IsZero() { + return nil, jsonrpc.NewError(jsonrpc.ErrorInvalidParams, "hash is required", nil) + } + + tx, err := svc.blockchain.ChainTx(ctx, req.Hash) + if err != nil { + svc.log.Error("tx by hash", "hash", req.Hash, "error", err) + return nil, jsonrpc.NewError(jsonrpc.ErrorNodeInternal, "failed to get tx: "+err.Error(), nil) + } + + return tx, nil +} + +func (svc *Service) Genesis(ctx context.Context, req *chainjson.GenesisRequest) (*chainjson.GenesisResponse, *jsonrpc.Error) { + return &chainjson.GenesisResponse{ + ChainID: svc.genesisCfg.ChainID, + Leader: svc.genesisCfg.Leader, + Validators: svc.genesisCfg.Validators, + MaxBlockSize: svc.genesisCfg.MaxBlockSize, + JoinExpiry: svc.genesisCfg.JoinExpiry, + VoteExpiry: svc.genesisCfg.VoteExpiry, + DisabledGasCosts: svc.genesisCfg.DisabledGasCosts, + MaxVotesPerTx: svc.genesisCfg.MaxVotesPerTx, + }, nil +} + +// Validators returns validator set at certain height. Default to latest height. +func (svc *Service) Validators(ctx context.Context, req *chainjson.ValidatorsRequest) (*chainjson.ValidatorsResponse, *jsonrpc.Error) { + panic("Plz inject voting dependency") + // should be able to get validator set at req.Height + vals := svc.voting.GetValidators() + + pbValidators := make([]*ktypes.Validator, len(vals)) + for i, vi := range vals { + pbValidators[i] = &ktypes.Validator{ + Role: vi.Role, + PubKey: vi.PubKey, + Power: vi.Power, + } + } + + return &chainjson.ValidatorsResponse{ + Height: svc.blockchain.BlockHeight(ctx), + Validators: nil, + }, nil +} + +// UnconfirmedTxs returns the unconfirmed txs. Default return 10 txs, max return 50 txs. +func (svc *Service) UnconfirmedTxs(ctx context.Context, req *chainjson.UnconfirmedTxsRequest) (*chainjson.UnconfirmedTxsResponse, *jsonrpc.Error) { + if req.Limit < 0 { + return nil, jsonrpc.NewError(jsonrpc.ErrorInvalidParams, "invalid limit", nil) + } + if req.Limit > 50 { + req.Limit = 50 + } + if req.Limit == 0 { + req.Limit = 10 + } + total, txs := svc.blockchain.ChainUnconfirmedTx(ctx, req.Limit) + return &chainjson.UnconfirmedTxsResponse{ + Total: total, + Txs: convertNamedTxs(txs), + }, nil +} + +// The admin Service must be usable as a Svc registered with a JSON-RPC Server. +var _ rpcserver.Svc = (*Service)(nil) + +func convertNamedTxs(txs []nodetypes.NamedTx) []chainjson.NamedTx { + res := make([]chainjson.NamedTx, len(txs)) + for i, tx := range txs { + res[i] = chainjson.NamedTx{ + Hash: tx.Hash, + Tx: tx.Tx, + } + } + return res +} diff --git a/node/services/jsonrpc/chainsvc/spec.go b/node/services/jsonrpc/chainsvc/spec.go new file mode 100644 index 000000000..ca89f5baf --- /dev/null +++ b/node/services/jsonrpc/chainsvc/spec.go @@ -0,0 +1,17 @@ +package chainsvc + +import ( + "github.com/kwilteam/kwil-db/node/services/jsonrpc/openrpc" +) + +var ( + SpecInfo = openrpc.Info{ + Title: "Kwil DB Chain service", + Description: `The JSON-RPC chain service for Kwil DB.`, + License: &openrpc.License{ // the spec file's license + Name: "CC0-1.0", + URL: "https://creativecommons.org/publicdomain/zero/1.0/legalcode", + }, + Version: "0.1.0", + } +) diff --git a/node/services/jsonrpc/funcsvc/service.go b/node/services/jsonrpc/funcsvc/service.go index 5c991e3a7..0ee05fbc4 100644 --- a/node/services/jsonrpc/funcsvc/service.go +++ b/node/services/jsonrpc/funcsvc/service.go @@ -68,14 +68,6 @@ func (svc *Service) Methods() map[jsonrpc.Method]rpcserver.MethodDef { } } -func (svc *Service) Handlers() map[jsonrpc.Method]rpcserver.MethodHandler { - handlers := make(map[jsonrpc.Method]rpcserver.MethodHandler) - for method, def := range svc.Methods() { - handlers[method] = def.Handler - } - return handlers -} - // VerifySignature checks the signature with the given public key and message. // This only verifies the signature against known kwil-db singing schema, which // is determined by the signature's type.