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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions modules/network/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"sort"

"cosmossdk.io/collections"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -237,6 +238,33 @@ func (q *queryServer) LastAttestedHeight(c context.Context, req *types.QueryLast
}, nil
}

// AttesterSet queries the full ordered attester set
func (q *queryServer) AttesterSet(goCtx context.Context, req *types.QueryAttesterSetRequest) (*types.QueryAttesterSetResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

ctx := sdk.UnwrapSDKContext(goCtx)
entries := []types.AttesterSetEntry{}
if err := q.keeper.ValidatorIndex.Walk(ctx, nil, func(addr string, idx uint16) (bool, error) {
info, err := q.keeper.GetAttesterInfo(ctx, addr)
if err != nil {
return false, err
}
entries = append(entries, types.AttesterSetEntry{
Authority: info.Authority,
ConsensusAddress: addr,
Index: uint32(idx),
Pubkey: info.Pubkey,
})
return false, nil
}); err != nil {
return nil, err
}
sort.Slice(entries, func(i, j int) bool { return entries[i].Index < entries[j].Index })
return &types.QueryAttesterSetResponse{Entries: entries}, nil
}

// AttesterInfo queries the attester information including public key
func (q *queryServer) AttesterInfo(c context.Context, req *types.QueryAttesterInfoRequest) (*types.QueryAttesterInfoResponse, error) {
if req == nil {
Expand Down
777 changes: 711 additions & 66 deletions modules/network/types/query.pb.go

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions modules/network/types/query.pb.gw.go

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

25 changes: 25 additions & 0 deletions modules/proto/evabci/network/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ option go_package = "github.com/evstack/ev-abci/modules/network/types";

import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "google/protobuf/any.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "evabci/network/v1/types.proto";
import "evabci/network/v1/attester.proto";
Expand Down Expand Up @@ -51,6 +53,11 @@ service Query {
rpc AttesterInfo(QueryAttesterInfoRequest) returns (QueryAttesterInfoResponse) {
option (google.api.http).get = "/evabci/network/v1/attester/{validator_address}";
}

// AttesterSet queries the full ordered attester set
rpc AttesterSet(QueryAttesterSetRequest) returns (QueryAttesterSetResponse) {
option (google.api.http).get = "/evabci/network/v1/attester_set";
}
}

// QueryParamsRequest is the request type for the Query/Params RPC method.
Expand Down Expand Up @@ -143,3 +150,21 @@ message QueryAttesterInfoRequest {
message QueryAttesterInfoResponse {
AttesterInfo attester_info = 1;
}

// QueryAttesterSetRequest is the request type for the Query/AttesterSet RPC method.
message QueryAttesterSetRequest {}

// AttesterSetEntry is a single entry in the attester set, ordered by index.
message AttesterSetEntry {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string consensus_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
uint32 index = 3;
google.protobuf.Any pubkey = 4 [(cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey"];
}

// QueryAttesterSetResponse is the response type for the Query/AttesterSet RPC method.
message QueryAttesterSetResponse {
repeated AttesterSetEntry entries = 1 [(gogoproto.nullable) = false];
}
66 changes: 66 additions & 0 deletions pkg/adapter/providers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package adapter_test

import (
"bytes"
"context"
"sort"
"testing"

tmcryptoed25519 "github.com/cometbft/cometbft/crypto/ed25519"
cmtstate "github.com/cometbft/cometbft/state"
cmttypes "github.com/cometbft/cometbft/types"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/stretchr/testify/require"

"github.com/evstack/ev-abci/pkg/adapter"
)

type mockStateStore struct {
state *cmtstate.State
}

func (m *mockStateStore) LoadState(_ context.Context) (*cmtstate.State, error) {
return m.state, nil
}

func TestValidatorHasherOrderingMatchesAddressSort(t *testing.T) {
// 3 random ed25519 validators
keys := []tmcryptoed25519.PubKey{
tmcryptoed25519.GenPrivKey().PubKey().(tmcryptoed25519.PubKey),
tmcryptoed25519.GenPrivKey().PubKey().(tmcryptoed25519.PubKey),
tmcryptoed25519.GenPrivKey().PubKey().(tmcryptoed25519.PubKey),
}

// Canonical: sort by Address() bytes ascending, convert to libp2p, hash.
canonicalOrder := make([]tmcryptoed25519.PubKey, len(keys))
copy(canonicalOrder, keys)
sort.Slice(canonicalOrder, func(i, j int) bool {
return bytes.Compare(canonicalOrder[i].Address(), canonicalOrder[j].Address()) < 0
})
libp2pCanonical := make([]crypto.PubKey, len(canonicalOrder))
for i, k := range canonicalOrder {
p, err := crypto.UnmarshalEd25519PublicKey(k.Bytes())
require.NoError(t, err)
libp2pCanonical[i] = p
}
sequencerAddr := canonicalOrder[0].Address().Bytes()
canonicalHash, err := adapter.ValidatorsHasher(libp2pCanonical, sequencerAddr)
require.NoError(t, err)

// Build a cmttypes.ValidatorSet via NewValidatorSet (will sort internally).
vals := make([]*cmttypes.Validator, len(keys))
for i, k := range keys {
vals[i] = cmttypes.NewValidator(k, 1)
}
vs := cmttypes.NewValidatorSet(vals)

// Wrap in a mock state and call the provider.
st := &cmtstate.State{Validators: vs}
store := &mockStateStore{state: st}
hasher := adapter.ValidatorHasherFromStoreProvider(store)
gotHash, err := hasher(sequencerAddr, nil)
require.NoError(t, err)

require.Equal(t, []byte(canonicalHash), []byte(gotHash),
"provider hash must match address-sorted canonical hash")
}
Loading
Loading