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
10 changes: 10 additions & 0 deletions sei-tendermint/autobahn/types/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ func (k SecretKey) Public() PublicKey {
return PublicKey{key: k.key.Public()}
}

// SignWithTag signs msg with a domain-separation tag.
func (k SecretKey) SignWithTag(tag ed25519.Tag, msg []byte) ed25519.Signature {
return k.key.SignWithTag(tag, msg)
}

// PublicKey is the public key of the validator.
// nolint:recvcheck
type PublicKey struct {
Expand All @@ -99,6 +104,11 @@ func (k PublicKey) Bytes() []byte { return k.key.Bytes() }
// ED25519 returns the underlying Ed25519 public key.
func (k PublicKey) ED25519() ed25519.PublicKey { return k.key }

// VerifyWithTag verifies a tagged signature.
func (k PublicKey) VerifyWithTag(tag ed25519.Tag, msg []byte, sig ed25519.Signature) error {
return k.key.VerifyWithTag(tag, msg, sig)
}

// PublicKeyFromBytes constructs a public key from bytes.
func PublicKeyFromBytes(b []byte) (PublicKey, error) {
k, err := ed25519.PublicKeyFromBytes(b)
Expand Down
10 changes: 8 additions & 2 deletions sei-tendermint/config/autobahn.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ type URL struct{ *url.URL }

func (u URL) MarshalText() ([]byte, error) { return []byte(u.String()), nil }
func (u *URL) UnmarshalText(text []byte) error {
url, err := url.Parse(string(text))
parsed, err := url.Parse(string(text))
if err != nil {
return err
}
u.URL = url
if err := utils.CheckHTTPURL(*parsed); err != nil {
return err
}
u.URL = parsed
return nil
}

Expand Down Expand Up @@ -107,6 +110,9 @@ func (fc *AutobahnFileConfig) Validate() error {
if v.EVMRPC.URL == nil {
return fmt.Errorf("validator %s is missing evmrpc URL", v.ValidatorKey)
}
if err := utils.CheckHTTPURL(*v.EVMRPC.URL); err != nil {
return fmt.Errorf("validator %s evmrpc: %w", v.ValidatorKey, err)
}
}
if fc.MaxTxsPerBlock == 0 {
return errors.New("max_txs_per_block must be > 0")
Expand Down
5 changes: 5 additions & 0 deletions sei-tendermint/config/autobahn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func TestURLJSONReencode(t *testing.T) {
require.Equal(t, want.String(), got.String())
}

func TestURLUnmarshalRejectsNonHTTP(t *testing.T) {
var got URL
require.Error(t, json.Unmarshal([]byte(`"ws://example.com:8545"`), &got))
}

func TestAutobahnBlockDBConfig_LittBlockConfig(t *testing.T) {
dir := t.TempDir()
const (
Expand Down
56 changes: 54 additions & 2 deletions sei-tendermint/internal/p2p/conv.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package p2p
import (
"errors"
"fmt"
"net/url"
"strings"

atypes "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types"
"github.com/sei-protocol/sei-chain/sei-tendermint/crypto/ed25519"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/conn"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/pb"
Expand Down Expand Up @@ -84,6 +86,15 @@ var nodePublicKeyConv = protoutils.Conv[NodePublicKey, *pb.NodePublicKey]{
},
}

// gigaHandshakeClaim is the Autobahn committee identity a validator advertises
// on the giga handshake.
type gigaHandshakeClaim struct {
utils.ReadOnly
Validator atypes.PublicKey
sig ed25519.Signature
evmRPC string
}

type handshakeSpec struct {
SelfAddr utils.Option[NodeAddress]
PexAddrs []NodeAddress
Expand All @@ -93,6 +104,7 @@ type handshakeSpec struct {
type handshakeMsg struct {
NodeAuth NodeChallengeSig
handshakeSpec
GigaClaim utils.Option[gigaHandshakeClaim]
}

var handshakeMsgConv = protoutils.Conv[*handshakeMsg, *pb.Handshake]{
Expand All @@ -105,14 +117,19 @@ var handshakeMsgConv = protoutils.Conv[*handshakeMsg, *pb.Handshake]{
for i, addr := range m.PexAddrs {
pexAddrs[i] = addr.String()
}

return &pb.Handshake{
out := &pb.Handshake{
NodeAuthKey: nodePublicKeyConv.Encode(m.NodeAuth.Key()),
NodeAuthSig: m.NodeAuth.sig.Bytes(),
SelfAddr: selfAddr,
PexAddrs: pexAddrs,
SeiGigaConnection: m.SeiGigaConnection,
}
if claim, ok := m.GigaClaim.Get(); ok {
out.ValidatorAuthKey = claim.Validator.Bytes()
out.ValidatorAuthSig = claim.sig.Bytes()
out.EvmRpc = utils.Alloc(claim.evmRPC)
}
return out
},
Decode: func(p *pb.Handshake) (*handshakeMsg, error) {
nodeAuthKey, err := nodePublicKeyConv.DecodeReq(p.NodeAuthKey)
Expand All @@ -139,13 +156,48 @@ var handshakeMsgConv = protoutils.Conv[*handshakeMsg, *pb.Handshake]{
}
pexAddrs[i] = addr
}
claim, err := decodeGigaClaim(p)
if err != nil {
return nil, err
}
return &handshakeMsg{
NodeAuth: NodeChallengeSig{key: nodeAuthKey, sig: nodeAuthSig},
handshakeSpec: handshakeSpec{
SelfAddr: selfAddr,
PexAddrs: pexAddrs,
SeiGigaConnection: p.SeiGigaConnection,
},
GigaClaim: claim,
}, nil
},
}

func decodeGigaClaim(p *pb.Handshake) (utils.Option[gigaHandshakeClaim], error) {
isValidator := p.ValidatorAuthKey != nil || p.ValidatorAuthSig != nil || p.EvmRpc != nil
if !isValidator {
return utils.None[gigaHandshakeClaim](), nil
}
if p.ValidatorAuthKey == nil || p.ValidatorAuthSig == nil || p.EvmRpc == nil {
return utils.None[gigaHandshakeClaim](), fmt.Errorf("giga claim requires validator_auth_key, validator_auth_sig, and evm_rpc")
}
valKey, err := atypes.PublicKeyFromBytes(p.ValidatorAuthKey)
if err != nil {
return utils.None[gigaHandshakeClaim](), fmt.Errorf("ValidatorAuthKey: %w", err)
}
valSig, err := ed25519.SignatureFromBytes(p.ValidatorAuthSig)
if err != nil {
return utils.None[gigaHandshakeClaim](), fmt.Errorf("ValidatorAuthSig: %w", err)
}
u, err := url.Parse(*p.EvmRpc)
if err != nil {
return utils.None[gigaHandshakeClaim](), fmt.Errorf("EvmRpc: %w", err)
}
if err := utils.CheckHTTPURL(*u); err != nil {
return utils.None[gigaHandshakeClaim](), fmt.Errorf("EvmRpc: %w", err)
}
return utils.Some(gigaHandshakeClaim{
Validator: valKey,
sig: valSig,
evmRPC: *p.EvmRpc,
}), nil
}
72 changes: 72 additions & 0 deletions sei-tendermint/internal/p2p/conv_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package p2p

import (
"strings"
"testing"

"google.golang.org/protobuf/proto"

atypes "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/conn"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/pb"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/protoutils"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/require"
)
Expand All @@ -28,6 +34,72 @@ func TestHandshakeMsgConv(t *testing.T) {
}
}

func TestDecodeGigaClaimEVMRPC(t *testing.T) {
rng := utils.TestRng()
key := atypes.GenSecretKey(rng)
sig := key.SignWithTag(gigaValidatorHandshakeTag, []byte("x"))
claim := func(evm string) *pb.Handshake {
return &pb.Handshake{
ValidatorAuthKey: key.Public().Bytes(),
ValidatorAuthSig: sig.Bytes(),
EvmRpc: &evm,
}
}
for _, s := range []string{"http://validator.example:8545", "https://validator.example:8545"} {
got, err := decodeGigaClaim(claim(s))
require.NoError(t, err)
require.Equal(t, s, got.OrPanic("missing claim").evmRPC)
}
for _, s := range []string{"file://localhost/rpc", "ws://validator.example:8545", "http://", "http://u:p@validator.example:8545"} {
_, err := decodeGigaClaim(claim(s))
require.Error(t, err)
}
}

func TestDecodeGigaClaimRequiresAllFields(t *testing.T) {
rng := utils.TestRng()
key := atypes.GenSecretKey(rng)
sig := key.SignWithTag(gigaValidatorHandshakeTag, []byte("x"))
evmRPC := "http://validator.example:8545"
full := func() *pb.Handshake {
return &pb.Handshake{
ValidatorAuthKey: key.Public().Bytes(),
ValidatorAuthSig: sig.Bytes(),
EvmRpc: &evmRPC,
}
}
for _, drop := range []func(*pb.Handshake){
func(p *pb.Handshake) { p.ValidatorAuthKey = nil },
func(p *pb.Handshake) { p.ValidatorAuthSig = nil },
func(p *pb.Handshake) { p.EvmRpc = nil },
func(p *pb.Handshake) { p.ValidatorAuthKey, p.ValidatorAuthSig = nil, nil },
func(p *pb.Handshake) { p.ValidatorAuthKey, p.EvmRpc = nil, nil },
func(p *pb.Handshake) { p.ValidatorAuthSig, p.EvmRpc = nil, nil },
} {
p := full()
drop(p)
_, err := decodeGigaClaim(p)
require.Error(t, err)
}

// None of the three is a peer without a claim, rather than a malformed one.
got, err := decodeGigaClaim(&pb.Handshake{})
require.NoError(t, err)
require.False(t, got.IsPresent())
}

func TestHandshakeWireguardRejectsOversizedEvmRPC(t *testing.T) {
tooLong := strings.Repeat("a", 2049)
raw, err := proto.Marshal(&pb.Handshake{EvmRpc: &tooLong})
require.NoError(t, err)
require.Error(t, protoutils.Scan[*pb.Handshake](raw))

ok := strings.Repeat("a", 2048)
raw, err = proto.Marshal(&pb.Handshake{EvmRpc: &ok})
require.NoError(t, err)
require.NoError(t, protoutils.Scan[*pb.Handshake](raw))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we test rejecting partial claim sets (less than all of authkey, authsig, and evmrpc)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added

func TestNodePublicKeyFromString(t *testing.T) {
rng := utils.TestRng()
key := makeKey(rng).Public()
Expand Down
6 changes: 4 additions & 2 deletions sei-tendermint/internal/p2p/giga_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
type GigaNodeAddr struct {
Key NodePublicKey
HostPort tcp.HostPort
EVMRPC *url.URL
EVMRPC url.URL
}

func (a GigaNodeAddr) String() string {
Expand Down Expand Up @@ -65,7 +65,8 @@ type GigaValidatorConfig struct {
// GigaRouter is the read-path / Run / EvmProxy surface. Implemented by
// *gigaValidatorRouter and *gigaFullnodeRouter; Mempool returns Some only
// on validators. RunInboundConn is served by both — non-committee peers
// get the block-sync subset only.
// get the block-sync subset only. A fullnode accepts committee peers but
// has no consensus state to serve them.
type GigaRouter interface {
Run(ctx context.Context) error
RunInboundConn(ctx context.Context, hConn *handshakedConn) error
Expand All @@ -76,4 +77,5 @@ type GigaRouter interface {
EvmProxy(sender common.Address) utils.Option[*rpc.Client]
Mempool() utils.Option[*producer.State]
Validators(n atypes.GlobalBlockNumber) ([]*types.Validator, atypes.GlobalBlockNumber, error)
fillInboundHandshake(spec handshakeSpec) (handshakeSpec, utils.Option[handshakeOffer])
}
Loading
Loading