CCQ/Node: Guardian Changes (#3423)
* CCQ/Node: Guardian Changes * Code review rework
This commit is contained in:
parent
c254ebf3ec
commit
669e2bc40e
|
@ -205,6 +205,12 @@ var (
|
|||
|
||||
chainGovernorEnabled *bool
|
||||
|
||||
ccqEnabled *bool
|
||||
ccqAllowedRequesters *string
|
||||
ccqP2pPort *uint
|
||||
ccqP2pBootstrap *string
|
||||
ccqAllowedPeers *string
|
||||
|
||||
gatewayRelayerContract *string
|
||||
gatewayRelayerKeyPath *string
|
||||
gatewayRelayerKeyPassPhrase *string
|
||||
|
@ -370,6 +376,12 @@ func init() {
|
|||
|
||||
chainGovernorEnabled = NodeCmd.Flags().Bool("chainGovernorEnabled", false, "Run the chain governor")
|
||||
|
||||
ccqEnabled = NodeCmd.Flags().Bool("ccqEnabled", false, "Enable cross chain query support")
|
||||
ccqAllowedRequesters = NodeCmd.Flags().String("ccqAllowedRequesters", "", "Comma separated list of signers allowed to submit cross chain queries")
|
||||
ccqP2pPort = NodeCmd.Flags().Uint("ccqP2pPort", 8996, "CCQ P2P UDP listener port")
|
||||
ccqP2pBootstrap = NodeCmd.Flags().String("ccqP2pBootstrap", "", "CCQ P2P bootstrap peers (comma-separated)")
|
||||
ccqAllowedPeers = NodeCmd.Flags().String("ccqAllowedPeers", "", "CCQ allowed P2P peers (comma-separated)")
|
||||
|
||||
gatewayRelayerContract = NodeCmd.Flags().String("gatewayRelayerContract", "", "Address of the smart contract on wormchain to receive relayed VAAs")
|
||||
gatewayRelayerKeyPath = NodeCmd.Flags().String("gatewayRelayerKeyPath", "", "Path to gateway relayer private key for signing transactions")
|
||||
gatewayRelayerKeyPassPhrase = NodeCmd.Flags().String("gatewayRelayerKeyPassPhrase", "", "Pass phrase used to unarmor the gateway relayer key file")
|
||||
|
@ -469,6 +481,7 @@ func runNode(cmd *cobra.Command, args []string) {
|
|||
|
||||
// Use the first guardian node as bootstrap
|
||||
*p2pBootstrap = fmt.Sprintf("/dns4/guardian-0.guardian/udp/%d/quic/p2p/%s", *p2pPort, g0key.String())
|
||||
*ccqP2pBootstrap = fmt.Sprintf("/dns4/guardian-0.guardian/udp/%d/quic/p2p/%s", *ccqP2pPort, g0key.String())
|
||||
|
||||
// Deterministic ganache ETH devnet address.
|
||||
*ethContract = unsafeDevModeEvmContractAddress(*ethContract)
|
||||
|
@ -1422,8 +1435,9 @@ func runNode(cmd *cobra.Command, args []string) {
|
|||
node.GuardianOptionAccountant(*accountantContract, *accountantWS, *accountantCheckEnabled, accountantWormchainConn),
|
||||
node.GuardianOptionGovernor(*chainGovernorEnabled),
|
||||
node.GuardianOptionGatewayRelayer(*gatewayRelayerContract, gatewayRelayerWormchainConn),
|
||||
node.GuardianOptionQueryHandler(*ccqEnabled, *ccqAllowedRequesters),
|
||||
node.GuardianOptionAdminService(*adminSocketPath, ethRPC, ethContract, rpcMap),
|
||||
node.GuardianOptionP2P(p2pKey, *p2pNetworkID, *p2pBootstrap, *nodeName, *disableHeartbeatVerify, *p2pPort, ibc.GetFeatures),
|
||||
node.GuardianOptionP2P(p2pKey, *p2pNetworkID, *p2pBootstrap, *nodeName, *disableHeartbeatVerify, *p2pPort, *ccqP2pBootstrap, *ccqP2pPort, *ccqAllowedPeers, ibc.GetFeatures),
|
||||
node.GuardianOptionStatusServer(*statusAddr),
|
||||
node.GuardianOptionProcessor(),
|
||||
}
|
||||
|
|
|
@ -356,6 +356,12 @@ func runSpy(cmd *cobra.Command, args []string) {
|
|||
components,
|
||||
nil, // ibc feature string
|
||||
false, // gateway relayer enabled
|
||||
false, // ccqEnabled
|
||||
nil, // query requests
|
||||
nil, // query responses
|
||||
"", // query bootstrap peers
|
||||
0, // query port
|
||||
"", // query allow list
|
||||
)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
"go.uber.org/zap"
|
||||
|
@ -68,6 +69,10 @@ func (m mockEVMConnector) RawCallContext(ctx context.Context, result interface{}
|
|||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (m mockEVMConnector) RawBatchCallContext(ctx context.Context, b []ethRpc.BatchElem) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func generateGS(num int) (keys []*ecdsa.PrivateKey, addrs []common.Address) {
|
||||
for i := 0; i < num; i++ {
|
||||
key, err := ethcrypto.GenerateKey()
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Environment string
|
||||
|
||||
const (
|
||||
|
@ -9,3 +14,24 @@ const (
|
|||
GoTest Environment = "unit-test"
|
||||
AccountantMock Environment = "accountant-mock" // Used for mocking accountant with a Wormchain connection
|
||||
)
|
||||
|
||||
// ParseEnvironment parses a string into the corresponding Environment value, allowing various reasonable variations.
|
||||
func ParseEnvironment(str string) (Environment, error) {
|
||||
str = strings.ToLower(str)
|
||||
if str == "prod" || str == "mainnet" {
|
||||
return MainNet, nil
|
||||
}
|
||||
if str == "test" || str == "testnet" {
|
||||
return TestNet, nil
|
||||
}
|
||||
if str == "dev" || str == "devnet" || str == "unsafedevnet" {
|
||||
return UnsafeDevNet, nil
|
||||
}
|
||||
if str == "unit-test" || str == "gotest" {
|
||||
return GoTest, nil
|
||||
}
|
||||
if str == "accountant-mock" || str == "accountantmock" {
|
||||
return AccountantMock, nil
|
||||
}
|
||||
return UnsafeDevNet, fmt.Errorf("invalid environment string: %s", str)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseEnvironment(t *testing.T) {
|
||||
type test struct {
|
||||
input string
|
||||
output Environment
|
||||
err bool
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{input: "MainNet", output: MainNet, err: false},
|
||||
{input: "Prod", output: MainNet, err: false},
|
||||
|
||||
{input: "TestNet", output: TestNet, err: false},
|
||||
{input: "test", output: TestNet, err: false},
|
||||
|
||||
{input: "UnsafeDevNet", output: UnsafeDevNet, err: false},
|
||||
{input: "devnet", output: UnsafeDevNet, err: false},
|
||||
{input: "dev", output: UnsafeDevNet, err: false},
|
||||
|
||||
{input: "GoTest", output: GoTest, err: false},
|
||||
{input: "unit-test", output: GoTest, err: false},
|
||||
|
||||
{input: "AccountantMock", output: AccountantMock, err: false},
|
||||
{input: "accountant-mock", output: AccountantMock, err: false},
|
||||
|
||||
{input: "junk", output: UnsafeDevNet, err: true},
|
||||
{input: "", output: UnsafeDevNet, err: true},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
output, err := ParseEnvironment(tc.input)
|
||||
if err != nil {
|
||||
if tc.err == false {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
} else if tc.err {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.Equal(t, tc.output, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -11,7 +11,9 @@ import (
|
|||
"github.com/certusone/wormhole/node/pkg/governor"
|
||||
"github.com/certusone/wormhole/node/pkg/gwrelayer"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
|
@ -59,6 +61,7 @@ type G struct {
|
|||
acct *accountant.Accountant
|
||||
gov *governor.ChainGovernor
|
||||
gatewayRelayer *gwrelayer.GatewayRelayer
|
||||
queryHandler *query.QueryHandler
|
||||
publicrpcServer *grpc.Server
|
||||
|
||||
// runnables
|
||||
|
@ -82,6 +85,12 @@ type G struct {
|
|||
obsvReqSendC channelPair[*gossipv1.ObservationRequest]
|
||||
// acctC is the channel where messages will be put after they reached quorum in the accountant.
|
||||
acctC channelPair[*common.MessagePublication]
|
||||
|
||||
// Cross Chain Query Handler channels
|
||||
chainQueryReqC map[vaa.ChainID]chan *query.PerChainQueryInternal
|
||||
signedQueryReqC channelPair[*gossipv1.SignedQueryRequest]
|
||||
queryResponseC channelPair[*query.PerChainQueryResponseInternal]
|
||||
queryResponsePublicationC channelPair[*query.QueryResponsePublication]
|
||||
}
|
||||
|
||||
func NewGuardianNode(
|
||||
|
@ -108,6 +117,11 @@ func (g *G) initializeBasic(rootCtxCancel context.CancelFunc) {
|
|||
g.obsvReqC = makeChannelPair[*gossipv1.ObservationRequest](observationRequestInboundBufferSize)
|
||||
g.obsvReqSendC = makeChannelPair[*gossipv1.ObservationRequest](observationRequestOutboundBufferSize)
|
||||
g.acctC = makeChannelPair[*common.MessagePublication](accountant.MsgChannelCapacity)
|
||||
// Cross Chain Query Handler channels
|
||||
g.chainQueryReqC = make(map[vaa.ChainID]chan *query.PerChainQueryInternal)
|
||||
g.signedQueryReqC = makeChannelPair[*gossipv1.SignedQueryRequest](query.SignedQueryRequestChannelSize)
|
||||
g.queryResponseC = makeChannelPair[*query.PerChainQueryResponseInternal](0)
|
||||
g.queryResponsePublicationC = makeChannelPair[*query.QueryResponsePublication](0)
|
||||
|
||||
// Guardian set state managed by processor
|
||||
g.gst = common.NewGuardianSetState(nil)
|
||||
|
@ -191,6 +205,13 @@ func (g *G) Run(rootCtxCancel context.CancelFunc, options ...*GuardianOption) su
|
|||
}
|
||||
}
|
||||
|
||||
if g.queryHandler != nil {
|
||||
logger.Info("Starting query handler", zap.String("component", "ccq"))
|
||||
if err := g.queryHandler.Start(ctx); err != nil {
|
||||
logger.Fatal("failed to create query handler", zap.Error(err), zap.String("component", "ccq"))
|
||||
}
|
||||
}
|
||||
|
||||
// Start any other runnables
|
||||
for name, runnable := range g.runnables {
|
||||
if err := supervisor.Run(ctx, name, runnable); err != nil {
|
||||
|
|
|
@ -190,7 +190,7 @@ func mockGuardianRunnable(t testing.TB, gs []*mockGuardian, mockGuardianIndex ui
|
|||
GuardianOptionNoAccountant(), // disable accountant
|
||||
GuardianOptionGovernor(true),
|
||||
GuardianOptionGatewayRelayer("", nil), // disable gateway relayer
|
||||
GuardianOptionP2P(gs[mockGuardianIndex].p2pKey, networkID, bootstrapPeers, nodeName, false, cfg.p2pPort, func() string { return "" }),
|
||||
GuardianOptionP2P(gs[mockGuardianIndex].p2pKey, networkID, bootstrapPeers, nodeName, false, cfg.p2pPort, "", 0, "", func() string { return "" }),
|
||||
GuardianOptionPublicRpcSocket(cfg.publicSocket, publicRpcLogDetail),
|
||||
GuardianOptionPublicrpcTcpService(cfg.publicRpc, publicRpcLogDetail),
|
||||
GuardianOptionPublicWeb(cfg.publicWeb, cfg.publicSocket, "", false, ""),
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/certusone/wormhole/node/pkg/p2p"
|
||||
"github.com/certusone/wormhole/node/pkg/processor"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/readiness"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
|
@ -38,7 +39,7 @@ type GuardianOption struct {
|
|||
|
||||
// GuardianOptionP2P configures p2p networking.
|
||||
// Dependencies: Accountant, Governor
|
||||
func GuardianOptionP2P(p2pKey libp2p_crypto.PrivKey, networkId string, bootstrapPeers string, nodeName string, disableHeartbeatVerify bool, port uint, ibcFeaturesFunc func() string) *GuardianOption {
|
||||
func GuardianOptionP2P(p2pKey libp2p_crypto.PrivKey, networkId string, bootstrapPeers string, nodeName string, disableHeartbeatVerify bool, port uint, ccqBootstrapPeers string, ccqPort uint, ccqAllowedPeers string, ibcFeaturesFunc func() string) *GuardianOption {
|
||||
return &GuardianOption{
|
||||
name: "p2p",
|
||||
dependencies: []string{"accountant", "governor", "gateway-relayer"},
|
||||
|
@ -72,6 +73,36 @@ func GuardianOptionP2P(p2pKey libp2p_crypto.PrivKey, networkId string, bootstrap
|
|||
components,
|
||||
ibcFeaturesFunc,
|
||||
(g.gatewayRelayer != nil),
|
||||
(g.queryHandler != nil),
|
||||
g.signedQueryReqC.writeC,
|
||||
g.queryResponsePublicationC.readC,
|
||||
ccqBootstrapPeers,
|
||||
ccqPort,
|
||||
ccqAllowedPeers,
|
||||
)
|
||||
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// GuardianOptionQueryHandler configures the Cross Chain Query module.
|
||||
func GuardianOptionQueryHandler(ccqEnabled bool, allowedRequesters string) *GuardianOption {
|
||||
return &GuardianOption{
|
||||
name: "query",
|
||||
f: func(ctx context.Context, logger *zap.Logger, g *G) error {
|
||||
if !ccqEnabled {
|
||||
logger.Info("ccq: cross chain query is disabled", zap.String("component", "ccq"))
|
||||
return nil
|
||||
}
|
||||
|
||||
g.queryHandler = query.NewQueryHandler(
|
||||
logger,
|
||||
g.env,
|
||||
allowedRequesters,
|
||||
g.signedQueryReqC.readC,
|
||||
g.chainQueryReqC,
|
||||
g.queryResponseC.readC,
|
||||
g.queryResponsePublicationC.writeC,
|
||||
)
|
||||
|
||||
return nil
|
||||
|
@ -301,6 +332,31 @@ func GuardianOptionWatchers(watcherConfigs []watchers.WatcherConfig, ibcWatcherC
|
|||
}(chainMsgC[chainId], chainId)
|
||||
}
|
||||
|
||||
// Per-chain query response channel
|
||||
chainQueryResponseC := make(map[vaa.ChainID]chan *query.PerChainQueryResponseInternal)
|
||||
// aggregate per-chain msgC into msgC.
|
||||
// SECURITY defense-in-depth: This way we enforce that a watcher must set the msg.EmitterChain to its chainId, which makes the code easier to audit
|
||||
for _, chainId := range vaa.GetAllNetworkIDs() {
|
||||
chainQueryResponseC[chainId] = make(chan *query.PerChainQueryResponseInternal)
|
||||
go func(c <-chan *query.PerChainQueryResponseInternal, chainId vaa.ChainID) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case response := <-c:
|
||||
if response.ChainId != chainId {
|
||||
// SECURITY: This should never happen. If it does, a watcher has been compromised.
|
||||
logger.Fatal("SECURITY CRITICAL: Received query response from a chain that was not marked as originating from that chain",
|
||||
zap.Uint16("responseChainId", uint16(response.ChainId)),
|
||||
zap.Stringer("watcherChainId", chainId),
|
||||
)
|
||||
}
|
||||
g.queryResponseC.writeC <- response
|
||||
}
|
||||
}
|
||||
}(chainQueryResponseC[chainId], chainId)
|
||||
}
|
||||
|
||||
watchers := make(map[watchers.NetworkID]interfaces.L1Finalizer)
|
||||
|
||||
for _, wc := range watcherConfigs {
|
||||
|
@ -316,6 +372,7 @@ func GuardianOptionWatchers(watcherConfigs []watchers.WatcherConfig, ibcWatcherC
|
|||
}
|
||||
|
||||
chainObsvReqC[wc.GetChainID()] = make(chan *gossipv1.ObservationRequest, observationRequestPerChainBufferSize)
|
||||
g.chainQueryReqC[wc.GetChainID()] = make(chan *query.PerChainQueryInternal, query.QueryRequestBufferSize)
|
||||
|
||||
if wc.RequiredL1Finalizer() != "" {
|
||||
l1watcher, ok := watchers[wc.RequiredL1Finalizer()]
|
||||
|
@ -327,7 +384,7 @@ func GuardianOptionWatchers(watcherConfigs []watchers.WatcherConfig, ibcWatcherC
|
|||
wc.SetL1Finalizer(l1watcher)
|
||||
}
|
||||
|
||||
l1finalizer, runnable, err := wc.Create(chainMsgC[wc.GetChainID()], chainObsvReqC[wc.GetChainID()], g.setC.writeC, g.env)
|
||||
l1finalizer, runnable, err := wc.Create(chainMsgC[wc.GetChainID()], chainObsvReqC[wc.GetChainID()], g.chainQueryReqC[wc.GetChainID()], chainQueryResponseC[wc.GetChainID()], g.setC.writeC, g.env)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating watcher: %w", err)
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
ccqP2pMessagesSent = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "wormhole_ccqp2p_broadcast_messages_sent_total",
|
||||
Help: "Total number of ccq p2p pubsub broadcast messages sent",
|
||||
})
|
||||
ccqP2pMessagesReceived = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "wormhole_ccqp2p_broadcast_messages_received_total",
|
||||
Help: "Total number of ccq p2p pubsub broadcast messages received",
|
||||
}, []string{"type"})
|
||||
)
|
||||
|
||||
type ccqP2p struct {
|
||||
logger *zap.Logger
|
||||
|
||||
h host.Host
|
||||
th_req *pubsub.Topic
|
||||
th_resp *pubsub.Topic
|
||||
sub *pubsub.Subscription
|
||||
allowedPeers map[string]struct{}
|
||||
}
|
||||
|
||||
func newCcqRunP2p(
|
||||
logger *zap.Logger,
|
||||
allowedPeersStr string,
|
||||
) *ccqP2p {
|
||||
l := logger.With(zap.String("component", "ccqp2p"))
|
||||
allowedPeers := make(map[string]struct{})
|
||||
for _, peerID := range strings.Split(allowedPeersStr, ",") {
|
||||
if peerID != "" {
|
||||
l.Info("will allow requests from peer", zap.String("peerID", peerID))
|
||||
allowedPeers[peerID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return &ccqP2p{
|
||||
logger: l,
|
||||
allowedPeers: allowedPeers,
|
||||
}
|
||||
}
|
||||
|
||||
func (ccq *ccqP2p) run(
|
||||
ctx context.Context,
|
||||
priv crypto.PrivKey,
|
||||
gk *ecdsa.PrivateKey,
|
||||
networkID string,
|
||||
bootstrapPeers string,
|
||||
port uint,
|
||||
signedQueryReqC chan<- *gossipv1.SignedQueryRequest,
|
||||
queryResponseReadC <-chan *query.QueryResponsePublication,
|
||||
errC chan error,
|
||||
) error {
|
||||
var err error
|
||||
|
||||
components := DefaultComponents()
|
||||
if components == nil {
|
||||
return fmt.Errorf("components is not initialized")
|
||||
}
|
||||
components.Port = port
|
||||
|
||||
ccq.h, err = NewHost(ccq.logger, ctx, networkID, bootstrapPeers, components, priv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create p2p: %w", err)
|
||||
}
|
||||
|
||||
topic_req := fmt.Sprintf("%s/%s", networkID, "ccq_req")
|
||||
topic_resp := fmt.Sprintf("%s/%s", networkID, "ccq_resp")
|
||||
|
||||
ccq.logger.Info("Creating pubsub topics", zap.String("request_topic", topic_req), zap.String("response_topic", topic_resp))
|
||||
ps, err := pubsub.NewGossipSub(ctx, ccq.h,
|
||||
// We only want to accept subscribes from peers in the allow list.
|
||||
pubsub.WithPeerFilter(func(peerID peer.ID, topic string) bool {
|
||||
if len(ccq.allowedPeers) == 0 {
|
||||
return true
|
||||
}
|
||||
if _, found := ccq.allowedPeers[peerID.String()]; found {
|
||||
return true
|
||||
}
|
||||
ccq.logger.Debug("Dropping subscribe attempt from unknown peer", zap.String("peerID", peerID.String()))
|
||||
return false
|
||||
}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new gossip sub for req: %w", err)
|
||||
}
|
||||
|
||||
// We want to join and subscribe to the request topic. We will receive messages from there, but never write to it.
|
||||
ccq.th_req, err = ps.Join(topic_req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to join topic_req: %w", err)
|
||||
}
|
||||
|
||||
// We only want to join the response topic. We will only write to it.
|
||||
ccq.th_resp, err = ps.Join(topic_resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to join topic_resp: %w", err)
|
||||
}
|
||||
|
||||
// We only want to accept messages from peers in the allow list.
|
||||
err = ps.RegisterTopicValidator(topic_req, func(ctx context.Context, from peer.ID, msg *pubsub.Message) bool {
|
||||
if len(ccq.allowedPeers) == 0 {
|
||||
return true
|
||||
}
|
||||
if _, found := ccq.allowedPeers[from.String()]; found {
|
||||
return true
|
||||
}
|
||||
ccq.logger.Debug("Dropping message from unknown peer", zap.String("fromPeerID", from.String()))
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register message filter: %w", err)
|
||||
}
|
||||
|
||||
// Increase the buffer size to prevent failed delivery to slower subscribers
|
||||
ccq.sub, err = ccq.th_req.Subscribe(pubsub.WithBufferSize(1024))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe topic_req: %w", err)
|
||||
}
|
||||
|
||||
common.StartRunnable(ctx, errC, false, "ccqp2p_listener", func(ctx context.Context) error {
|
||||
return ccq.listener(ctx, signedQueryReqC)
|
||||
})
|
||||
|
||||
common.StartRunnable(ctx, errC, false, "ccqp2p_publisher", func(ctx context.Context) error {
|
||||
return ccq.publisher(ctx, gk, queryResponseReadC)
|
||||
})
|
||||
|
||||
ccq.logger.Info("Node has been started", zap.String("peer_id", ccq.h.ID().String()), zap.String("addrs", fmt.Sprintf("%v", ccq.h.Addrs())))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ccq *ccqP2p) close() {
|
||||
ccq.logger.Info("entering close")
|
||||
|
||||
if err := ccq.th_req.Close(); err != nil && !errors.Is(err, context.Canceled) {
|
||||
ccq.logger.Error("Error closing the topic_req", zap.Error(err))
|
||||
}
|
||||
if err := ccq.th_resp.Close(); err != nil && !errors.Is(err, context.Canceled) {
|
||||
ccq.logger.Error("Error closing the topic_req", zap.Error(err))
|
||||
}
|
||||
|
||||
ccq.sub.Cancel()
|
||||
|
||||
if err := ccq.h.Close(); err != nil {
|
||||
ccq.logger.Error("error closing the host", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (ccq *ccqP2p) listener(ctx context.Context, signedQueryReqC chan<- *gossipv1.SignedQueryRequest) error {
|
||||
for {
|
||||
envelope, err := ccq.sub.Next(ctx) // Note: sub.Next(ctx) will return an error once ctx is canceled
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to receive pubsub message: %w", err)
|
||||
}
|
||||
|
||||
var msg gossipv1.GossipMessage
|
||||
err = proto.Unmarshal(envelope.Data, &msg)
|
||||
if err != nil {
|
||||
ccq.logger.Info("received invalid message",
|
||||
zap.Binary("data", envelope.Data),
|
||||
zap.String("from", envelope.GetFrom().String()))
|
||||
ccqP2pMessagesReceived.WithLabelValues("invalid").Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
ccq.logger.Info("received message", //TODO: Change to Debug
|
||||
zap.Any("payload", msg.Message),
|
||||
zap.Binary("raw", envelope.Data),
|
||||
zap.String("from", envelope.GetFrom().String()))
|
||||
|
||||
switch m := msg.Message.(type) {
|
||||
case *gossipv1.GossipMessage_SignedQueryRequest:
|
||||
if err := query.PostSignedQueryRequest(signedQueryReqC, m.SignedQueryRequest); err != nil {
|
||||
ccq.logger.Warn("failed to handle query request", zap.Error(err))
|
||||
}
|
||||
default:
|
||||
ccqP2pMessagesReceived.WithLabelValues("unknown").Inc()
|
||||
ccq.logger.Warn("received unknown message type (running outdated software?)",
|
||||
zap.Any("payload", msg.Message),
|
||||
zap.Binary("raw", envelope.Data),
|
||||
zap.String("from", envelope.GetFrom().String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ccq *ccqP2p) publisher(ctx context.Context, gk *ecdsa.PrivateKey, queryResponseReadC <-chan *query.QueryResponsePublication) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case msg := <-queryResponseReadC:
|
||||
msgBytes, err := msg.Marshal()
|
||||
if err != nil {
|
||||
ccq.logger.Error("failed to marshal query response", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
digest := query.GetQueryResponseDigestFromBytes(msgBytes)
|
||||
sig, err := ethcrypto.Sign(digest.Bytes(), gk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
envelope := &gossipv1.GossipMessage{
|
||||
Message: &gossipv1.GossipMessage_SignedQueryResponse{
|
||||
SignedQueryResponse: &gossipv1.SignedQueryResponse{
|
||||
QueryResponse: msgBytes,
|
||||
Signature: sig,
|
||||
},
|
||||
},
|
||||
}
|
||||
b, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = ccq.th_resp.Publish(ctx, b)
|
||||
ccqP2pMessagesSent.Inc()
|
||||
if err != nil {
|
||||
ccq.logger.Error("failed to publish query response",
|
||||
zap.String("requestID", msg.RequestID()),
|
||||
zap.Any("query_response", msg),
|
||||
zap.Any("signature", sig),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
ccq.logger.Info("published signed query response", //TODO: Change to Debug
|
||||
zap.String("requestID", msg.RequestID()),
|
||||
zap.Any("query_response", msg),
|
||||
zap.Any("signature", sig),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/certusone/wormhole/node/pkg/accountant"
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
"github.com/certusone/wormhole/node/pkg/governor"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/version"
|
||||
eth_common "github.com/ethereum/go-ethereum/common"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
|
@ -249,6 +250,12 @@ func Run(
|
|||
components *Components,
|
||||
ibcFeaturesFunc func() string,
|
||||
gatewayRelayerEnabled bool,
|
||||
ccqEnabled bool,
|
||||
signedQueryReqC chan<- *gossipv1.SignedQueryRequest,
|
||||
queryResponseReadC <-chan *query.QueryResponsePublication,
|
||||
ccqBootstrapPeers string,
|
||||
ccqPort uint,
|
||||
ccqAllowedPeers string,
|
||||
) func(ctx context.Context) error {
|
||||
if components == nil {
|
||||
components = DefaultComponents()
|
||||
|
@ -342,6 +349,27 @@ func Run(
|
|||
|
||||
bootTime := time.Now()
|
||||
|
||||
if ccqEnabled {
|
||||
ccqErrC := make(chan error)
|
||||
ccq := newCcqRunP2p(logger, ccqAllowedPeers)
|
||||
if err := ccq.run(ctx, priv, gk, networkID, ccqBootstrapPeers, ccqPort, signedQueryReqC, queryResponseReadC, ccqErrC); err != nil {
|
||||
return fmt.Errorf("failed to start p2p for CCQ: %w", err)
|
||||
}
|
||||
defer ccq.close()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case ccqErr := <-ccqErrC:
|
||||
logger.Error("ccqp2p returned an error", zap.Error(ccqErr), zap.String("component", "ccqp2p"))
|
||||
rootCtxCancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Periodically run guardian state set cleanup.
|
||||
go func() {
|
||||
ticker := time.NewTicker(15 * time.Second)
|
||||
|
@ -403,6 +431,9 @@ func Run(
|
|||
if gatewayRelayerEnabled {
|
||||
features = append(features, "gwrelayer")
|
||||
}
|
||||
if ccqEnabled {
|
||||
features = append(features, "ccq")
|
||||
}
|
||||
|
||||
heartbeat := &gossipv1.Heartbeat{
|
||||
NodeName: nodeName,
|
||||
|
|
|
@ -185,5 +185,11 @@ func startGuardian(t *testing.T, ctx context.Context, g *G) {
|
|||
g.components,
|
||||
nil, // ibc feature string
|
||||
false, // gateway relayer enabled
|
||||
false, // ccqEnabled
|
||||
nil, // signed query request channel
|
||||
nil, // query response channel
|
||||
"", // query bootstrap peers
|
||||
0, // query port
|
||||
"", // query allowed peers
|
||||
))
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ type GossipMessage struct {
|
|||
// *GossipMessage_SignedBatchVaaWithQuorum
|
||||
// *GossipMessage_SignedChainGovernorConfig
|
||||
// *GossipMessage_SignedChainGovernorStatus
|
||||
// *GossipMessage_SignedQueryRequest
|
||||
// *GossipMessage_SignedQueryResponse
|
||||
Message isGossipMessage_Message `protobuf_oneof:"message"`
|
||||
}
|
||||
|
||||
|
@ -133,6 +135,20 @@ func (x *GossipMessage) GetSignedChainGovernorStatus() *SignedChainGovernorStatu
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *GossipMessage) GetSignedQueryRequest() *SignedQueryRequest {
|
||||
if x, ok := x.GetMessage().(*GossipMessage_SignedQueryRequest); ok {
|
||||
return x.SignedQueryRequest
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *GossipMessage) GetSignedQueryResponse() *SignedQueryResponse {
|
||||
if x, ok := x.GetMessage().(*GossipMessage_SignedQueryResponse); ok {
|
||||
return x.SignedQueryResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isGossipMessage_Message interface {
|
||||
isGossipMessage_Message()
|
||||
}
|
||||
|
@ -169,6 +185,14 @@ type GossipMessage_SignedChainGovernorStatus struct {
|
|||
SignedChainGovernorStatus *SignedChainGovernorStatus `protobuf:"bytes,9,opt,name=signed_chain_governor_status,json=signedChainGovernorStatus,proto3,oneof"`
|
||||
}
|
||||
|
||||
type GossipMessage_SignedQueryRequest struct {
|
||||
SignedQueryRequest *SignedQueryRequest `protobuf:"bytes,10,opt,name=signed_query_request,json=signedQueryRequest,proto3,oneof"`
|
||||
}
|
||||
|
||||
type GossipMessage_SignedQueryResponse struct {
|
||||
SignedQueryResponse *SignedQueryResponse `protobuf:"bytes,11,opt,name=signed_query_response,json=signedQueryResponse,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*GossipMessage_SignedObservation) isGossipMessage_Message() {}
|
||||
|
||||
func (*GossipMessage_SignedHeartbeat) isGossipMessage_Message() {}
|
||||
|
@ -185,6 +209,10 @@ func (*GossipMessage_SignedChainGovernorConfig) isGossipMessage_Message() {}
|
|||
|
||||
func (*GossipMessage_SignedChainGovernorStatus) isGossipMessage_Message() {}
|
||||
|
||||
func (*GossipMessage_SignedQueryRequest) isGossipMessage_Message() {}
|
||||
|
||||
func (*GossipMessage_SignedQueryResponse) isGossipMessage_Message() {}
|
||||
|
||||
type SignedHeartbeat struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -1127,6 +1155,120 @@ func (x *ChainGovernorStatus) GetChains() []*ChainGovernorStatus_Chain {
|
|||
return nil
|
||||
}
|
||||
|
||||
type SignedQueryRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Serialized QueryRequest message.
|
||||
QueryRequest []byte `protobuf:"bytes,1,opt,name=query_request,json=queryRequest,proto3" json:"query_request,omitempty"`
|
||||
// ECDSA signature using the requestor's public key.
|
||||
Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SignedQueryRequest) Reset() {
|
||||
*x = SignedQueryRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SignedQueryRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SignedQueryRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SignedQueryRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[13]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SignedQueryRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SignedQueryRequest) Descriptor() ([]byte, []int) {
|
||||
return file_gossip_v1_gossip_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *SignedQueryRequest) GetQueryRequest() []byte {
|
||||
if x != nil {
|
||||
return x.QueryRequest
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SignedQueryRequest) GetSignature() []byte {
|
||||
if x != nil {
|
||||
return x.Signature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SignedQueryResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Serialized QueryResponse message.
|
||||
QueryResponse []byte `protobuf:"bytes,1,opt,name=query_response,json=queryResponse,proto3" json:"query_response,omitempty"`
|
||||
// ECDSA signature using the node's guardian public key.
|
||||
Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SignedQueryResponse) Reset() {
|
||||
*x = SignedQueryResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SignedQueryResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SignedQueryResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SignedQueryResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[14]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SignedQueryResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SignedQueryResponse) Descriptor() ([]byte, []int) {
|
||||
return file_gossip_v1_gossip_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
func (x *SignedQueryResponse) GetQueryResponse() []byte {
|
||||
if x != nil {
|
||||
return x.QueryResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SignedQueryResponse) GetSignature() []byte {
|
||||
if x != nil {
|
||||
return x.Signature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Heartbeat_Network struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -1145,7 +1287,7 @@ type Heartbeat_Network struct {
|
|||
func (x *Heartbeat_Network) Reset() {
|
||||
*x = Heartbeat_Network{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[13]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1158,7 +1300,7 @@ func (x *Heartbeat_Network) String() string {
|
|||
func (*Heartbeat_Network) ProtoMessage() {}
|
||||
|
||||
func (x *Heartbeat_Network) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[13]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[15]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1215,7 +1357,7 @@ type ChainGovernorConfig_Chain struct {
|
|||
func (x *ChainGovernorConfig_Chain) Reset() {
|
||||
*x = ChainGovernorConfig_Chain{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[14]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1228,7 +1370,7 @@ func (x *ChainGovernorConfig_Chain) String() string {
|
|||
func (*ChainGovernorConfig_Chain) ProtoMessage() {}
|
||||
|
||||
func (x *ChainGovernorConfig_Chain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[14]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[16]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1278,7 +1420,7 @@ type ChainGovernorConfig_Token struct {
|
|||
func (x *ChainGovernorConfig_Token) Reset() {
|
||||
*x = ChainGovernorConfig_Token{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[15]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1291,7 +1433,7 @@ func (x *ChainGovernorConfig_Token) String() string {
|
|||
func (*ChainGovernorConfig_Token) ProtoMessage() {}
|
||||
|
||||
func (x *ChainGovernorConfig_Token) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[15]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[17]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1342,7 +1484,7 @@ type ChainGovernorStatus_EnqueuedVAA struct {
|
|||
func (x *ChainGovernorStatus_EnqueuedVAA) Reset() {
|
||||
*x = ChainGovernorStatus_EnqueuedVAA{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[16]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[18]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1355,7 +1497,7 @@ func (x *ChainGovernorStatus_EnqueuedVAA) String() string {
|
|||
func (*ChainGovernorStatus_EnqueuedVAA) ProtoMessage() {}
|
||||
|
||||
func (x *ChainGovernorStatus_EnqueuedVAA) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[16]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[18]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1412,7 +1554,7 @@ type ChainGovernorStatus_Emitter struct {
|
|||
func (x *ChainGovernorStatus_Emitter) Reset() {
|
||||
*x = ChainGovernorStatus_Emitter{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[17]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[19]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1425,7 +1567,7 @@ func (x *ChainGovernorStatus_Emitter) String() string {
|
|||
func (*ChainGovernorStatus_Emitter) ProtoMessage() {}
|
||||
|
||||
func (x *ChainGovernorStatus_Emitter) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[17]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[19]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1475,7 +1617,7 @@ type ChainGovernorStatus_Chain struct {
|
|||
func (x *ChainGovernorStatus_Chain) Reset() {
|
||||
*x = ChainGovernorStatus_Chain{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[18]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1488,7 +1630,7 @@ func (x *ChainGovernorStatus_Chain) String() string {
|
|||
func (*ChainGovernorStatus_Chain) ProtoMessage() {}
|
||||
|
||||
func (x *ChainGovernorStatus_Chain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[18]
|
||||
mi := &file_gossip_v1_gossip_proto_msgTypes[20]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1530,7 +1672,7 @@ var File_gossip_v1_gossip_proto protoreflect.FileDescriptor
|
|||
var file_gossip_v1_gossip_proto_rawDesc = []byte{
|
||||
0x0a, 0x16, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x6f, 0x73, 0x73,
|
||||
0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70,
|
||||
0x2e, 0x76, 0x31, 0x22, 0x86, 0x06, 0x0a, 0x0d, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4d, 0x65,
|
||||
0x2e, 0x76, 0x31, 0x22, 0xaf, 0x07, 0x0a, 0x0d, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f,
|
||||
0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69,
|
||||
|
@ -1578,180 +1720,202 @@ var file_gossip_v1_gossip_proto_rawDesc = []byte{
|
|||
0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x19, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x68,
|
||||
0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x72, 0x0a, 0x0f,
|
||||
0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x1c, 0x0a,
|
||||
0x73, 0x12, 0x51, 0x0a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x65, 0x72,
|
||||
0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1d, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e,
|
||||
0x65, 0x64, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,
|
||||
0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x15, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x71,
|
||||
0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x0b, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e,
|
||||
0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x13, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x51, 0x75, 0x65,
|
||||
0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x72, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x48,
|
||||
0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72,
|
||||
0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x68, 0x65, 0x61,
|
||||
0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74,
|
||||
0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||
0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e,
|
||||
0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x67, 0x75, 0x61,
|
||||
0x72, 0x64, 0x69, 0x61, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x22, 0xbb, 0x03, 0x0a, 0x09, 0x48, 0x65,
|
||||
0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1c,
|
||||
0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x38, 0x0a, 0x08,
|
||||
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c,
|
||||
0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74,
|
||||
0x62, 0x65, 0x61, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65,
|
||||
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x61, 0x64, 0x64,
|
||||
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61,
|
||||
0x6e, 0x41, 0x64, 0x64, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62,
|
||||
0x6f, 0x6f, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08,
|
||||
0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x70, 0x32, 0x70, 0x5f,
|
||||
0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70,
|
||||
0x32, 0x70, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x1a, 0x7d, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77,
|
||||
0x6f, 0x72, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x72, 0x72,
|
||||
0x6f, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x91, 0x01, 0x0a, 0x11, 0x53, 0x69, 0x67, 0x6e,
|
||||
0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x61, 0x64, 0x64,
|
||||
0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75,
|
||||
0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74,
|
||||
0x75, 0x72, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x64, 0x22, 0x27, 0x0a, 0x13, 0x53,
|
||||
0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x41, 0x41, 0x57, 0x69, 0x74, 0x68, 0x51, 0x75, 0x6f, 0x72,
|
||||
0x75, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x03, 0x76, 0x61, 0x61, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f,
|
||||
0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x2f, 0x0a, 0x13, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12,
|
||||
0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,
|
||||
0x12, 0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x61, 0x64, 0x64,
|
||||
0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61,
|
||||
0x6e, 0x41, 0x64, 0x64, 0x72, 0x22, 0x48, 0x0a, 0x12, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63,
|
||||
0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63,
|
||||
0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73,
|
||||
0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22,
|
||||
0xbf, 0x01, 0x0a, 0x16, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f,
|
||||
0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64,
|
||||
0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61,
|
||||
0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,
|
||||
0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x74, 0x63, 0x68, 0x49,
|
||||
0x64, 0x22, 0x98, 0x01, 0x0a, 0x18, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x42, 0x61, 0x74, 0x63,
|
||||
0x68, 0x56, 0x41, 0x41, 0x57, 0x69, 0x74, 0x68, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x12, 0x1b,
|
||||
0x0a, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x76, 0x61, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x56, 0x61, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x63,
|
||||
0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63,
|
||||
0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e,
|
||||
0x6f, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63,
|
||||
0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x74, 0x63, 0x68, 0x49, 0x64, 0x22, 0x76, 0x0a, 0x19,
|
||||
0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72,
|
||||
0x6e, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12,
|
||||
0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e,
|
||||
0x41, 0x64, 0x64, 0x72, 0x22, 0xd1, 0x03, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f,
|
||||
0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68,
|
||||
0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12,
|
||||
0x3c, 0x0a, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x24, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x69,
|
||||
0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x1a, 0x7b, 0x0a,
|
||||
0x05, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49,
|
||||
0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6c, 0x69,
|
||||
0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x61, 0x6c, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x62, 0x69, 0x67, 0x5f,
|
||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x7a, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x62, 0x69, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x7a, 0x65, 0x1a, 0x6c, 0x0a, 0x05, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x63, 0x68,
|
||||
0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6f, 0x72,
|
||||
0x69, 0x67, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6f,
|
||||
0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||
0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x02, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x22, 0x76, 0x0a, 0x19, 0x53, 0x69, 0x67, 0x6e,
|
||||
0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a,
|
||||
0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67,
|
||||
0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x0c, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x41, 0x64, 0x64, 0x72,
|
||||
0x22, 0xbb, 0x03, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x1b,
|
||||
0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x12, 0x38, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18,
|
||||
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77,
|
||||
0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64,
|
||||
0x69, 0x61, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
||||
0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x12, 0x25, 0x0a, 0x0e,
|
||||
0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x6f, 0x6f, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18,
|
||||
0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12,
|
||||
0x1e, 0x0a, 0x0b, 0x70, 0x32, 0x70, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x09,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x32, 0x70, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x1a,
|
||||
0x7d, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65,
|
||||
0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67,
|
||||
0x68, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x04, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x91,
|
||||
0x01, 0x0a, 0x11, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09,
|
||||
0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78,
|
||||
0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x78, 0x48,
|
||||
0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x49, 0x64, 0x22, 0x27, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x41, 0x41, 0x57,
|
||||
0x69, 0x74, 0x68, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x61,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x76, 0x61, 0x61, 0x22, 0x8e, 0x01, 0x0a, 0x18,
|
||||
0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x6f, 0x62, 0x73, 0x65,
|
||||
0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67,
|
||||
0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69,
|
||||
0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64,
|
||||
0x69, 0x61, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c,
|
||||
0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x22, 0x48, 0x0a, 0x12,
|
||||
0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a,
|
||||
0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,
|
||||
0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, 0xbf, 0x01, 0x0a, 0x16, 0x53, 0x69, 0x67, 0x6e, 0x65,
|
||||
0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67,
|
||||
0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69,
|
||||
0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08,
|
||||
0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
|
||||
0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65,
|
||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x19, 0x0a,
|
||||
0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x07, 0x62, 0x61, 0x74, 0x63, 0x68, 0x49, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x18, 0x53, 0x69, 0x67,
|
||||
0x6e, 0x65, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x56, 0x41, 0x41, 0x57, 0x69, 0x74, 0x68, 0x51,
|
||||
0x75, 0x6f, 0x72, 0x75, 0x6d, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x76,
|
||||
0x61, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x61, 0x74, 0x63, 0x68, 0x56,
|
||||
0x61, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x13, 0x0a,
|
||||
0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78,
|
||||
0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x74, 0x63,
|
||||
0x68, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x74, 0x63,
|
||||
0x68, 0x49, 0x64, 0x22, 0x76, 0x0a, 0x19, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x68, 0x61,
|
||||
0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e,
|
||||
0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67,
|
||||
0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69,
|
||||
0x61, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x67,
|
||||
0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x22, 0xd1, 0x03, 0x0a, 0x13,
|
||||
0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x69,
|
||||
0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69,
|
||||
0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e,
|
||||
0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x06,
|
||||
0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
|
||||
0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x74, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x73, 0x1a, 0x7b, 0x0a, 0x05, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x0a,
|
||||
0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
|
||||
0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12,
|
||||
0x30, 0x0a, 0x14, 0x62, 0x69, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x62,
|
||||
0x69, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x7a,
|
||||
0x65, 0x1a, 0x6c, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6f, 0x72,
|
||||
0x69, 0x67, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e,
|
||||
0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x61, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67,
|
||||
0x69, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69,
|
||||
0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x22,
|
||||
0x76, 0x0a, 0x19, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f,
|
||||
0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75,
|
||||
0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x61,
|
||||
0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x67, 0x75, 0x61, 0x72, 0x64,
|
||||
0x69, 0x61, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x22, 0x98, 0x05, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x69,
|
||||
0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||
0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x04,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x63, 0x68, 0x61, 0x69,
|
||||
0x6e, 0x73, 0x1a, 0x8c, 0x01, 0x0a, 0x0b, 0x45, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x56,
|
||||
0x41, 0x41, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x21,
|
||||
0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x61, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68,
|
||||
0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73,
|
||||
0x68, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x45, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a,
|
||||
0x0f, 0x65, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x41,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
|
||||
0x65, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x61, 0x73, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x04, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x6e, 0x71, 0x75, 0x65, 0x75,
|
||||
0x65, 0x64, 0x56, 0x61, 0x61, 0x73, 0x12, 0x4f, 0x0a, 0x0d, 0x65, 0x6e, 0x71, 0x75, 0x65, 0x75,
|
||||
0x65, 0x64, 0x5f, 0x76, 0x61, 0x61, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e,
|
||||
0x22, 0x98, 0x05, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e,
|
||||
0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65,
|
||||
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64,
|
||||
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3c, 0x0a,
|
||||
0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e,
|
||||
0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47,
|
||||
0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x45, 0x6e,
|
||||
0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x56, 0x41, 0x41, 0x52, 0x0c, 0x65, 0x6e, 0x71, 0x75, 0x65,
|
||||
0x75, 0x65, 0x64, 0x56, 0x61, 0x61, 0x73, 0x1a, 0xa8, 0x01, 0x0a, 0x05, 0x43, 0x68, 0x61, 0x69,
|
||||
0x6e, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1c,
|
||||
0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x76, 0x61,
|
||||
0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x42,
|
||||
0x0a, 0x08, 0x65, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x26, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61,
|
||||
0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x2e, 0x45, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x52, 0x08, 0x65, 0x6d, 0x69, 0x74, 0x74, 0x65,
|
||||
0x72, 0x73, 0x42, 0x41, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x63, 0x65, 0x72, 0x74, 0x75, 0x73, 0x6f, 0x6e, 0x65, 0x2f, 0x77, 0x6f, 0x72, 0x6d, 0x68,
|
||||
0x6f, 0x6c, 0x65, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x6f, 0x73,
|
||||
0x73, 0x69, 0x70, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x43, 0x68,
|
||||
0x61, 0x69, 0x6e, 0x52, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x1a, 0x8c, 0x01, 0x0a, 0x0b,
|
||||
0x45, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x56, 0x41, 0x41, 0x12, 0x1a, 0x0a, 0x08, 0x73,
|
||||
0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73,
|
||||
0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61,
|
||||
0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x72,
|
||||
0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x04, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x45,
|
||||
0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6d, 0x69, 0x74, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0e, 0x65, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
|
||||
0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65,
|
||||
0x64, 0x5f, 0x76, 0x61, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x74, 0x6f,
|
||||
0x74, 0x61, 0x6c, 0x45, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x56, 0x61, 0x61, 0x73, 0x12,
|
||||
0x4f, 0x0a, 0x0d, 0x65, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x61, 0x73,
|
||||
0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72, 0x6e, 0x6f, 0x72,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x45, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x56,
|
||||
0x41, 0x41, 0x52, 0x0c, 0x65, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x56, 0x61, 0x61, 0x73,
|
||||
0x1a, 0xa8, 0x01, 0x0a, 0x05, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68,
|
||||
0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x68,
|
||||
0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1c, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69,
|
||||
0x6e, 0x67, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x6f, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4e,
|
||||
0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x42, 0x0a, 0x08, 0x65, 0x6d, 0x69, 0x74, 0x74,
|
||||
0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x6f, 0x73, 0x73,
|
||||
0x69, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x47, 0x6f, 0x76, 0x65, 0x72,
|
||||
0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x45, 0x6d, 0x69, 0x74, 0x74, 0x65,
|
||||
0x72, 0x52, 0x08, 0x65, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x73, 0x22, 0x57, 0x0a, 0x12, 0x53,
|
||||
0x69, 0x67, 0x6e, 0x65, 0x64, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x23, 0x0a, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x71, 0x75, 0x65, 0x72, 0x79, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74,
|
||||
0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||
0x74, 0x75, 0x72, 0x65, 0x22, 0x5a, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x51, 0x75,
|
||||
0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x71,
|
||||
0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,
|
||||
0x42, 0x41, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
|
||||
0x65, 0x72, 0x74, 0x75, 0x73, 0x6f, 0x6e, 0x65, 0x2f, 0x77, 0x6f, 0x72, 0x6d, 0x68, 0x6f, 0x6c,
|
||||
0x65, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2f, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x6f, 0x73, 0x73, 0x69,
|
||||
0x70, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -1766,7 +1930,7 @@ func file_gossip_v1_gossip_proto_rawDescGZIP() []byte {
|
|||
return file_gossip_v1_gossip_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_gossip_v1_gossip_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
|
||||
var file_gossip_v1_gossip_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
|
||||
var file_gossip_v1_gossip_proto_goTypes = []interface{}{
|
||||
(*GossipMessage)(nil), // 0: gossip.v1.GossipMessage
|
||||
(*SignedHeartbeat)(nil), // 1: gossip.v1.SignedHeartbeat
|
||||
|
@ -1781,12 +1945,14 @@ var file_gossip_v1_gossip_proto_goTypes = []interface{}{
|
|||
(*ChainGovernorConfig)(nil), // 10: gossip.v1.ChainGovernorConfig
|
||||
(*SignedChainGovernorStatus)(nil), // 11: gossip.v1.SignedChainGovernorStatus
|
||||
(*ChainGovernorStatus)(nil), // 12: gossip.v1.ChainGovernorStatus
|
||||
(*Heartbeat_Network)(nil), // 13: gossip.v1.Heartbeat.Network
|
||||
(*ChainGovernorConfig_Chain)(nil), // 14: gossip.v1.ChainGovernorConfig.Chain
|
||||
(*ChainGovernorConfig_Token)(nil), // 15: gossip.v1.ChainGovernorConfig.Token
|
||||
(*ChainGovernorStatus_EnqueuedVAA)(nil), // 16: gossip.v1.ChainGovernorStatus.EnqueuedVAA
|
||||
(*ChainGovernorStatus_Emitter)(nil), // 17: gossip.v1.ChainGovernorStatus.Emitter
|
||||
(*ChainGovernorStatus_Chain)(nil), // 18: gossip.v1.ChainGovernorStatus.Chain
|
||||
(*SignedQueryRequest)(nil), // 13: gossip.v1.SignedQueryRequest
|
||||
(*SignedQueryResponse)(nil), // 14: gossip.v1.SignedQueryResponse
|
||||
(*Heartbeat_Network)(nil), // 15: gossip.v1.Heartbeat.Network
|
||||
(*ChainGovernorConfig_Chain)(nil), // 16: gossip.v1.ChainGovernorConfig.Chain
|
||||
(*ChainGovernorConfig_Token)(nil), // 17: gossip.v1.ChainGovernorConfig.Token
|
||||
(*ChainGovernorStatus_EnqueuedVAA)(nil), // 18: gossip.v1.ChainGovernorStatus.EnqueuedVAA
|
||||
(*ChainGovernorStatus_Emitter)(nil), // 19: gossip.v1.ChainGovernorStatus.Emitter
|
||||
(*ChainGovernorStatus_Chain)(nil), // 20: gossip.v1.ChainGovernorStatus.Chain
|
||||
}
|
||||
var file_gossip_v1_gossip_proto_depIdxs = []int32{
|
||||
3, // 0: gossip.v1.GossipMessage.signed_observation:type_name -> gossip.v1.SignedObservation
|
||||
|
@ -1797,17 +1963,19 @@ var file_gossip_v1_gossip_proto_depIdxs = []int32{
|
|||
8, // 5: gossip.v1.GossipMessage.signed_batch_vaa_with_quorum:type_name -> gossip.v1.SignedBatchVAAWithQuorum
|
||||
9, // 6: gossip.v1.GossipMessage.signed_chain_governor_config:type_name -> gossip.v1.SignedChainGovernorConfig
|
||||
11, // 7: gossip.v1.GossipMessage.signed_chain_governor_status:type_name -> gossip.v1.SignedChainGovernorStatus
|
||||
13, // 8: gossip.v1.Heartbeat.networks:type_name -> gossip.v1.Heartbeat.Network
|
||||
14, // 9: gossip.v1.ChainGovernorConfig.chains:type_name -> gossip.v1.ChainGovernorConfig.Chain
|
||||
15, // 10: gossip.v1.ChainGovernorConfig.tokens:type_name -> gossip.v1.ChainGovernorConfig.Token
|
||||
18, // 11: gossip.v1.ChainGovernorStatus.chains:type_name -> gossip.v1.ChainGovernorStatus.Chain
|
||||
16, // 12: gossip.v1.ChainGovernorStatus.Emitter.enqueued_vaas:type_name -> gossip.v1.ChainGovernorStatus.EnqueuedVAA
|
||||
17, // 13: gossip.v1.ChainGovernorStatus.Chain.emitters:type_name -> gossip.v1.ChainGovernorStatus.Emitter
|
||||
14, // [14:14] is the sub-list for method output_type
|
||||
14, // [14:14] is the sub-list for method input_type
|
||||
14, // [14:14] is the sub-list for extension type_name
|
||||
14, // [14:14] is the sub-list for extension extendee
|
||||
0, // [0:14] is the sub-list for field type_name
|
||||
13, // 8: gossip.v1.GossipMessage.signed_query_request:type_name -> gossip.v1.SignedQueryRequest
|
||||
14, // 9: gossip.v1.GossipMessage.signed_query_response:type_name -> gossip.v1.SignedQueryResponse
|
||||
15, // 10: gossip.v1.Heartbeat.networks:type_name -> gossip.v1.Heartbeat.Network
|
||||
16, // 11: gossip.v1.ChainGovernorConfig.chains:type_name -> gossip.v1.ChainGovernorConfig.Chain
|
||||
17, // 12: gossip.v1.ChainGovernorConfig.tokens:type_name -> gossip.v1.ChainGovernorConfig.Token
|
||||
20, // 13: gossip.v1.ChainGovernorStatus.chains:type_name -> gossip.v1.ChainGovernorStatus.Chain
|
||||
18, // 14: gossip.v1.ChainGovernorStatus.Emitter.enqueued_vaas:type_name -> gossip.v1.ChainGovernorStatus.EnqueuedVAA
|
||||
19, // 15: gossip.v1.ChainGovernorStatus.Chain.emitters:type_name -> gossip.v1.ChainGovernorStatus.Emitter
|
||||
16, // [16:16] is the sub-list for method output_type
|
||||
16, // [16:16] is the sub-list for method input_type
|
||||
16, // [16:16] is the sub-list for extension type_name
|
||||
16, // [16:16] is the sub-list for extension extendee
|
||||
0, // [0:16] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_gossip_v1_gossip_proto_init() }
|
||||
|
@ -1973,7 +2141,7 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Heartbeat_Network); i {
|
||||
switch v := v.(*SignedQueryRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1985,7 +2153,7 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChainGovernorConfig_Chain); i {
|
||||
switch v := v.(*SignedQueryResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1997,7 +2165,7 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChainGovernorConfig_Token); i {
|
||||
switch v := v.(*Heartbeat_Network); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -2009,7 +2177,7 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChainGovernorStatus_EnqueuedVAA); i {
|
||||
switch v := v.(*ChainGovernorConfig_Chain); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -2021,7 +2189,7 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChainGovernorStatus_Emitter); i {
|
||||
switch v := v.(*ChainGovernorConfig_Token); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -2033,6 +2201,30 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChainGovernorStatus_EnqueuedVAA); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChainGovernorStatus_Emitter); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_gossip_v1_gossip_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChainGovernorStatus_Chain); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2054,6 +2246,8 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
(*GossipMessage_SignedBatchVaaWithQuorum)(nil),
|
||||
(*GossipMessage_SignedChainGovernorConfig)(nil),
|
||||
(*GossipMessage_SignedChainGovernorStatus)(nil),
|
||||
(*GossipMessage_SignedQueryRequest)(nil),
|
||||
(*GossipMessage_SignedQueryResponse)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
|
@ -2061,7 +2255,7 @@ func file_gossip_v1_gossip_proto_init() {
|
|||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_gossip_v1_gossip_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 19,
|
||||
NumMessages: 21,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN WORMHOLE GUARDIAN PRIVATE KEY-----
|
||||
PublicKey: 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
|
||||
Description: auto-generated deterministic devnet key
|
||||
|
||||
CiDPsSMDoZzeWAu03XcWObDSa8aDU2RVcajP9RarLuEToBAB
|
||||
=VN/A
|
||||
-----END WORMHOLE GUARDIAN PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package query
|
||||
|
||||
func makeChannelPair[T any](cap int) (<-chan T, chan<- T) {
|
||||
out := make(chan T, cap)
|
||||
return out, out
|
||||
}
|
|
@ -0,0 +1,421 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
ethCrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
// RequestTimeout indicates how long before a request is considered to have timed out.
|
||||
RequestTimeout = 1 * time.Minute
|
||||
|
||||
// RetryInterval specifies how long we will wait between retry intervals. This is the interval of our ticker.
|
||||
RetryInterval = 10 * time.Second
|
||||
|
||||
// SignedQueryRequestChannelSize is the buffer size of the incoming query request channel.
|
||||
SignedQueryRequestChannelSize = 50
|
||||
|
||||
// QueryRequestBufferSize is the buffer size of the per-network query request channel.
|
||||
QueryRequestBufferSize = 25
|
||||
)
|
||||
|
||||
func NewQueryHandler(
|
||||
logger *zap.Logger,
|
||||
env common.Environment,
|
||||
allowedRequestorsStr string,
|
||||
signedQueryReqC <-chan *gossipv1.SignedQueryRequest,
|
||||
chainQueryReqC map[vaa.ChainID]chan *PerChainQueryInternal,
|
||||
queryResponseReadC <-chan *PerChainQueryResponseInternal,
|
||||
queryResponseWriteC chan<- *QueryResponsePublication,
|
||||
) *QueryHandler {
|
||||
return &QueryHandler{
|
||||
logger: logger.With(zap.String("component", "ccq")),
|
||||
env: env,
|
||||
allowedRequestorsStr: allowedRequestorsStr,
|
||||
signedQueryReqC: signedQueryReqC,
|
||||
chainQueryReqC: chainQueryReqC,
|
||||
queryResponseReadC: queryResponseReadC,
|
||||
queryResponseWriteC: queryResponseWriteC,
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// QueryHandler defines the cross chain query handler.
|
||||
QueryHandler struct {
|
||||
logger *zap.Logger
|
||||
env common.Environment
|
||||
allowedRequestorsStr string
|
||||
signedQueryReqC <-chan *gossipv1.SignedQueryRequest
|
||||
chainQueryReqC map[vaa.ChainID]chan *PerChainQueryInternal
|
||||
queryResponseReadC <-chan *PerChainQueryResponseInternal
|
||||
queryResponseWriteC chan<- *QueryResponsePublication
|
||||
allowedRequestors map[ethCommon.Address]struct{}
|
||||
}
|
||||
|
||||
// pendingQuery is the cache entry for a given query.
|
||||
pendingQuery struct {
|
||||
signedRequest *gossipv1.SignedQueryRequest
|
||||
request *QueryRequest
|
||||
requestID string
|
||||
receiveTime time.Time
|
||||
queries []*perChainQuery
|
||||
responses []*PerChainQueryResponseInternal
|
||||
|
||||
// respPub is only populated when we need to retry sending the response to p2p.
|
||||
respPub *QueryResponsePublication
|
||||
}
|
||||
|
||||
// perChainQuery is the data associated with a single per chain query in a query request.
|
||||
perChainQuery struct {
|
||||
req *PerChainQueryInternal
|
||||
channel chan *PerChainQueryInternal
|
||||
lastUpdateTime time.Time
|
||||
}
|
||||
)
|
||||
|
||||
// Start initializes the query handler and starts the runnable.
|
||||
func (qh *QueryHandler) Start(ctx context.Context) error {
|
||||
qh.logger.Debug("entering Start", zap.String("enforceFlag", qh.allowedRequestorsStr))
|
||||
|
||||
var err error
|
||||
qh.allowedRequestors, err = parseAllowedRequesters(qh.allowedRequestorsStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse allowed requesters: %w", err)
|
||||
}
|
||||
|
||||
if err := supervisor.Run(ctx, "query_handler", common.WrapWithScissors(qh.handleQueryRequests, "query_handler")); err != nil {
|
||||
return fmt.Errorf("failed to start query handler routine: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleQueryRequests multiplexes observation requests to the appropriate chain
|
||||
func (qh *QueryHandler) handleQueryRequests(ctx context.Context) error {
|
||||
return handleQueryRequestsImpl(ctx, qh.logger, qh.signedQueryReqC, qh.chainQueryReqC, qh.allowedRequestors, qh.queryResponseReadC, qh.queryResponseWriteC, qh.env, RequestTimeout, RetryInterval)
|
||||
}
|
||||
|
||||
// handleQueryRequestsImpl allows instantiating the handler in the test environment with shorter timeout and retry parameters.
|
||||
func handleQueryRequestsImpl(
|
||||
ctx context.Context,
|
||||
logger *zap.Logger,
|
||||
signedQueryReqC <-chan *gossipv1.SignedQueryRequest,
|
||||
chainQueryReqC map[vaa.ChainID]chan *PerChainQueryInternal,
|
||||
allowedRequestors map[ethCommon.Address]struct{},
|
||||
queryResponseReadC <-chan *PerChainQueryResponseInternal,
|
||||
queryResponseWriteC chan<- *QueryResponsePublication,
|
||||
env common.Environment,
|
||||
requestTimeoutImpl time.Duration,
|
||||
retryIntervalImpl time.Duration,
|
||||
) error {
|
||||
qLogger := logger.With(zap.String("component", "ccqhandler"))
|
||||
qLogger.Info("cross chain queries are enabled", zap.Any("allowedRequestors", allowedRequestors), zap.String("env", string(env)))
|
||||
|
||||
pendingQueries := make(map[string]*pendingQuery) // Key is requestID.
|
||||
|
||||
// CCQ is currently only supported on EVM.
|
||||
supportedChains := map[vaa.ChainID]struct{}{
|
||||
vaa.ChainIDEthereum: {},
|
||||
vaa.ChainIDBSC: {},
|
||||
vaa.ChainIDPolygon: {},
|
||||
vaa.ChainIDAvalanche: {},
|
||||
vaa.ChainIDOasis: {},
|
||||
vaa.ChainIDAurora: {},
|
||||
vaa.ChainIDFantom: {},
|
||||
vaa.ChainIDKarura: {},
|
||||
vaa.ChainIDAcala: {},
|
||||
vaa.ChainIDKlaytn: {},
|
||||
vaa.ChainIDCelo: {},
|
||||
vaa.ChainIDMoonbeam: {},
|
||||
vaa.ChainIDNeon: {},
|
||||
vaa.ChainIDArbitrum: {},
|
||||
vaa.ChainIDOptimism: {},
|
||||
vaa.ChainIDBase: {},
|
||||
vaa.ChainIDSepolia: {},
|
||||
}
|
||||
|
||||
// But we don't want to allow CCQ if the chain is not enabled.
|
||||
for chainID := range supportedChains {
|
||||
if _, exists := chainQueryReqC[chainID]; !exists {
|
||||
delete(supportedChains, chainID)
|
||||
} else {
|
||||
logger.Info("queries supported on chain", zap.Stringer("chainID", chainID))
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(retryIntervalImpl)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
|
||||
case signedRequest := <-signedQueryReqC: // Inbound query request.
|
||||
// requestor validation happens here
|
||||
// request type validation is currently handled by the watcher
|
||||
// in the future, it may be worthwhile to catch certain types of
|
||||
// invalid requests here for tracking purposes
|
||||
// e.g.
|
||||
// - length check on "signature" 65 bytes
|
||||
// - length check on "to" address 20 bytes
|
||||
// - valid "block" strings
|
||||
|
||||
requestID := hex.EncodeToString(signedRequest.Signature)
|
||||
digest := QueryRequestDigest(env, signedRequest.QueryRequest)
|
||||
|
||||
qLogger.Info("received a query request", zap.String("requestID", requestID))
|
||||
|
||||
signerBytes, err := ethCrypto.Ecrecover(digest.Bytes(), signedRequest.Signature)
|
||||
if err != nil {
|
||||
qLogger.Error("failed to recover public key", zap.String("requestID", requestID))
|
||||
continue
|
||||
}
|
||||
|
||||
signerAddress := ethCommon.BytesToAddress(ethCrypto.Keccak256(signerBytes[1:])[12:])
|
||||
|
||||
if _, exists := allowedRequestors[signerAddress]; !exists {
|
||||
qLogger.Error("invalid requestor", zap.String("requestor", signerAddress.Hex()), zap.String("requestID", requestID))
|
||||
continue
|
||||
}
|
||||
|
||||
// Make sure this is not a duplicate request. TODO: Should we do something smarter here than just dropping the duplicate?
|
||||
if oldReq, exists := pendingQueries[requestID]; exists {
|
||||
qLogger.Warn("dropping duplicate query request", zap.String("requestID", requestID), zap.Stringer("origRecvTime", oldReq.receiveTime))
|
||||
continue
|
||||
}
|
||||
|
||||
var queryRequest QueryRequest
|
||||
err = queryRequest.Unmarshal(signedRequest.QueryRequest)
|
||||
if err != nil {
|
||||
qLogger.Error("failed to unmarshal query request", zap.String("requestor", signerAddress.Hex()), zap.String("requestID", requestID), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := queryRequest.Validate(); err != nil {
|
||||
qLogger.Error("received invalid message", zap.String("requestor", signerAddress.Hex()), zap.String("requestID", requestID), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the set of per chain queries and placeholders for the per chain responses.
|
||||
errorFound := false
|
||||
queries := []*perChainQuery{}
|
||||
responses := make([]*PerChainQueryResponseInternal, len(queryRequest.PerChainQueries))
|
||||
receiveTime := time.Now()
|
||||
|
||||
for requestIdx, pcq := range queryRequest.PerChainQueries {
|
||||
chainID := vaa.ChainID(pcq.ChainId)
|
||||
if _, exists := supportedChains[chainID]; !exists {
|
||||
qLogger.Error("chain does not support cross chain queries", zap.String("requestID", requestID), zap.Stringer("chainID", chainID))
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
|
||||
channel, channelExists := chainQueryReqC[chainID]
|
||||
if !channelExists {
|
||||
qLogger.Error("unknown chain ID for query request, dropping it", zap.String("requestID", requestID), zap.Stringer("chain_id", chainID))
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
|
||||
queries = append(queries, &perChainQuery{
|
||||
req: &PerChainQueryInternal{
|
||||
RequestID: requestID,
|
||||
RequestIdx: requestIdx,
|
||||
Request: pcq,
|
||||
},
|
||||
channel: channel,
|
||||
})
|
||||
}
|
||||
|
||||
if errorFound {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create the pending query and add it to the cache.
|
||||
pq := &pendingQuery{
|
||||
signedRequest: signedRequest,
|
||||
request: &queryRequest,
|
||||
requestID: requestID,
|
||||
receiveTime: receiveTime,
|
||||
queries: queries,
|
||||
responses: responses,
|
||||
}
|
||||
pendingQueries[requestID] = pq
|
||||
|
||||
// Forward the requests to the watchers.
|
||||
for _, pcq := range pq.queries {
|
||||
pcq.ccqForwardToWatcher(qLogger, pq.receiveTime)
|
||||
}
|
||||
|
||||
case resp := <-queryResponseReadC: // Response from a watcher.
|
||||
if resp.Status == QuerySuccess {
|
||||
if resp.Response == nil {
|
||||
qLogger.Error("received a successful query response with no results, dropping it!", zap.String("requestID", resp.RequestID))
|
||||
continue
|
||||
}
|
||||
|
||||
pq, exists := pendingQueries[resp.RequestID]
|
||||
if !exists {
|
||||
qLogger.Warn("received a success response with no outstanding query, dropping it", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx))
|
||||
continue
|
||||
}
|
||||
|
||||
if resp.RequestIdx >= len(pq.responses) {
|
||||
qLogger.Error("received a response with an invalid index", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx))
|
||||
continue
|
||||
}
|
||||
|
||||
// Store the result, which will mark this per-chain query as completed.
|
||||
pq.responses[resp.RequestIdx] = resp
|
||||
|
||||
// If we still have other outstanding per chain queries for this request, keep waiting.
|
||||
numStillPending := pq.numPendingRequests()
|
||||
if numStillPending > 0 {
|
||||
qLogger.Info("received a per chain query response, still waiting for more", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx), zap.Int("numStillPending", numStillPending))
|
||||
continue
|
||||
} else {
|
||||
qLogger.Info("received final per chain query response, ready to publish", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx))
|
||||
}
|
||||
|
||||
// Build the list of per chain response publications and the overall query response publication.
|
||||
responses := []*PerChainQueryResponse{}
|
||||
for _, resp := range pq.responses {
|
||||
if resp == nil {
|
||||
qLogger.Error("unexpected null response in pending query!", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx))
|
||||
continue
|
||||
}
|
||||
|
||||
responses = append(responses, &PerChainQueryResponse{
|
||||
ChainId: resp.ChainId,
|
||||
Response: resp.Response,
|
||||
})
|
||||
}
|
||||
|
||||
respPub := &QueryResponsePublication{
|
||||
Request: pq.signedRequest,
|
||||
PerChainResponses: responses,
|
||||
}
|
||||
|
||||
// Send the response to be published.
|
||||
select {
|
||||
case queryResponseWriteC <- respPub:
|
||||
qLogger.Info("forwarded query response to p2p", zap.String("requestID", resp.RequestID))
|
||||
delete(pendingQueries, resp.RequestID)
|
||||
default:
|
||||
qLogger.Warn("failed to publish query response to p2p, will retry publishing next interval", zap.String("requestID", resp.RequestID))
|
||||
pq.respPub = respPub
|
||||
}
|
||||
} else if resp.Status == QueryRetryNeeded {
|
||||
if _, exists := pendingQueries[resp.RequestID]; exists {
|
||||
qLogger.Warn("query failed, will retry next interval", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx))
|
||||
} else {
|
||||
qLogger.Warn("received a retry needed response with no outstanding query, dropping it", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx))
|
||||
}
|
||||
} else if resp.Status == QueryFatalError {
|
||||
qLogger.Error("received a fatal error response, dropping the whole request", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx))
|
||||
delete(pendingQueries, resp.RequestID)
|
||||
} else {
|
||||
qLogger.Error("received an unexpected query status, dropping the whole request", zap.String("requestID", resp.RequestID), zap.Int("requestIdx", resp.RequestIdx), zap.Int("status", int(resp.Status)))
|
||||
delete(pendingQueries, resp.RequestID)
|
||||
}
|
||||
|
||||
case <-ticker.C: // Retry audit timer.
|
||||
now := time.Now()
|
||||
for reqId, pq := range pendingQueries {
|
||||
timeout := pq.receiveTime.Add(requestTimeoutImpl)
|
||||
qLogger.Debug("audit", zap.String("requestId", reqId), zap.Stringer("receiveTime", pq.receiveTime), zap.Stringer("timeout", timeout))
|
||||
if timeout.Before(now) {
|
||||
qLogger.Error("query request timed out, dropping it", zap.String("requestId", reqId), zap.Stringer("receiveTime", pq.receiveTime))
|
||||
delete(pendingQueries, reqId)
|
||||
} else {
|
||||
if pq.respPub != nil {
|
||||
// Resend the response to be published.
|
||||
select {
|
||||
case queryResponseWriteC <- pq.respPub:
|
||||
qLogger.Info("resend of query response to p2p succeeded", zap.String("requestID", reqId))
|
||||
delete(pendingQueries, reqId)
|
||||
default:
|
||||
qLogger.Warn("resend of query response to p2p failed again, will keep retrying", zap.String("requestID", reqId))
|
||||
}
|
||||
} else {
|
||||
for requestIdx, pcq := range pq.queries {
|
||||
if pq.responses[requestIdx] == nil && pcq.lastUpdateTime.Add(retryIntervalImpl).Before(now) {
|
||||
qLogger.Info("retrying query request",
|
||||
zap.String("requestId", reqId),
|
||||
zap.Int("requestIdx", requestIdx),
|
||||
zap.Stringer("receiveTime", pq.receiveTime),
|
||||
zap.Stringer("lastUpdateTime", pcq.lastUpdateTime),
|
||||
zap.String("chainID", pq.queries[requestIdx].req.Request.ChainId.String()),
|
||||
)
|
||||
pcq.ccqForwardToWatcher(qLogger, pq.receiveTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseAllowedRequesters parses a comma separated list of allowed requesters into a map to be used for look ups.
|
||||
func parseAllowedRequesters(ccqAllowedRequesters string) (map[ethCommon.Address]struct{}, error) {
|
||||
if ccqAllowedRequesters == "" {
|
||||
return nil, fmt.Errorf("if cross chain query is enabled `--ccqAllowedRequesters` must be specified")
|
||||
}
|
||||
|
||||
var nullAddr ethCommon.Address
|
||||
result := make(map[ethCommon.Address]struct{})
|
||||
for _, str := range strings.Split(ccqAllowedRequesters, ",") {
|
||||
addr := ethCommon.BytesToAddress(ethCommon.Hex2Bytes(str))
|
||||
if addr == nullAddr {
|
||||
return nil, fmt.Errorf("invalid value in `--ccqAllowedRequesters`: `%s`", str)
|
||||
}
|
||||
result[addr] = struct{}{}
|
||||
}
|
||||
|
||||
if len(result) <= 0 {
|
||||
return nil, fmt.Errorf("no allowed requestors specified, ccqAllowedRequesters: `%s`", ccqAllowedRequesters)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ccqForwardToWatcher submits a query request to the appropriate watcher. It updates the request object if the write succeeds.
|
||||
// If the write fails, it does not update the last update time, which will cause a retry next interval (until it times out)
|
||||
func (pcq *perChainQuery) ccqForwardToWatcher(qLogger *zap.Logger, receiveTime time.Time) {
|
||||
select {
|
||||
// TODO: only send the query request itself and reassemble in this module
|
||||
case pcq.channel <- pcq.req:
|
||||
qLogger.Debug("forwarded query request to watcher", zap.String("requestID", pcq.req.RequestID), zap.Stringer("chainID", pcq.req.Request.ChainId))
|
||||
pcq.lastUpdateTime = receiveTime
|
||||
default:
|
||||
// By leaving lastUpdateTime unset, we will retry next interval.
|
||||
qLogger.Warn("failed to send query request to watcher, will retry next interval", zap.String("requestID", pcq.req.RequestID), zap.Stringer("chain_id", pcq.req.Request.ChainId))
|
||||
}
|
||||
}
|
||||
|
||||
// numPendingRequests returns the number of per chain queries in a request that are still awaiting responses. Zero means the request can now be published.
|
||||
func (pq *pendingQuery) numPendingRequests() int {
|
||||
numPending := 0
|
||||
for _, resp := range pq.responses {
|
||||
if resp == nil {
|
||||
numPending += 1
|
||||
}
|
||||
}
|
||||
|
||||
return numPending
|
||||
}
|
|
@ -0,0 +1,664 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
ethCrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
testSigner = "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"
|
||||
|
||||
// Magic retry values used to cause special behavior in the watchers.
|
||||
fatalError = math.MaxInt
|
||||
ignoreQuery = math.MaxInt - 1
|
||||
|
||||
// Speed things up for testing purposes.
|
||||
requestTimeoutForTest = 100 * time.Millisecond
|
||||
retryIntervalForTest = 10 * time.Millisecond
|
||||
pollIntervalForTest = 5 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
nonce = uint32(0)
|
||||
|
||||
watcherChainsForTest = []vaa.ChainID{vaa.ChainIDPolygon, vaa.ChainIDBSC}
|
||||
)
|
||||
|
||||
// createPerChainQueryForTesting creates a per chain query for use in tests. The To and Data fields are meaningless gibberish, not ABI.
|
||||
func createPerChainQueryForTesting(
|
||||
t *testing.T,
|
||||
chainId vaa.ChainID,
|
||||
block string,
|
||||
numCalls int,
|
||||
) *PerChainQueryRequest {
|
||||
t.Helper()
|
||||
callData := []*EthCallData{}
|
||||
for count := 0; count < numCalls; count++ {
|
||||
callData = append(callData, &EthCallData{
|
||||
To: []byte(fmt.Sprintf("%-20s", fmt.Sprintf("To for %d:%d", chainId, count))),
|
||||
Data: []byte(fmt.Sprintf("CallData for %d:%d", chainId, count)),
|
||||
})
|
||||
}
|
||||
|
||||
callRequest := &EthCallQueryRequest{
|
||||
BlockId: block,
|
||||
CallData: callData,
|
||||
}
|
||||
|
||||
return &PerChainQueryRequest{
|
||||
ChainId: chainId,
|
||||
Query: callRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// createSignedQueryRequestForTesting creates a query request object and signs it using the specified key.
|
||||
func createSignedQueryRequestForTesting(
|
||||
t *testing.T,
|
||||
sk *ecdsa.PrivateKey,
|
||||
perChainQueries []*PerChainQueryRequest,
|
||||
) (*gossipv1.SignedQueryRequest, *QueryRequest) {
|
||||
t.Helper()
|
||||
nonce += 1
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: nonce,
|
||||
PerChainQueries: perChainQueries,
|
||||
}
|
||||
|
||||
queryRequestBytes, err := queryRequest.Marshal()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
digest := QueryRequestDigest(common.UnsafeDevNet, queryRequestBytes)
|
||||
sig, err := ethCrypto.Sign(digest.Bytes(), sk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
signedQueryRequest := &gossipv1.SignedQueryRequest{
|
||||
QueryRequest: queryRequestBytes,
|
||||
Signature: sig,
|
||||
}
|
||||
|
||||
return signedQueryRequest, queryRequest
|
||||
}
|
||||
|
||||
// createExpectedResultsForTest generates an array of the results expected for a request. These results are returned by the watcher, and used to validate the response.
|
||||
func createExpectedResultsForTest(t *testing.T, perChainQueries []*PerChainQueryRequest) []PerChainQueryResponse {
|
||||
t.Helper()
|
||||
expectedResults := []PerChainQueryResponse{}
|
||||
for _, pcq := range perChainQueries {
|
||||
switch req := pcq.Query.(type) {
|
||||
case *EthCallQueryRequest:
|
||||
now := time.Now()
|
||||
blockNum, err := strconv.ParseUint(strings.TrimPrefix(req.BlockId, "0x"), 16, 64)
|
||||
if err != nil {
|
||||
panic("invalid blockNum!")
|
||||
}
|
||||
resp := &EthCallQueryResponse{
|
||||
BlockNumber: blockNum,
|
||||
Hash: ethCommon.HexToHash("0x9999bac44d09a7f69ee7941819b0a19c59ccb1969640cc513be09ef95ed2d8e2"),
|
||||
Time: timeForTest(t, now),
|
||||
Results: [][]byte{},
|
||||
}
|
||||
for _, cd := range req.CallData {
|
||||
resp.Results = append(resp.Results, []byte(hex.EncodeToString(cd.To)+":"+hex.EncodeToString(cd.Data)))
|
||||
}
|
||||
expectedResults = append(expectedResults, PerChainQueryResponse{
|
||||
ChainId: pcq.ChainId,
|
||||
Response: resp,
|
||||
})
|
||||
|
||||
default:
|
||||
panic("Invalid call data type!")
|
||||
}
|
||||
}
|
||||
|
||||
return expectedResults
|
||||
}
|
||||
|
||||
// validateResponseForTest performs validation on the responses generated by these tests. Note that it is not a generalized validate function.
|
||||
func validateResponseForTest(
|
||||
t *testing.T,
|
||||
response *QueryResponsePublication,
|
||||
signedRequest *gossipv1.SignedQueryRequest,
|
||||
queryRequest *QueryRequest,
|
||||
expectedResults []PerChainQueryResponse,
|
||||
) bool {
|
||||
require.NotNil(t, response)
|
||||
require.True(t, SignedQueryRequestEqual(signedRequest, response.Request))
|
||||
require.Equal(t, len(queryRequest.PerChainQueries), len(response.PerChainResponses))
|
||||
require.True(t, bytes.Equal(response.Request.Signature, signedRequest.Signature))
|
||||
require.Equal(t, len(response.PerChainResponses), len(expectedResults))
|
||||
for idx := range response.PerChainResponses {
|
||||
require.True(t, response.PerChainResponses[idx].Equal(&expectedResults[idx]))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func TestParseAllowedRequestersSuccess(t *testing.T) {
|
||||
ccqAllowedRequestersList, err := parseAllowedRequesters(testSigner)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ccqAllowedRequestersList)
|
||||
require.Equal(t, 1, len(ccqAllowedRequestersList))
|
||||
|
||||
_, exists := ccqAllowedRequestersList[ethCommon.BytesToAddress(ethCommon.Hex2Bytes(testSigner))]
|
||||
require.True(t, exists)
|
||||
_, exists = ccqAllowedRequestersList[ethCommon.BytesToAddress(ethCommon.Hex2Bytes("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBf"))]
|
||||
require.False(t, exists)
|
||||
|
||||
ccqAllowedRequestersList, err = parseAllowedRequesters("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe,beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBf")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ccqAllowedRequestersList)
|
||||
require.Equal(t, 2, len(ccqAllowedRequestersList))
|
||||
|
||||
_, exists = ccqAllowedRequestersList[ethCommon.BytesToAddress(ethCommon.Hex2Bytes(testSigner))]
|
||||
require.True(t, exists)
|
||||
_, exists = ccqAllowedRequestersList[ethCommon.BytesToAddress(ethCommon.Hex2Bytes("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBf"))]
|
||||
require.True(t, exists)
|
||||
}
|
||||
|
||||
func TestParseAllowedRequestersFailsIfParameterEmpty(t *testing.T) {
|
||||
ccqAllowedRequestersList, err := parseAllowedRequesters("")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, ccqAllowedRequestersList)
|
||||
|
||||
ccqAllowedRequestersList, err = parseAllowedRequesters(",")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, ccqAllowedRequestersList)
|
||||
}
|
||||
|
||||
func TestParseAllowedRequestersFailsIfInvalidParameter(t *testing.T) {
|
||||
ccqAllowedRequestersList, err := parseAllowedRequesters("Hello")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, ccqAllowedRequestersList)
|
||||
}
|
||||
|
||||
// mockData is the data structure used to mock up the query handler environment.
|
||||
type mockData struct {
|
||||
sk *ecdsa.PrivateKey
|
||||
|
||||
signedQueryReqReadC <-chan *gossipv1.SignedQueryRequest
|
||||
signedQueryReqWriteC chan<- *gossipv1.SignedQueryRequest
|
||||
|
||||
chainQueryReqC map[vaa.ChainID]chan *PerChainQueryInternal
|
||||
|
||||
queryResponseReadC <-chan *PerChainQueryResponseInternal
|
||||
queryResponseWriteC chan<- *PerChainQueryResponseInternal
|
||||
|
||||
queryResponsePublicationReadC <-chan *QueryResponsePublication
|
||||
queryResponsePublicationWriteC chan<- *QueryResponsePublication
|
||||
|
||||
mutex sync.Mutex
|
||||
queryResponsePublication *QueryResponsePublication
|
||||
expectedResults []PerChainQueryResponse
|
||||
requestsPerChain map[vaa.ChainID]int
|
||||
retriesPerChain map[vaa.ChainID]int
|
||||
}
|
||||
|
||||
// resetState() is used to reset mock data between queries in the same test.
|
||||
func (md *mockData) resetState() {
|
||||
md.mutex.Lock()
|
||||
defer md.mutex.Unlock()
|
||||
md.queryResponsePublication = nil
|
||||
md.expectedResults = nil
|
||||
md.requestsPerChain = make(map[vaa.ChainID]int)
|
||||
md.retriesPerChain = make(map[vaa.ChainID]int)
|
||||
}
|
||||
|
||||
// setExpectedResults sets the results to be returned by the watchers.
|
||||
func (md *mockData) setExpectedResults(expectedResults []PerChainQueryResponse) {
|
||||
md.mutex.Lock()
|
||||
defer md.mutex.Unlock()
|
||||
md.expectedResults = expectedResults
|
||||
}
|
||||
|
||||
// setRetries allows a test to specify how many times a given watcher should retry before returning success.
|
||||
// If the count is the special value `fatalError`, the watcher will return QueryFatalError.
|
||||
func (md *mockData) setRetries(chainId vaa.ChainID, count int) {
|
||||
md.mutex.Lock()
|
||||
defer md.mutex.Unlock()
|
||||
md.retriesPerChain[chainId] = count
|
||||
}
|
||||
|
||||
// incrementRequestsPerChainAlreadyLocked is used by the watchers to keep track of how many times they were invoked in a given test.
|
||||
func (md *mockData) incrementRequestsPerChainAlreadyLocked(chainId vaa.ChainID) {
|
||||
if val, exists := md.requestsPerChain[chainId]; exists {
|
||||
md.requestsPerChain[chainId] = val + 1
|
||||
} else {
|
||||
md.requestsPerChain[chainId] = 1
|
||||
}
|
||||
}
|
||||
|
||||
// getQueryResponsePublication returns the latest query response publication received by the mock.
|
||||
func (md *mockData) getQueryResponsePublication() *QueryResponsePublication {
|
||||
md.mutex.Lock()
|
||||
defer md.mutex.Unlock()
|
||||
return md.queryResponsePublication
|
||||
}
|
||||
|
||||
// getRequestsPerChain returns the count of the number of times the given watcher was invoked in a given test.
|
||||
func (md *mockData) getRequestsPerChain(chainId vaa.ChainID) int {
|
||||
md.mutex.Lock()
|
||||
defer md.mutex.Unlock()
|
||||
if ret, exists := md.requestsPerChain[chainId]; exists {
|
||||
return ret
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// shouldIgnoreAlreadyLocked is used by the watchers to see if they should ignore a query (causing a retry).
|
||||
func (md *mockData) shouldIgnoreAlreadyLocked(chainId vaa.ChainID) bool {
|
||||
if val, exists := md.retriesPerChain[chainId]; exists {
|
||||
if val == ignoreQuery {
|
||||
delete(md.retriesPerChain, chainId)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getStatusAlreadyLocked is used by the watchers to determine what query status they should return, based on the `retriesPerChain`.
|
||||
func (md *mockData) getStatusAlreadyLocked(chainId vaa.ChainID) QueryStatus {
|
||||
if val, exists := md.retriesPerChain[chainId]; exists {
|
||||
if val == fatalError {
|
||||
return QueryFatalError
|
||||
}
|
||||
val -= 1
|
||||
if val > 0 {
|
||||
md.retriesPerChain[chainId] = val
|
||||
} else {
|
||||
delete(md.retriesPerChain, chainId)
|
||||
}
|
||||
return QueryRetryNeeded
|
||||
}
|
||||
return QuerySuccess
|
||||
}
|
||||
|
||||
// createQueryHandlerForTest creates the query handler mock environment, including the set of watchers and the response listener.
|
||||
// Most tests will use this function to set up the mock.
|
||||
func createQueryHandlerForTest(t *testing.T, ctx context.Context, logger *zap.Logger, chains []vaa.ChainID) *mockData {
|
||||
md := createQueryHandlerForTestWithoutPublisher(t, ctx, logger, chains)
|
||||
md.startResponseListener(ctx)
|
||||
return md
|
||||
}
|
||||
|
||||
// createQueryHandlerForTestWithoutPublisher creates the query handler mock environment, including the set of watchers but not the response listener.
|
||||
// This function can be invoked directly to test retries of response publication (by delaying the start of the response listener).
|
||||
func createQueryHandlerForTestWithoutPublisher(t *testing.T, ctx context.Context, logger *zap.Logger, chains []vaa.ChainID) *mockData {
|
||||
md := mockData{}
|
||||
var err error
|
||||
|
||||
md.sk, err = common.LoadGuardianKey("dev.guardian.key", true)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, md.sk)
|
||||
|
||||
ccqAllowedRequestersList, err := parseAllowedRequesters(testSigner)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Inbound observation requests from the p2p service (for all chains)
|
||||
md.signedQueryReqReadC, md.signedQueryReqWriteC = makeChannelPair[*gossipv1.SignedQueryRequest](SignedQueryRequestChannelSize)
|
||||
|
||||
// Per-chain query requests
|
||||
md.chainQueryReqC = make(map[vaa.ChainID]chan *PerChainQueryInternal)
|
||||
for _, chainId := range chains {
|
||||
md.chainQueryReqC[chainId] = make(chan *PerChainQueryInternal)
|
||||
}
|
||||
|
||||
// Query responses from watchers to query handler aggregated across all chains
|
||||
md.queryResponseReadC, md.queryResponseWriteC = makeChannelPair[*PerChainQueryResponseInternal](0)
|
||||
|
||||
// Query responses from query handler to p2p
|
||||
md.queryResponsePublicationReadC, md.queryResponsePublicationWriteC = makeChannelPair[*QueryResponsePublication](0)
|
||||
|
||||
md.resetState()
|
||||
|
||||
go func() {
|
||||
err := handleQueryRequestsImpl(ctx, logger, md.signedQueryReqReadC, md.chainQueryReqC, ccqAllowedRequestersList,
|
||||
md.queryResponseReadC, md.queryResponsePublicationWriteC, common.GoTest, requestTimeoutForTest, retryIntervalForTest)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Create a routine for each configured watcher. It will take a per chain query and return the corresponding expected result.
|
||||
// It also pegs a counter of the number of requests the watcher received, for verification purposes.
|
||||
for chainId := range md.chainQueryReqC {
|
||||
go func(chainId vaa.ChainID, chainQueryReqC <-chan *PerChainQueryInternal) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case pcqr := <-chainQueryReqC:
|
||||
require.Equal(t, chainId, pcqr.Request.ChainId)
|
||||
md.mutex.Lock()
|
||||
md.incrementRequestsPerChainAlreadyLocked(chainId)
|
||||
if md.shouldIgnoreAlreadyLocked(chainId) {
|
||||
logger.Info("watcher ignoring query", zap.String("chainId", chainId.String()), zap.Int("requestIdx", pcqr.RequestIdx))
|
||||
} else {
|
||||
results := md.expectedResults[pcqr.RequestIdx].Response
|
||||
status := md.getStatusAlreadyLocked(chainId)
|
||||
logger.Info("watcher returning", zap.String("chainId", chainId.String()), zap.Int("requestIdx", pcqr.RequestIdx), zap.Int("status", int(status)))
|
||||
queryResponse := CreatePerChainQueryResponseInternal(pcqr.RequestID, pcqr.RequestIdx, pcqr.Request.ChainId, status, results)
|
||||
md.queryResponseWriteC <- queryResponse
|
||||
}
|
||||
md.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
}(chainId, md.chainQueryReqC[chainId])
|
||||
}
|
||||
|
||||
return &md
|
||||
}
|
||||
|
||||
// startResponseListener starts the response listener routine. It is called as part of the standard mock environment set up. Or, it can be used
|
||||
// along with `createQueryHandlerForTestWithoutPublisher“ to test retries of response publication (by delaying the start of the response listener).
|
||||
func (md *mockData) startResponseListener(ctx context.Context) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case qrp := <-md.queryResponsePublicationReadC:
|
||||
md.mutex.Lock()
|
||||
md.queryResponsePublication = qrp
|
||||
md.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// waitForResponse is used by the tests to wait for a response publication. It will eventually timeout if the query fails.
|
||||
func (md *mockData) waitForResponse() *QueryResponsePublication {
|
||||
for count := 0; count < 50; count++ {
|
||||
time.Sleep(pollIntervalForTest)
|
||||
ret := md.getQueryResponsePublication()
|
||||
if ret != nil {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestInvalidQueries tests all the obvious reasons why a query may fail (aside from watcher failures).
|
||||
func TestInvalidQueries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
var perChainQueries []*PerChainQueryRequest
|
||||
var signedQueryRequest *gossipv1.SignedQueryRequest
|
||||
|
||||
// Query with a bad signature should fail.
|
||||
md.resetState()
|
||||
perChainQueries = []*PerChainQueryRequest{createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2)}
|
||||
signedQueryRequest, _ = createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
signedQueryRequest.Signature[0] += 1 // Corrupt the signature.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
require.Nil(t, md.waitForResponse())
|
||||
|
||||
// Query for an unsupported chain should fail. The supported chains are defined in supportedChains in query.go
|
||||
md.resetState()
|
||||
perChainQueries = []*PerChainQueryRequest{createPerChainQueryForTesting(t, vaa.ChainIDAlgorand, "0x28d9630", 2)}
|
||||
signedQueryRequest, _ = createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
require.Nil(t, md.waitForResponse())
|
||||
|
||||
// Query for a chain that supports queries but that is not in the watcher channel map should fail.
|
||||
md.resetState()
|
||||
perChainQueries = []*PerChainQueryRequest{createPerChainQueryForTesting(t, vaa.ChainIDSepolia, "0x28d9630", 2)}
|
||||
signedQueryRequest, _ = createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
require.Nil(t, md.waitForResponse())
|
||||
}
|
||||
|
||||
func TestSingleQueryShouldSucceed(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2)}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// Wait until we receive a response or timeout.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.NotNil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, 1, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
assert.True(t, validateResponseForTest(t, queryResponsePublication, signedQueryRequest, queryRequest, expectedResults))
|
||||
}
|
||||
|
||||
func TestBatchOfTwoQueriesShouldSucceed(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2),
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDBSC, "0x28d9123", 3),
|
||||
}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// Wait until we receive a response or timeout.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.NotNil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, 1, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
assert.Equal(t, 1, md.getRequestsPerChain(vaa.ChainIDBSC))
|
||||
assert.True(t, validateResponseForTest(t, queryResponsePublication, signedQueryRequest, queryRequest, expectedResults))
|
||||
}
|
||||
|
||||
func TestQueryWithLimitedRetriesShouldSucceed(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2)}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Make it retry a couple of times, but not enough to make it fail.
|
||||
retries := 2
|
||||
md.setRetries(vaa.ChainIDPolygon, retries)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// The request should eventually succeed.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.NotNil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, retries+1, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
assert.True(t, validateResponseForTest(t, queryResponsePublication, signedQueryRequest, queryRequest, expectedResults))
|
||||
}
|
||||
|
||||
func TestQueryWithRetryDueToTimeoutShouldSucceed(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2)}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Make the first per chain query timeout, but the retry should succeed.
|
||||
md.setRetries(vaa.ChainIDPolygon, ignoreQuery)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// The request should eventually succeed.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.NotNil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, 2, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
assert.True(t, validateResponseForTest(t, queryResponsePublication, signedQueryRequest, queryRequest, expectedResults))
|
||||
}
|
||||
|
||||
func TestQueryWithTooManyRetriesShouldFail(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2),
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDBSC, "0x28d9123", 3),
|
||||
}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Make polygon retry a couple of times, but not enough to make it fail.
|
||||
retriesForPolygon := 2
|
||||
md.setRetries(vaa.ChainIDPolygon, retriesForPolygon)
|
||||
|
||||
// Make BSC retry so many times that the request times out.
|
||||
md.setRetries(vaa.ChainIDBSC, 1000)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// The request should timeout.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.Nil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, retriesForPolygon+1, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
}
|
||||
|
||||
func TestQueryWithLimitedRetriesOnMultipleChainsShouldSucceed(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2),
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDBSC, "0x28d9123", 3),
|
||||
}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Make both chains retry a couple of times, but not enough to make it fail.
|
||||
retriesForPolygon := 2
|
||||
md.setRetries(vaa.ChainIDPolygon, retriesForPolygon)
|
||||
|
||||
retriesForBSC := 3
|
||||
md.setRetries(vaa.ChainIDBSC, retriesForBSC)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// The request should eventually succeed.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.NotNil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, retriesForPolygon+1, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
assert.Equal(t, retriesForBSC+1, md.getRequestsPerChain(vaa.ChainIDBSC))
|
||||
assert.True(t, validateResponseForTest(t, queryResponsePublication, signedQueryRequest, queryRequest, expectedResults))
|
||||
}
|
||||
|
||||
func TestFatalErrorOnPerChainQueryShouldCauseRequestToFail(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTest(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2),
|
||||
createPerChainQueryForTesting(t, vaa.ChainIDBSC, "0x28d9123", 3),
|
||||
}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Make BSC return a fatal error.
|
||||
md.setRetries(vaa.ChainIDBSC, fatalError)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// The request should timeout.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.Nil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, 1, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
assert.Equal(t, 1, md.getRequestsPerChain(vaa.ChainIDBSC))
|
||||
}
|
||||
|
||||
func TestPublishRetrySucceeds(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zap.NewNop()
|
||||
|
||||
md := createQueryHandlerForTestWithoutPublisher(t, ctx, logger, watcherChainsForTest)
|
||||
|
||||
// Create the request and the expected results. Give the expected results to the mock.
|
||||
perChainQueries := []*PerChainQueryRequest{createPerChainQueryForTesting(t, vaa.ChainIDPolygon, "0x28d9630", 2)}
|
||||
signedQueryRequest, queryRequest := createSignedQueryRequestForTesting(t, md.sk, perChainQueries)
|
||||
expectedResults := createExpectedResultsForTest(t, queryRequest.PerChainQueries)
|
||||
md.setExpectedResults(expectedResults)
|
||||
|
||||
// Submit the query request to the handler.
|
||||
md.signedQueryReqWriteC <- signedQueryRequest
|
||||
|
||||
// Sleep for a bit before we start listening for published results.
|
||||
// If you look in the log, you should see one of these: "failed to publish query response to p2p, will retry publishing next interval"
|
||||
// and at least one of these: "resend of query response to p2p failed again, will keep retrying".
|
||||
time.Sleep(retryIntervalForTest * 3)
|
||||
|
||||
// Now start the publisher routine.
|
||||
// If you look in the log, you should see one of these: "resend of query response to p2p succeeded".
|
||||
md.startResponseListener(ctx)
|
||||
|
||||
// The response should still get published.
|
||||
queryResponsePublication := md.waitForResponse()
|
||||
require.NotNil(t, queryResponsePublication)
|
||||
|
||||
assert.Equal(t, 1, md.getRequestsPerChain(vaa.ChainIDPolygon))
|
||||
assert.True(t, validateResponseForTest(t, queryResponsePublication, signedQueryRequest, queryRequest, expectedResults))
|
||||
}
|
|
@ -0,0 +1,472 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
ethCrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// MSG_VERSION is the current version of the CCQ message protocol.
|
||||
const MSG_VERSION uint8 = 1
|
||||
|
||||
// QueryRequest defines a cross chain query request to be submitted to the guardians.
|
||||
// It is the payload of the SignedQueryRequest gossip message.
|
||||
type QueryRequest struct {
|
||||
Nonce uint32
|
||||
PerChainQueries []*PerChainQueryRequest
|
||||
}
|
||||
|
||||
// PerChainQueryRequest represents a query request for a single chain.
|
||||
type PerChainQueryRequest struct {
|
||||
// ChainId indicates which chain this query is destine for.
|
||||
ChainId vaa.ChainID
|
||||
|
||||
// Query is the chain specific query data.
|
||||
Query ChainSpecificQuery
|
||||
}
|
||||
|
||||
// ChainSpecificQuery is the interface that must be implemented by a chain specific query.
|
||||
type ChainSpecificQuery interface {
|
||||
Type() ChainSpecificQueryType
|
||||
Marshal() ([]byte, error)
|
||||
Unmarshal(data []byte) error
|
||||
UnmarshalFromReader(reader *bytes.Reader) error
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ChainSpecificQueryType is used to interpret the data in a per chain query request.
|
||||
type ChainSpecificQueryType uint8
|
||||
|
||||
// EthCallQueryRequestType is the type of an EVM eth_call query request.
|
||||
const EthCallQueryRequestType ChainSpecificQueryType = 1
|
||||
|
||||
// EthCallQueryRequest implements ChainSpecificQuery for an EVM eth_call query request.
|
||||
type EthCallQueryRequest struct {
|
||||
// BlockId identifies the block to be queried. It mus be a hex string starting with 0x. It may be a block number or a block hash.
|
||||
BlockId string
|
||||
|
||||
// CallData is an array of specific queries to be performed on the specified block, in a single RPC call.
|
||||
CallData []*EthCallData
|
||||
}
|
||||
|
||||
// EthCallData specifies the parameters to a single EVM eth_call request.
|
||||
type EthCallData struct {
|
||||
// To specifies the contract address to be queried.
|
||||
To []byte
|
||||
|
||||
// Data is the ABI encoded parameters to the query.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
const EvmContractAddressLength = 20
|
||||
|
||||
// PerChainQueryInternal is an internal representation of a query request that is passed to the watcher.
|
||||
type PerChainQueryInternal struct {
|
||||
RequestID string
|
||||
RequestIdx int
|
||||
Request *PerChainQueryRequest
|
||||
}
|
||||
|
||||
// QueryRequestDigest returns the query signing prefix based on the environment.
|
||||
func QueryRequestDigest(env common.Environment, b []byte) ethCommon.Hash {
|
||||
var queryRequestPrefix []byte
|
||||
if env == common.MainNet {
|
||||
queryRequestPrefix = []byte("mainnet_query_request_000000000000|")
|
||||
} else if env == common.TestNet {
|
||||
queryRequestPrefix = []byte("testnet_query_request_000000000000|")
|
||||
} else {
|
||||
queryRequestPrefix = []byte("devnet_query_request_0000000000000|")
|
||||
}
|
||||
|
||||
return ethCrypto.Keccak256Hash(append(queryRequestPrefix, b...))
|
||||
}
|
||||
|
||||
// PostSignedQueryRequest posts a signed query request to the specified channel.
|
||||
func PostSignedQueryRequest(signedQueryReqSendC chan<- *gossipv1.SignedQueryRequest, req *gossipv1.SignedQueryRequest) error {
|
||||
select {
|
||||
case signedQueryReqSendC <- req:
|
||||
return nil
|
||||
default:
|
||||
return common.ErrChanFull
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of QueryRequest.
|
||||
//
|
||||
|
||||
// Marshal serializes the binary representation of a query request.
|
||||
// This method calls Validate() and relies on it to range checks lengths, etc.
|
||||
func (queryRequest *QueryRequest) Marshal() ([]byte, error) {
|
||||
if err := queryRequest.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
vaa.MustWrite(buf, binary.BigEndian, MSG_VERSION) // version
|
||||
vaa.MustWrite(buf, binary.BigEndian, queryRequest.Nonce) // uint32
|
||||
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint8(len(queryRequest.PerChainQueries)))
|
||||
for _, perChainQuery := range queryRequest.PerChainQueries {
|
||||
pcqBuf, err := perChainQuery.Marshal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal per chain query: %w", err)
|
||||
}
|
||||
buf.Write(pcqBuf)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal deserializes the binary representation of a query request from a byte array
|
||||
func (queryRequest *QueryRequest) Unmarshal(data []byte) error {
|
||||
reader := bytes.NewReader(data[:])
|
||||
return queryRequest.UnmarshalFromReader(reader)
|
||||
}
|
||||
|
||||
// UnmarshalFromReader deserializes the binary representation of a query request from an existing reader
|
||||
func (queryRequest *QueryRequest) UnmarshalFromReader(reader *bytes.Reader) error {
|
||||
var version uint8
|
||||
if err := binary.Read(reader, binary.BigEndian, &version); err != nil {
|
||||
return fmt.Errorf("failed to read message version: %w", err)
|
||||
}
|
||||
|
||||
if version != MSG_VERSION {
|
||||
return fmt.Errorf("unsupported message version: %d", version)
|
||||
}
|
||||
|
||||
if err := binary.Read(reader, binary.BigEndian, &queryRequest.Nonce); err != nil {
|
||||
return fmt.Errorf("failed to read request nonce: %w", err)
|
||||
}
|
||||
|
||||
numPerChainQueries := uint8(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &numPerChainQueries); err != nil {
|
||||
return fmt.Errorf("failed to read number of per chain queries: %w", err)
|
||||
}
|
||||
|
||||
for count := 0; count < int(numPerChainQueries); count++ {
|
||||
perChainQuery := PerChainQueryRequest{}
|
||||
err := perChainQuery.UnmarshalFromReader(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to Unmarshal per chain query: %w", err)
|
||||
}
|
||||
queryRequest.PerChainQueries = append(queryRequest.PerChainQueries, &perChainQuery)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate does basic validation on a received query request.
|
||||
func (queryRequest *QueryRequest) Validate() error {
|
||||
// Nothing to validate on the Nonce.
|
||||
if len(queryRequest.PerChainQueries) <= 0 {
|
||||
return fmt.Errorf("request does not contain any per chain queries")
|
||||
}
|
||||
if len(queryRequest.PerChainQueries) > math.MaxUint8 {
|
||||
return fmt.Errorf("too many per chain queries")
|
||||
}
|
||||
for idx, perChainQuery := range queryRequest.PerChainQueries {
|
||||
if err := perChainQuery.Validate(); err != nil {
|
||||
return fmt.Errorf("failed to validate per chain query %d: %w", idx, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal verifies that two query requests are equal.
|
||||
func (left *QueryRequest) Equal(right *QueryRequest) bool {
|
||||
if left.Nonce != right.Nonce {
|
||||
return false
|
||||
}
|
||||
if len(left.PerChainQueries) != len(right.PerChainQueries) {
|
||||
return false
|
||||
}
|
||||
|
||||
for idx := range left.PerChainQueries {
|
||||
if !left.PerChainQueries[idx].Equal(right.PerChainQueries[idx]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of PerChainQueryRequest.
|
||||
//
|
||||
|
||||
// Marshal serializes the binary representation of a per chain query request.
|
||||
// This method calls Validate() and relies on it to range checks lengths, etc.
|
||||
func (perChainQuery *PerChainQueryRequest) Marshal() ([]byte, error) {
|
||||
if err := perChainQuery.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
vaa.MustWrite(buf, binary.BigEndian, perChainQuery.ChainId)
|
||||
vaa.MustWrite(buf, binary.BigEndian, perChainQuery.Query.Type())
|
||||
queryBuf, err := perChainQuery.Query.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the length of the query to facilitate on-chain parsing.
|
||||
if len(queryBuf) > math.MaxUint32 {
|
||||
return nil, fmt.Errorf("query too long")
|
||||
}
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint32(len(queryBuf)))
|
||||
|
||||
buf.Write(queryBuf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal deserializes the binary representation of a per chain query request from a byte array
|
||||
func (perChainQuery *PerChainQueryRequest) Unmarshal(data []byte) error {
|
||||
reader := bytes.NewReader(data[:])
|
||||
return perChainQuery.UnmarshalFromReader(reader)
|
||||
}
|
||||
|
||||
// UnmarshalFromReader deserializes the binary representation of a per chain query request from an existing reader
|
||||
func (perChainQuery *PerChainQueryRequest) UnmarshalFromReader(reader *bytes.Reader) error {
|
||||
if err := binary.Read(reader, binary.BigEndian, &perChainQuery.ChainId); err != nil {
|
||||
return fmt.Errorf("failed to read request chain: %w", err)
|
||||
}
|
||||
|
||||
qt := uint8(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &qt); err != nil {
|
||||
return fmt.Errorf("failed to read request type: %w", err)
|
||||
}
|
||||
queryType := ChainSpecificQueryType(qt)
|
||||
|
||||
if err := ValidatePerChainQueryRequestType(queryType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip the query length.
|
||||
var queryLength uint32
|
||||
if err := binary.Read(reader, binary.BigEndian, &queryLength); err != nil {
|
||||
return fmt.Errorf("failed to read query length: %w", err)
|
||||
}
|
||||
|
||||
switch queryType {
|
||||
case EthCallQueryRequestType:
|
||||
q := EthCallQueryRequest{}
|
||||
if err := q.UnmarshalFromReader(reader); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal eth call request: %w", err)
|
||||
}
|
||||
perChainQuery.Query = &q
|
||||
default:
|
||||
return fmt.Errorf("unsupported query type: %d", queryType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate does basic validation on a per chain query request.
|
||||
func (perChainQuery *PerChainQueryRequest) Validate() error {
|
||||
str := perChainQuery.ChainId.String()
|
||||
if _, err := vaa.ChainIDFromString(str); err != nil {
|
||||
return fmt.Errorf("invalid chainID: %d", uint16(perChainQuery.ChainId))
|
||||
}
|
||||
|
||||
if perChainQuery.Query == nil {
|
||||
return fmt.Errorf("query is nil")
|
||||
}
|
||||
|
||||
if err := ValidatePerChainQueryRequestType(perChainQuery.Query.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := perChainQuery.Query.Validate(); err != nil {
|
||||
return fmt.Errorf("chain specific query is invalid: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal verifies that two query requests are equal.
|
||||
func (left *PerChainQueryRequest) Equal(right *PerChainQueryRequest) bool {
|
||||
if left.ChainId != right.ChainId {
|
||||
return false
|
||||
}
|
||||
|
||||
if left.Query == nil && right.Query == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if left.Query == nil || right.Query == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if left.Query.Type() != right.Query.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
switch leftEcq := left.Query.(type) {
|
||||
case *EthCallQueryRequest:
|
||||
switch rightEcd := right.Query.(type) {
|
||||
case *EthCallQueryRequest:
|
||||
return leftEcq.Equal(rightEcd)
|
||||
default:
|
||||
panic("unsupported query type on right") // We checked this above!
|
||||
}
|
||||
default:
|
||||
panic("unsupported query type on left") // We checked this above!
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of EthCallQueryRequest, which implements the ChainSpecificQuery interface.
|
||||
//
|
||||
|
||||
func (e *EthCallQueryRequest) Type() ChainSpecificQueryType {
|
||||
return EthCallQueryRequestType
|
||||
}
|
||||
|
||||
// Marshal serializes the binary representation of an EVM eth_call request.
|
||||
// This method calls Validate() and relies on it to range checks lengths, etc.
|
||||
func (ecd *EthCallQueryRequest) Marshal() ([]byte, error) {
|
||||
if err := ecd.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint32(len(ecd.BlockId)))
|
||||
buf.Write([]byte(ecd.BlockId))
|
||||
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint8(len(ecd.CallData)))
|
||||
for _, callData := range ecd.CallData {
|
||||
buf.Write(callData.To)
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint32(len(callData.Data)))
|
||||
buf.Write(callData.Data)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal deserializes an EVM eth_call query from a byte array
|
||||
func (ecd *EthCallQueryRequest) Unmarshal(data []byte) error {
|
||||
reader := bytes.NewReader(data[:])
|
||||
return ecd.UnmarshalFromReader(reader)
|
||||
}
|
||||
|
||||
// UnmarshalFromReader deserializes an EVM eth_call query from a byte array
|
||||
func (ecd *EthCallQueryRequest) UnmarshalFromReader(reader *bytes.Reader) error {
|
||||
blockIdLen := uint32(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &blockIdLen); err != nil {
|
||||
return fmt.Errorf("failed to read call Data len: %w", err)
|
||||
}
|
||||
|
||||
blockId := make([]byte, blockIdLen)
|
||||
if n, err := reader.Read(blockId[:]); err != nil || n != int(blockIdLen) {
|
||||
return fmt.Errorf("failed to read call To [%d]: %w", n, err)
|
||||
}
|
||||
ecd.BlockId = string(blockId[:])
|
||||
|
||||
numCallData := uint8(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &numCallData); err != nil {
|
||||
return fmt.Errorf("failed to read number of call data entries: %w", err)
|
||||
}
|
||||
|
||||
for count := 0; count < int(numCallData); count++ {
|
||||
to := [EvmContractAddressLength]byte{}
|
||||
if n, err := reader.Read(to[:]); err != nil || n != EvmContractAddressLength {
|
||||
return fmt.Errorf("failed to read call To [%d]: %w", n, err)
|
||||
}
|
||||
|
||||
dataLen := uint32(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &dataLen); err != nil {
|
||||
return fmt.Errorf("failed to read call Data len: %w", err)
|
||||
}
|
||||
data := make([]byte, dataLen)
|
||||
if n, err := reader.Read(data[:]); err != nil || n != int(dataLen) {
|
||||
return fmt.Errorf("failed to read call To [%d]: %w", n, err)
|
||||
}
|
||||
|
||||
callData := &EthCallData{
|
||||
To: to[:],
|
||||
Data: data[:],
|
||||
}
|
||||
|
||||
ecd.CallData = append(ecd.CallData, callData)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate does basic validation on an EVM eth_call query.
|
||||
func (ecd *EthCallQueryRequest) Validate() error {
|
||||
if len(ecd.BlockId) > math.MaxUint32 {
|
||||
return fmt.Errorf("block id too long")
|
||||
}
|
||||
if !strings.HasPrefix(ecd.BlockId, "0x") {
|
||||
return fmt.Errorf("block id must be a hex number or hash starting with 0x")
|
||||
}
|
||||
if len(ecd.CallData) <= 0 {
|
||||
return fmt.Errorf("does not contain any call data")
|
||||
}
|
||||
if len(ecd.CallData) > math.MaxUint8 {
|
||||
return fmt.Errorf("too many call data entries")
|
||||
}
|
||||
for _, callData := range ecd.CallData {
|
||||
if callData.To == nil || len(callData.To) <= 0 {
|
||||
return fmt.Errorf("no call data to")
|
||||
}
|
||||
if len(callData.To) != EvmContractAddressLength {
|
||||
return fmt.Errorf("invalid length for To contract")
|
||||
}
|
||||
if callData.Data == nil || len(callData.Data) <= 0 {
|
||||
return fmt.Errorf("no call data data")
|
||||
}
|
||||
if len(callData.Data) > math.MaxUint32 {
|
||||
return fmt.Errorf("call data data too long")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal verifies that two EVM eth_call queries are equal.
|
||||
func (left *EthCallQueryRequest) Equal(right *EthCallQueryRequest) bool {
|
||||
if left.BlockId != right.BlockId {
|
||||
return false
|
||||
}
|
||||
if len(left.CallData) != len(right.CallData) {
|
||||
return false
|
||||
}
|
||||
for idx := range left.CallData {
|
||||
if !bytes.Equal(left.CallData[idx].To, right.CallData[idx].To) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(left.CallData[idx].Data, right.CallData[idx].Data) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func ValidatePerChainQueryRequestType(qt ChainSpecificQueryType) error {
|
||||
if qt != EthCallQueryRequestType {
|
||||
return fmt.Errorf("invalid query request type: %d", qt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SignedQueryRequestEqual(left *gossipv1.SignedQueryRequest, right *gossipv1.SignedQueryRequest) bool {
|
||||
if !bytes.Equal(left.QueryRequest, right.QueryRequest) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(left.Signature, right.Signature) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
)
|
||||
|
||||
func createQueryRequestForTesting(t *testing.T, chainId vaa.ChainID) *QueryRequest {
|
||||
t.Helper()
|
||||
// Create a query request.
|
||||
wethAbi, err := abi.JSON(strings.NewReader("[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
data1, err := wethAbi.Pack("name")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data2, err := wethAbi.Pack("totalSupply")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
to, _ := hex.DecodeString("0d500b1d8e8ef31e21c99d1db9a6444d3adf1270")
|
||||
block := "0x28d9630"
|
||||
callData := []*EthCallData{
|
||||
{
|
||||
To: to,
|
||||
Data: data1,
|
||||
},
|
||||
{
|
||||
To: to,
|
||||
Data: data2,
|
||||
},
|
||||
}
|
||||
callRequest := &EthCallQueryRequest{
|
||||
BlockId: block,
|
||||
CallData: callData,
|
||||
}
|
||||
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: chainId,
|
||||
Query: callRequest,
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
|
||||
return queryRequest
|
||||
}
|
||||
|
||||
// A timestamp has nanos, but we only marshal down to micros, so trim our time to micros for testing purposes.
|
||||
func timeForTest(t *testing.T, ts time.Time) time.Time {
|
||||
t.Helper()
|
||||
return time.UnixMicro(ts.UnixMicro())
|
||||
}
|
||||
|
||||
func TestQueryRequestMarshalUnmarshal(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
queryRequestBytes, err := queryRequest.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
var queryRequest2 QueryRequest
|
||||
err = queryRequest2.Unmarshal(queryRequestBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, queryRequest.Equal(&queryRequest2))
|
||||
}
|
||||
|
||||
func TestMarshalOfQueryRequestWithNoPerChainQueriesShouldFail(t *testing.T) {
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{
|
||||
{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
// Leave Query nil.
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfQueryRequestWithTooManyPerChainQueriesShouldFail(t *testing.T) {
|
||||
perChainQueries := []*PerChainQueryRequest{}
|
||||
for count := 0; count < 300; count++ {
|
||||
callData := []*EthCallData{{
|
||||
|
||||
To: []byte(fmt.Sprintf("%-20s", fmt.Sprintf("To for %d", count))),
|
||||
Data: []byte(fmt.Sprintf("CallData for %d", count)),
|
||||
},
|
||||
}
|
||||
|
||||
perChainQueries = append(perChainQueries, &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: callData,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: perChainQueries,
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfQueryRequestForInvalidChainIdShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDUnset)
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfQueryRequestWithInvalidBlockIdShouldFail(t *testing.T) {
|
||||
callData := []*EthCallData{{
|
||||
To: []byte(fmt.Sprintf("%-20s", fmt.Sprintf("To for %d", 0))),
|
||||
Data: []byte(fmt.Sprintf("CallData for %d", 0)),
|
||||
}}
|
||||
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "latest",
|
||||
CallData: callData,
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfQueryRequestWithNoCallDataEntriesShouldFail(t *testing.T) {
|
||||
callData := []*EthCallData{}
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: callData,
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfQueryRequestWithNilCallDataEntriesShouldFail(t *testing.T) {
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: nil,
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfQueryRequestWithTooManyCallDataEntriesShouldFail(t *testing.T) {
|
||||
callData := []*EthCallData{}
|
||||
for count := 0; count < 300; count++ {
|
||||
callData = append(callData, &EthCallData{
|
||||
To: []byte(fmt.Sprintf("%-20s", fmt.Sprintf("To for %d", count))),
|
||||
Data: []byte(fmt.Sprintf("CallData for %d", count)),
|
||||
})
|
||||
}
|
||||
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: callData,
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfEthCallQueryWithNilToShouldFail(t *testing.T) {
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: []*EthCallData{
|
||||
{
|
||||
To: nil,
|
||||
Data: []byte("This can't be zero length"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfEthCallQueryWithEmptyToShouldFail(t *testing.T) {
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: []*EthCallData{
|
||||
{
|
||||
To: []byte{},
|
||||
Data: []byte("This can't be zero length"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfEthCallQueryWithWrongLengthToShouldFail(t *testing.T) {
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: []*EthCallData{
|
||||
{
|
||||
To: []byte("TooShort"),
|
||||
Data: []byte("This can't be zero length"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfEthCallQueryWithNilDataShouldFail(t *testing.T) {
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: []*EthCallData{
|
||||
{
|
||||
To: []byte(fmt.Sprintf("%-20s", fmt.Sprintf("To for %d", 0))),
|
||||
Data: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfEthCallQueryWithEmptyDataShouldFail(t *testing.T) {
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: []*EthCallData{
|
||||
{
|
||||
To: []byte(fmt.Sprintf("%-20s", fmt.Sprintf("To for %d", 0))),
|
||||
Data: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalOfEthCallQueryWithWrongToLengthShouldFail(t *testing.T) {
|
||||
perChainQuery := &PerChainQueryRequest{
|
||||
ChainId: vaa.ChainIDPolygon,
|
||||
Query: &EthCallQueryRequest{
|
||||
BlockId: "0x28d9630",
|
||||
CallData: []*EthCallData{
|
||||
{
|
||||
To: []byte("This is too short!"),
|
||||
Data: []byte("This can't be zero length"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
queryRequest := &QueryRequest{
|
||||
Nonce: 1,
|
||||
PerChainQueries: []*PerChainQueryRequest{perChainQuery},
|
||||
}
|
||||
_, err := queryRequest.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPostSignedQueryRequestShouldFailIfNoOneIsListening(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
queryRequestBytes, err := queryRequest.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
sig := [65]byte{}
|
||||
signedQueryRequest := &gossipv1.SignedQueryRequest{
|
||||
QueryRequest: queryRequestBytes,
|
||||
Signature: sig[:],
|
||||
}
|
||||
|
||||
var signedQueryReqSendC chan<- *gossipv1.SignedQueryRequest
|
||||
assert.Error(t, PostSignedQueryRequest(signedQueryReqSendC, signedQueryRequest))
|
||||
}
|
|
@ -0,0 +1,507 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
)
|
||||
|
||||
// QueryStatus is the status returned from the watcher to the query handler.
|
||||
type QueryStatus int
|
||||
|
||||
const (
|
||||
// QuerySuccess means the query was successful and the response should be returned to the requester.
|
||||
QuerySuccess QueryStatus = 1
|
||||
|
||||
// QueryRetryNeeded means the query failed, but a retry may be helpful.
|
||||
QueryRetryNeeded QueryStatus = 0
|
||||
|
||||
// QueryFatalError means the query failed, and there is no point in retrying it.
|
||||
QueryFatalError QueryStatus = -1
|
||||
)
|
||||
|
||||
// This is the query response returned from the watcher to the query handler.
|
||||
type PerChainQueryResponseInternal struct {
|
||||
RequestID string
|
||||
RequestIdx int
|
||||
ChainId vaa.ChainID
|
||||
Status QueryStatus
|
||||
Response ChainSpecificResponse
|
||||
}
|
||||
|
||||
// CreatePerChainQueryResponseInternal creates a PerChainQueryResponseInternal and returns a pointer to it.
|
||||
func CreatePerChainQueryResponseInternal(reqId string, reqIdx int, chainId vaa.ChainID, status QueryStatus, response ChainSpecificResponse) *PerChainQueryResponseInternal {
|
||||
return &PerChainQueryResponseInternal{
|
||||
RequestID: reqId,
|
||||
RequestIdx: reqIdx,
|
||||
ChainId: chainId,
|
||||
Status: status,
|
||||
Response: response,
|
||||
}
|
||||
}
|
||||
|
||||
var queryResponsePrefix = []byte("query_response_0000000000000000000|")
|
||||
|
||||
// QueryResponsePublication is the response to a QueryRequest.
|
||||
type QueryResponsePublication struct {
|
||||
Request *gossipv1.SignedQueryRequest
|
||||
PerChainResponses []*PerChainQueryResponse
|
||||
}
|
||||
|
||||
// PerChainQueryResponse represents a query response for a single chain.
|
||||
type PerChainQueryResponse struct {
|
||||
// ChainId indicates which chain this query was destine for.
|
||||
ChainId vaa.ChainID
|
||||
|
||||
// Response is the chain specific query data.
|
||||
Response ChainSpecificResponse
|
||||
}
|
||||
|
||||
// ChainSpecificResponse is the interface that must be implemented by a chain specific response.
|
||||
type ChainSpecificResponse interface {
|
||||
Type() ChainSpecificQueryType
|
||||
Marshal() ([]byte, error)
|
||||
Unmarshal(data []byte) error
|
||||
UnmarshalFromReader(reader *bytes.Reader) error
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// EthCallQueryResponse implements ChainSpecificResponse for an EVM eth_call query response.
|
||||
type EthCallQueryResponse struct {
|
||||
BlockNumber uint64
|
||||
Hash common.Hash
|
||||
Time time.Time
|
||||
|
||||
// Results is the array of responses matching CallData in EthCallQueryRequest
|
||||
Results [][]byte
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of QueryResponsePublication.
|
||||
//
|
||||
|
||||
// Marshal serializes the binary representation of a query response.
|
||||
// This method calls Validate() and relies on it to range checks lengths, etc.
|
||||
func (msg *QueryResponsePublication) Marshal() ([]byte, error) {
|
||||
if err := msg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint8(1)) // version
|
||||
|
||||
// Source
|
||||
// TODO: support writing off-chain and on-chain requests
|
||||
// Here, unset represents an off-chain request
|
||||
vaa.MustWrite(buf, binary.BigEndian, vaa.ChainIDUnset)
|
||||
|
||||
buf.Write(msg.Request.Signature[:])
|
||||
|
||||
// Write the length of the request to facilitate on-chain parsing.
|
||||
if len(msg.Request.QueryRequest) > math.MaxUint32 {
|
||||
return nil, fmt.Errorf("request too long")
|
||||
}
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint32(len(msg.Request.QueryRequest)))
|
||||
|
||||
buf.Write(msg.Request.QueryRequest)
|
||||
|
||||
// Per chain responses
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint8(len(msg.PerChainResponses)))
|
||||
for idx := range msg.PerChainResponses {
|
||||
pcrBuf, err := msg.PerChainResponses[idx].Marshal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal per chain response: %w", err)
|
||||
}
|
||||
buf.Write(pcrBuf)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal deserializes the binary representation of a query response
|
||||
func (msg *QueryResponsePublication) Unmarshal(data []byte) error {
|
||||
reader := bytes.NewReader(data[:])
|
||||
|
||||
var version uint8
|
||||
if err := binary.Read(reader, binary.BigEndian, &version); err != nil {
|
||||
return fmt.Errorf("failed to read message version: %w", err)
|
||||
}
|
||||
|
||||
if version != 1 {
|
||||
return fmt.Errorf("unsupported message version: %d", version)
|
||||
}
|
||||
|
||||
// Request
|
||||
requestChain := vaa.ChainID(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &requestChain); err != nil {
|
||||
return fmt.Errorf("failed to read request chain: %w", err)
|
||||
}
|
||||
if requestChain != vaa.ChainIDUnset {
|
||||
// TODO: support reading off-chain and on-chain requests
|
||||
return fmt.Errorf("unsupported request chain: %d", requestChain)
|
||||
}
|
||||
|
||||
signedQueryRequest := &gossipv1.SignedQueryRequest{}
|
||||
signature := [65]byte{}
|
||||
if n, err := reader.Read(signature[:]); err != nil || n != 65 {
|
||||
return fmt.Errorf("failed to read signature [%d]: %w", n, err)
|
||||
}
|
||||
signedQueryRequest.Signature = signature[:]
|
||||
|
||||
// Skip the query length.
|
||||
queryRequestLen := uint32(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &queryRequestLen); err != nil {
|
||||
return fmt.Errorf("failed to read length of query request: %w", err)
|
||||
}
|
||||
|
||||
queryRequest := QueryRequest{}
|
||||
err := queryRequest.UnmarshalFromReader(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal query request: %w", err)
|
||||
}
|
||||
|
||||
queryRequestBytes, err := queryRequest.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedQueryRequest.QueryRequest = queryRequestBytes
|
||||
|
||||
msg.Request = signedQueryRequest
|
||||
|
||||
// Responses
|
||||
numPerChainResponses := uint8(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &numPerChainResponses); err != nil {
|
||||
return fmt.Errorf("failed to read number of per chain responses: %w", err)
|
||||
}
|
||||
|
||||
for count := 0; count < int(numPerChainResponses); count++ {
|
||||
var pcr PerChainQueryResponse
|
||||
err := pcr.UnmarshalFromReader(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal per chain response: %w", err)
|
||||
}
|
||||
msg.PerChainResponses = append(msg.PerChainResponses, &pcr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate does basic validation on a received query request.
|
||||
func (msg *QueryResponsePublication) Validate() error {
|
||||
// Unmarshal and validate the contained query request.
|
||||
var queryRequest QueryRequest
|
||||
err := queryRequest.Unmarshal(msg.Request.QueryRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal query request")
|
||||
}
|
||||
if err := queryRequest.Validate(); err != nil {
|
||||
return fmt.Errorf("query request is invalid: %w", err)
|
||||
}
|
||||
|
||||
if len(msg.PerChainResponses) <= 0 {
|
||||
return fmt.Errorf("response does not contain any per chain responses")
|
||||
}
|
||||
if len(msg.PerChainResponses) > math.MaxUint8 {
|
||||
return fmt.Errorf("too many per chain responses")
|
||||
}
|
||||
if len(msg.PerChainResponses) != len(queryRequest.PerChainQueries) {
|
||||
return fmt.Errorf("number of responses does not match number of queries")
|
||||
}
|
||||
for idx, pcr := range msg.PerChainResponses {
|
||||
if err := pcr.Validate(); err != nil {
|
||||
return fmt.Errorf("failed to validate per chain query %d: %w", idx, err)
|
||||
}
|
||||
if pcr.Response.Type() != queryRequest.PerChainQueries[idx].Query.Type() {
|
||||
return fmt.Errorf("type of response %d does not match the query", idx)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal checks for equality on two query response publications.
|
||||
func (left *QueryResponsePublication) Equal(right *QueryResponsePublication) bool {
|
||||
if !bytes.Equal(left.Request.QueryRequest, right.Request.QueryRequest) || !bytes.Equal(left.Request.Signature, right.Request.Signature) {
|
||||
return false
|
||||
}
|
||||
if len(left.PerChainResponses) != len(right.PerChainResponses) {
|
||||
return false
|
||||
}
|
||||
for idx := range left.PerChainResponses {
|
||||
if !left.PerChainResponses[idx].Equal(right.PerChainResponses[idx]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (resp *QueryResponsePublication) RequestID() string {
|
||||
if resp == nil || resp.Request == nil {
|
||||
return "nil"
|
||||
}
|
||||
return hex.EncodeToString(resp.Request.Signature)
|
||||
}
|
||||
|
||||
// Similar to sdk/vaa/structs.go,
|
||||
// In order to save space in the solana signature verification instruction, we hash twice so we only need to pass in
|
||||
// the first hash (32 bytes) vs the full body data.
|
||||
func (msg *QueryResponsePublication) SigningDigest() (common.Hash, error) {
|
||||
msgBytes, err := msg.Marshal()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return GetQueryResponseDigestFromBytes(msgBytes), nil
|
||||
}
|
||||
|
||||
// GetQueryResponseDigestFromBytes computes the digest bytes for a query response byte array.
|
||||
func GetQueryResponseDigestFromBytes(b []byte) common.Hash {
|
||||
return crypto.Keccak256Hash(append(queryResponsePrefix, crypto.Keccak256Hash(b).Bytes()...))
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of PerChainQueryResponse.
|
||||
//
|
||||
|
||||
// Marshal marshalls a per chain query response.
|
||||
func (perChainResponse *PerChainQueryResponse) Marshal() ([]byte, error) {
|
||||
if err := perChainResponse.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
vaa.MustWrite(buf, binary.BigEndian, perChainResponse.ChainId)
|
||||
vaa.MustWrite(buf, binary.BigEndian, perChainResponse.Response.Type())
|
||||
respBuf, err := perChainResponse.Response.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the length of the response to facilitate on-chain parsing.
|
||||
if len(respBuf) > math.MaxUint32 {
|
||||
return nil, fmt.Errorf("response is too long")
|
||||
}
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint32(len(respBuf)))
|
||||
buf.Write(respBuf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal deserializes the binary representation of a per chain query response from a byte array
|
||||
func (perChainResponse *PerChainQueryResponse) Unmarshal(data []byte) error {
|
||||
reader := bytes.NewReader(data[:])
|
||||
return perChainResponse.UnmarshalFromReader(reader)
|
||||
}
|
||||
|
||||
// UnmarshalFromReader deserializes the binary representation of a per chain query response from an existing reader
|
||||
func (perChainResponse *PerChainQueryResponse) UnmarshalFromReader(reader *bytes.Reader) error {
|
||||
if err := binary.Read(reader, binary.BigEndian, &perChainResponse.ChainId); err != nil {
|
||||
return fmt.Errorf("failed to read response chain: %w", err)
|
||||
}
|
||||
|
||||
qt := uint8(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &qt); err != nil {
|
||||
return fmt.Errorf("failed to read response type: %w", err)
|
||||
}
|
||||
queryType := ChainSpecificQueryType(qt)
|
||||
|
||||
if err := ValidatePerChainQueryRequestType(queryType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip the response length.
|
||||
var respLength uint32
|
||||
if err := binary.Read(reader, binary.BigEndian, &respLength); err != nil {
|
||||
return fmt.Errorf("failed to read response length: %w", err)
|
||||
}
|
||||
|
||||
switch queryType {
|
||||
case EthCallQueryRequestType:
|
||||
r := EthCallQueryResponse{}
|
||||
if err := r.UnmarshalFromReader(reader); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal eth call response: %w", err)
|
||||
}
|
||||
perChainResponse.Response = &r
|
||||
default:
|
||||
return fmt.Errorf("unsupported query type: %d", queryType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePerChainResponse performs basic validation on a per chain query response.
|
||||
func (perChainResponse *PerChainQueryResponse) Validate() error {
|
||||
str := perChainResponse.ChainId.String()
|
||||
if _, err := vaa.ChainIDFromString(str); err != nil {
|
||||
return fmt.Errorf("invalid chainID: %d", uint16(perChainResponse.ChainId))
|
||||
}
|
||||
|
||||
if perChainResponse.Response == nil {
|
||||
return fmt.Errorf("response is nil")
|
||||
}
|
||||
|
||||
if err := ValidatePerChainQueryRequestType(perChainResponse.Response.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := perChainResponse.Response.Validate(); err != nil {
|
||||
return fmt.Errorf("chain specific response is invalid: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal checks for equality on two per chain query responses.
|
||||
func (left *PerChainQueryResponse) Equal(right *PerChainQueryResponse) bool {
|
||||
if left.ChainId != right.ChainId {
|
||||
return false
|
||||
}
|
||||
|
||||
if left.Response == nil && right.Response == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if left.Response == nil || right.Response == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if left.Response.Type() != right.Response.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
switch leftEcq := left.Response.(type) {
|
||||
case *EthCallQueryResponse:
|
||||
switch rightEcd := right.Response.(type) {
|
||||
case *EthCallQueryResponse:
|
||||
return leftEcq.Equal(rightEcd)
|
||||
default:
|
||||
panic("unsupported query type on right") // We checked this above!
|
||||
}
|
||||
default:
|
||||
panic("unsupported query type on left") // We checked this above!
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of EthCallQueryResponse, which implements the ChainSpecificResponse for an EVM eth_call query response.
|
||||
//
|
||||
|
||||
func (e *EthCallQueryResponse) Type() ChainSpecificQueryType {
|
||||
return EthCallQueryRequestType
|
||||
}
|
||||
|
||||
// Marshal serializes the binary representation of an EVM eth_call response.
|
||||
// This method calls Validate() and relies on it to range checks lengths, etc.
|
||||
func (ecr *EthCallQueryResponse) Marshal() ([]byte, error) {
|
||||
if err := ecr.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
vaa.MustWrite(buf, binary.BigEndian, ecr.BlockNumber)
|
||||
buf.Write(ecr.Hash[:])
|
||||
vaa.MustWrite(buf, binary.BigEndian, ecr.Time.UnixMicro())
|
||||
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint8(len(ecr.Results)))
|
||||
for idx := range ecr.Results {
|
||||
vaa.MustWrite(buf, binary.BigEndian, uint32(len(ecr.Results[idx])))
|
||||
buf.Write(ecr.Results[idx])
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal deserializes an EVM eth_call response from a byte array
|
||||
func (ecr *EthCallQueryResponse) Unmarshal(data []byte) error {
|
||||
reader := bytes.NewReader(data[:])
|
||||
return ecr.UnmarshalFromReader(reader)
|
||||
}
|
||||
|
||||
// UnmarshalFromReader deserializes an EVM eth_call response from a byte array
|
||||
func (ecr *EthCallQueryResponse) UnmarshalFromReader(reader *bytes.Reader) error {
|
||||
if err := binary.Read(reader, binary.BigEndian, &ecr.BlockNumber); err != nil {
|
||||
return fmt.Errorf("failed to read response number: %w", err)
|
||||
}
|
||||
|
||||
responseHash := common.Hash{}
|
||||
if n, err := reader.Read(responseHash[:]); err != nil || n != 32 {
|
||||
return fmt.Errorf("failed to read response hash [%d]: %w", n, err)
|
||||
}
|
||||
ecr.Hash = responseHash
|
||||
|
||||
unixMicros := int64(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &unixMicros); err != nil {
|
||||
return fmt.Errorf("failed to read response timestamp: %w", err)
|
||||
}
|
||||
ecr.Time = time.UnixMicro(unixMicros)
|
||||
|
||||
numResults := uint8(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &numResults); err != nil {
|
||||
return fmt.Errorf("failed to read number of results: %w", err)
|
||||
}
|
||||
|
||||
for count := 0; count < int(numResults); count++ {
|
||||
resultLen := uint32(0)
|
||||
if err := binary.Read(reader, binary.BigEndian, &resultLen); err != nil {
|
||||
return fmt.Errorf("failed to read result len: %w", err)
|
||||
}
|
||||
result := make([]byte, resultLen)
|
||||
if n, err := reader.Read(result[:]); err != nil || n != int(resultLen) {
|
||||
return fmt.Errorf("failed to read result [%d]: %w", n, err)
|
||||
}
|
||||
|
||||
ecr.Results = append(ecr.Results, result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate does basic validation on an EVM eth_call response.
|
||||
func (ecr *EthCallQueryResponse) Validate() error {
|
||||
// Not checking for BlockNumber == 0, because maybe that could happen??
|
||||
|
||||
if len(ecr.Hash) != 32 {
|
||||
return fmt.Errorf("invalid length for block hash")
|
||||
}
|
||||
|
||||
if len(ecr.Results) <= 0 {
|
||||
return fmt.Errorf("does not contain any results")
|
||||
}
|
||||
if len(ecr.Results) > math.MaxUint8 {
|
||||
return fmt.Errorf("too many results")
|
||||
}
|
||||
for _, result := range ecr.Results {
|
||||
if len(result) > math.MaxUint32 {
|
||||
return fmt.Errorf("result too long")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal verifies that two EVM eth_call responses are equal.
|
||||
func (left *EthCallQueryResponse) Equal(right *EthCallQueryResponse) bool {
|
||||
if left.BlockNumber != right.BlockNumber {
|
||||
return false
|
||||
}
|
||||
|
||||
if !bytes.Equal(left.Hash.Bytes(), right.Hash.Bytes()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(left.Results) != len(right.Results) {
|
||||
return false
|
||||
}
|
||||
for idx := range left.Results {
|
||||
if !bytes.Equal(left.Results[idx], right.Results[idx]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func createQueryResponseFromRequest(t *testing.T, queryRequest *QueryRequest) *QueryResponsePublication {
|
||||
queryRequestBytes, err := queryRequest.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
sig := [65]byte{}
|
||||
signedQueryRequest := &gossipv1.SignedQueryRequest{
|
||||
QueryRequest: queryRequestBytes,
|
||||
Signature: sig[:],
|
||||
}
|
||||
|
||||
perChainResponses := []*PerChainQueryResponse{}
|
||||
for idx, pcr := range queryRequest.PerChainQueries {
|
||||
switch req := pcr.Query.(type) {
|
||||
case *EthCallQueryRequest:
|
||||
results := [][]byte{}
|
||||
for idx := range req.CallData {
|
||||
result := []byte([]byte(fmt.Sprintf("Result %d", idx)))
|
||||
results = append(results, result[:])
|
||||
}
|
||||
perChainResponses = append(perChainResponses, &PerChainQueryResponse{
|
||||
ChainId: pcr.ChainId,
|
||||
Response: &EthCallQueryResponse{
|
||||
BlockNumber: uint64(1000 + idx),
|
||||
Hash: ethCommon.HexToHash("0x9999bac44d09a7f69ee7941819b0a19c59ccb1969640cc513be09ef95ed2d8e2"),
|
||||
Time: timeForTest(t, time.Now()),
|
||||
Results: results,
|
||||
},
|
||||
})
|
||||
default:
|
||||
panic("invalid query type!")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &QueryResponsePublication{
|
||||
Request: signedQueryRequest,
|
||||
PerChainResponses: perChainResponses,
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryResponseMarshalUnmarshal(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
respPubBytes, err := respPub.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
var respPub2 QueryResponsePublication
|
||||
err = respPub2.Unmarshal(respPubBytes)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, respPub2)
|
||||
|
||||
assert.True(t, respPub.Equal(&respPub2))
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithNoPerChainResponsesShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
queryRequestBytes, err := queryRequest.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
sig := [65]byte{}
|
||||
signedQueryRequest := &gossipv1.SignedQueryRequest{
|
||||
QueryRequest: queryRequestBytes,
|
||||
Signature: sig[:],
|
||||
}
|
||||
|
||||
respPub := &QueryResponsePublication{
|
||||
Request: signedQueryRequest,
|
||||
PerChainResponses: []*PerChainQueryResponse{},
|
||||
}
|
||||
|
||||
_, err = respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithNilPerChainResponsesShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
queryRequestBytes, err := queryRequest.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
sig := [65]byte{}
|
||||
signedQueryRequest := &gossipv1.SignedQueryRequest{
|
||||
QueryRequest: queryRequestBytes,
|
||||
Signature: sig[:],
|
||||
}
|
||||
|
||||
respPub := &QueryResponsePublication{
|
||||
Request: signedQueryRequest,
|
||||
PerChainResponses: nil,
|
||||
}
|
||||
|
||||
_, err = respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithTooManyPerChainResponsesShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
for count := 0; count < 300; count++ {
|
||||
respPub.PerChainResponses = append(respPub.PerChainResponses, respPub.PerChainResponses[0])
|
||||
}
|
||||
|
||||
_, err := respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithWrongNumberOfPerChainResponsesShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
respPub.PerChainResponses = append(respPub.PerChainResponses, respPub.PerChainResponses[0])
|
||||
|
||||
_, err := respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithInvalidChainIDShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
respPub.PerChainResponses[0].ChainId = vaa.ChainIDUnset
|
||||
|
||||
_, err := respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithNilResponseShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
respPub.PerChainResponses[0].Response = nil
|
||||
|
||||
_, err := respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithNoResultsShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
switch resp := respPub.PerChainResponses[0].Response.(type) {
|
||||
case *EthCallQueryResponse:
|
||||
resp.Results = [][]byte{}
|
||||
default:
|
||||
panic("invalid query type!")
|
||||
}
|
||||
|
||||
_, err := respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithNilResultsShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
switch resp := respPub.PerChainResponses[0].Response.(type) {
|
||||
case *EthCallQueryResponse:
|
||||
resp.Results = nil
|
||||
default:
|
||||
panic("invalid query type!")
|
||||
}
|
||||
|
||||
_, err := respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalQueryResponseWithTooManyResultsShouldFail(t *testing.T) {
|
||||
queryRequest := createQueryRequestForTesting(t, vaa.ChainIDPolygon)
|
||||
respPub := createQueryResponseFromRequest(t, queryRequest)
|
||||
|
||||
results := [][]byte{}
|
||||
for count := 0; count < 300; count++ {
|
||||
results = append(results, []byte{})
|
||||
}
|
||||
|
||||
switch resp := respPub.PerChainResponses[0].Response.(type) {
|
||||
case *EthCallQueryResponse:
|
||||
resp.Results = results
|
||||
default:
|
||||
panic("invalid query type!")
|
||||
}
|
||||
|
||||
_, err := respPub.Marshal()
|
||||
require.Error(t, err)
|
||||
}
|
|
@ -3,6 +3,7 @@ package algorand
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -38,6 +39,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
_ <-chan *query.PerChainQueryInternal,
|
||||
_ chan<- *query.PerChainQueryResponseInternal,
|
||||
_ chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package aptos
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -36,6 +37,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
_ <-chan *query.PerChainQueryInternal,
|
||||
_ chan<- *query.PerChainQueryResponseInternal,
|
||||
_ chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package cosmwasm
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -36,6 +37,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
_ <-chan *query.PerChainQueryInternal,
|
||||
_ chan<- *query.PerChainQueryResponseInternal,
|
||||
_ chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package evm
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -42,6 +43,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
queryReqC <-chan *query.PerChainQueryInternal,
|
||||
queryResponseC chan<- *query.PerChainQueryResponseInternal,
|
||||
setC chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
@ -54,7 +57,7 @@ func (wc *WatcherConfig) Create(
|
|||
|
||||
var devMode bool = (env == common.UnsafeDevNet)
|
||||
|
||||
watcher := NewEthWatcher(wc.Rpc, eth_common.HexToAddress(wc.Contract), string(wc.NetworkID), wc.ChainID, msgC, setWriteC, obsvReqC, devMode)
|
||||
watcher := NewEthWatcher(wc.Rpc, eth_common.HexToAddress(wc.Contract), string(wc.NetworkID), wc.ChainID, msgC, setWriteC, obsvReqC, queryReqC, queryResponseC, devMode)
|
||||
watcher.SetWaitForConfirmations(wc.WaitForConfirmations)
|
||||
if err := watcher.SetRootChainParams(wc.RootChainRpc, wc.RootChainContract); err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
ethTypes "github.com/ethereum/go-ethereum/core/types"
|
||||
ethEvent "github.com/ethereum/go-ethereum/event"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
"go.uber.org/zap"
|
||||
|
@ -181,6 +182,19 @@ func (c *CeloConnector) RawCallContext(ctx context.Context, result interface{},
|
|||
return c.rawClient.CallContext(ctx, result, method, args...)
|
||||
}
|
||||
|
||||
func (c *CeloConnector) RawBatchCallContext(ctx context.Context, b []ethRpc.BatchElem) error {
|
||||
celoB := make([]celoRpc.BatchElem, len(b))
|
||||
for i, v := range b {
|
||||
celoB[i] = celoRpc.BatchElem{
|
||||
Method: v.Method,
|
||||
Args: v.Args,
|
||||
Result: v.Result,
|
||||
Error: v.Error,
|
||||
}
|
||||
}
|
||||
return c.rawClient.BatchCallContext(ctx, celoB)
|
||||
}
|
||||
|
||||
func convertCeloEventToEth(ev *celoAbi.AbiLogMessagePublished) *ethAbi.AbiLogMessagePublished {
|
||||
return ðAbi.AbiLogMessagePublished{
|
||||
Sender: ethCommon.BytesToAddress(ev.Sender.Bytes()),
|
||||
|
|
|
@ -10,10 +10,22 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
type BlockMarshaller struct {
|
||||
Number *hexutil.Big
|
||||
Hash common.Hash `json:"hash"`
|
||||
Time hexutil.Uint64 `json:"timestamp"`
|
||||
|
||||
// L1BlockNumber is the L1 block number in which an Arbitrum batch containing this block was submitted.
|
||||
// This field is only populated when connecting to Arbitrum.
|
||||
L1BlockNumber *hexutil.Big
|
||||
}
|
||||
|
||||
type NewBlock struct {
|
||||
Number *big.Int
|
||||
Hash common.Hash
|
||||
|
@ -33,6 +45,7 @@ type Connector interface {
|
|||
ParseLogMessagePublished(log types.Log) (*ethabi.AbiLogMessagePublished, error)
|
||||
SubscribeForBlocks(ctx context.Context, errC chan error, sink chan<- *NewBlock) (ethereum.Subscription, error)
|
||||
RawCallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
|
||||
RawBatchCallContext(ctx context.Context, b []rpc.BatchElem) error
|
||||
}
|
||||
|
||||
type PollSubscription struct {
|
||||
|
|
|
@ -133,7 +133,10 @@ func (e *EthereumConnector) SubscribeForBlocks(ctx context.Context, errC chan er
|
|||
|
||||
func (e *EthereumConnector) RawCallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||
return e.rawClient.CallContext(ctx, result, method, args...)
|
||||
}
|
||||
|
||||
func (e *EthereumConnector) RawBatchCallContext(ctx context.Context, b []ethRpc.BatchElem) error {
|
||||
return e.rawClient.BatchCallContext(ctx, b)
|
||||
}
|
||||
|
||||
func (e *EthereumConnector) Client() *ethClient.Client {
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
ethEvent "github.com/ethereum/go-ethereum/event"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
ethHexUtils "github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
@ -224,16 +223,7 @@ func getBlock(ctx context.Context, logger *zap.Logger, conn Connector, number *b
|
|||
numStr = "latest"
|
||||
}
|
||||
|
||||
type Marshaller struct {
|
||||
Number *ethHexUtils.Big
|
||||
Hash ethCommon.Hash `json:"hash"`
|
||||
|
||||
// L1BlockNumber is the L1 block number in which an Arbitrum batch containing this block was submitted.
|
||||
// This field is only populated when connecting to Arbitrum.
|
||||
L1BlockNumber *ethHexUtils.Big
|
||||
}
|
||||
|
||||
var m Marshaller
|
||||
var m BlockMarshaller
|
||||
err := conn.RawCallContext(ctx, &m, "eth_getBlockByNumber", numStr, false)
|
||||
if err != nil {
|
||||
logger.Error("failed to get block",
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
ethTypes "github.com/ethereum/go-ethereum/core/types"
|
||||
ethClient "github.com/ethereum/go-ethereum/ethclient"
|
||||
ethEvent "github.com/ethereum/go-ethereum/event"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// mockConnectorForPoller implements the connector interface for testing purposes.
|
||||
|
@ -107,6 +108,10 @@ func (e *mockConnectorForPoller) RawCallContext(ctx context.Context, result inte
|
|||
return
|
||||
}
|
||||
|
||||
func (e *mockConnectorForPoller) RawBatchCallContext(ctx context.Context, b []ethRpc.BatchElem) error {
|
||||
panic("method not implemented by mockConnectorForPoller")
|
||||
}
|
||||
|
||||
func (e *mockConnectorForPoller) setBlockNumber(blockNumber uint64) {
|
||||
e.mutex.Lock()
|
||||
e.blockNumber = blockNumber
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||
ethTypes "github.com/ethereum/go-ethereum/core/types"
|
||||
ethEvent "github.com/ethereum/go-ethereum/event"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -36,6 +37,10 @@ func (e *moonbeamMockConnector) RawCallContext(ctx context.Context, result inter
|
|||
return
|
||||
}
|
||||
|
||||
func (e *moonbeamMockConnector) RawBatchCallContext(ctx context.Context, b []ethRpc.BatchElem) error {
|
||||
panic("method not implemented by moonbeamMockConnector")
|
||||
}
|
||||
|
||||
func (e *moonbeamMockConnector) NetworkName() string {
|
||||
return "moonbeamMockConnector"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package evm
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -24,6 +26,7 @@ import (
|
|||
"go.uber.org/zap"
|
||||
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/readiness"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
@ -94,6 +97,13 @@ type (
|
|||
// include requests for our chainID.
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest
|
||||
|
||||
// Incoming query requests from the network. Pre-filtered to only
|
||||
// include requests for our chainID.
|
||||
queryReqC <-chan *query.PerChainQueryInternal
|
||||
|
||||
// Outbound query responses to query requests
|
||||
queryResponseC chan<- *query.PerChainQueryResponseInternal
|
||||
|
||||
pending map[pendingKey]*pendingMessage
|
||||
pendingMu sync.Mutex
|
||||
|
||||
|
@ -143,6 +153,8 @@ func NewEthWatcher(
|
|||
msgC chan<- *common.MessagePublication,
|
||||
setC chan<- *common.GuardianSet,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
queryReqC <-chan *query.PerChainQueryInternal,
|
||||
queryResponseC chan<- *query.PerChainQueryResponseInternal,
|
||||
unsafeDevMode bool,
|
||||
) *Watcher {
|
||||
|
||||
|
@ -157,6 +169,8 @@ func NewEthWatcher(
|
|||
msgC: msgC,
|
||||
setC: setC,
|
||||
obsvReqC: obsvReqC,
|
||||
queryReqC: queryReqC,
|
||||
queryResponseC: queryResponseC,
|
||||
pending: map[pendingKey]*pendingMessage{},
|
||||
unsafeDevMode: unsafeDevMode,
|
||||
}
|
||||
|
@ -515,6 +529,21 @@ func (w *Watcher) Run(parentCtx context.Context) error {
|
|||
}
|
||||
})
|
||||
|
||||
common.RunWithScissors(ctx, errC, "evm_fetch_query_req", func(ctx context.Context) error {
|
||||
ccqLogger := logger.With(
|
||||
zap.String("eth_network", w.networkName),
|
||||
zap.String("component", "ccqevm"),
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case queryRequest := <-w.queryReqC:
|
||||
w.ccqHandleQuery(ccqLogger, ctx, queryRequest)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
common.RunWithScissors(ctx, errC, "evm_fetch_messages", func(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
|
@ -930,3 +959,220 @@ func (w *Watcher) SetWaitForConfirmations(waitForConfirmations bool) {
|
|||
func (w *Watcher) SetMaxWaitConfirmations(maxWaitConfirmations uint64) {
|
||||
w.maxWaitConfirmations = maxWaitConfirmations
|
||||
}
|
||||
|
||||
// ccqSendQueryResponse sends an error response back to the query handler.
|
||||
func (w *Watcher) ccqSendQueryResponse(logger *zap.Logger, req *query.PerChainQueryInternal, status query.QueryStatus, resp *query.EthCallQueryResponse) {
|
||||
queryResponse := query.CreatePerChainQueryResponseInternal(req.RequestID, req.RequestIdx, req.Request.ChainId, status, resp)
|
||||
select {
|
||||
case w.queryResponseC <- queryResponse:
|
||||
logger.Debug("published query response error to handler", zap.String("component", "ccqevm"))
|
||||
default:
|
||||
logger.Error("failed to published query response error to handler", zap.String("component", "ccqevm"))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) ccqHandleQuery(logger *zap.Logger, ctx context.Context, queryRequest *query.PerChainQueryInternal) {
|
||||
ccqMaxBlockNumber := big.NewInt(0).SetUint64(math.MaxUint64)
|
||||
|
||||
// This can't happen unless there is a programming error - the caller
|
||||
// is expected to send us only requests for our chainID.
|
||||
if queryRequest.Request.ChainId != w.chainID {
|
||||
panic("ccqevm: invalid chain ID")
|
||||
}
|
||||
|
||||
switch req := queryRequest.Request.Query.(type) {
|
||||
case *query.EthCallQueryRequest:
|
||||
block := req.BlockId
|
||||
logger.Info("received query request",
|
||||
zap.String("block", block),
|
||||
zap.Int("numRequests", len(req.CallData)),
|
||||
)
|
||||
|
||||
timeout, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
// like https://github.com/ethereum/go-ethereum/blob/master/ethclient/ethclient.go#L610
|
||||
|
||||
var blockMethod string
|
||||
var callBlockArg interface{}
|
||||
// TODO: try making these error and see what happens
|
||||
// 1. 66 chars but not 0x hex
|
||||
// 2. 64 chars but not hex
|
||||
// 3. bad blocks
|
||||
// 4. bad 0x lengths
|
||||
// 5. strings that aren't "latest", "safe", "finalized"
|
||||
// 6. "safe" on a chain that doesn't support safe
|
||||
// etc?
|
||||
// I would expect this to trip within this scissor (if at all) but maybe this should get more defensive
|
||||
if len(block) == 66 || len(block) == 64 {
|
||||
blockMethod = "eth_getBlockByHash"
|
||||
// looks like a hash which requires the object parameter
|
||||
// https://eips.ethereum.org/EIPS/eip-1898
|
||||
// https://docs.alchemy.com/reference/eth-call
|
||||
hash := eth_common.HexToHash(block)
|
||||
callBlockArg = rpc.BlockNumberOrHash{
|
||||
BlockHash: &hash,
|
||||
RequireCanonical: true,
|
||||
}
|
||||
} else {
|
||||
blockMethod = "eth_getBlockByNumber"
|
||||
callBlockArg = block
|
||||
}
|
||||
|
||||
// EvmCallData contains the details of a single query in the batch.
|
||||
type EvmCallData struct {
|
||||
to eth_common.Address
|
||||
data string
|
||||
callTransactionArg map[string]interface{}
|
||||
callResult *eth_hexutil.Bytes
|
||||
callErr error
|
||||
}
|
||||
|
||||
// We build two slices. The first is the batch submitted to the RPC call. It contains one entry for each query plus one to query the block.
|
||||
// The second is the data associated with each request (but not the block request). The index into both is the index into the request call data.
|
||||
batch := []rpc.BatchElem{}
|
||||
evmCallData := []EvmCallData{}
|
||||
|
||||
// Add each requested query to the batch.
|
||||
for _, callData := range req.CallData {
|
||||
// like https://github.com/ethereum/go-ethereum/blob/master/ethclient/ethclient.go#L610
|
||||
to := eth_common.BytesToAddress(callData.To)
|
||||
data := eth_hexutil.Encode(callData.Data)
|
||||
ecd := EvmCallData{
|
||||
to: to,
|
||||
data: data,
|
||||
callTransactionArg: map[string]interface{}{
|
||||
"to": to,
|
||||
"data": data,
|
||||
},
|
||||
callResult: ð_hexutil.Bytes{},
|
||||
}
|
||||
evmCallData = append(evmCallData, ecd)
|
||||
|
||||
batch = append(batch, rpc.BatchElem{
|
||||
Method: "eth_call",
|
||||
Args: []interface{}{
|
||||
ecd.callTransactionArg,
|
||||
callBlockArg,
|
||||
},
|
||||
Result: ecd.callResult,
|
||||
Error: ecd.callErr,
|
||||
})
|
||||
}
|
||||
|
||||
// Add the block query to the batch.
|
||||
var blockResult connectors.BlockMarshaller
|
||||
var blockError error
|
||||
batch = append(batch, rpc.BatchElem{
|
||||
Method: blockMethod,
|
||||
Args: []interface{}{
|
||||
block,
|
||||
false, // no full transaction details
|
||||
},
|
||||
Result: &blockResult,
|
||||
Error: blockError,
|
||||
})
|
||||
|
||||
// Query the RPC.
|
||||
err := w.ethConn.RawBatchCallContext(timeout, batch)
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
logger.Error("failed to process query request",
|
||||
zap.Error(err),
|
||||
zap.String("block", block),
|
||||
zap.Any("batch", batch),
|
||||
)
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QueryRetryNeeded, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if blockError != nil {
|
||||
logger.Error("failed to process query block request",
|
||||
zap.Error(blockError),
|
||||
zap.String("block", block),
|
||||
zap.Any("batch", batch),
|
||||
)
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QueryRetryNeeded, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if blockResult.Number == nil {
|
||||
logger.Error("invalid query block result",
|
||||
zap.String("eth_network", w.networkName),
|
||||
zap.String("block", block),
|
||||
zap.Any("batch", batch),
|
||||
)
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QueryRetryNeeded, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if blockResult.Number.ToInt().Cmp(ccqMaxBlockNumber) > 0 {
|
||||
logger.Error("block number too large",
|
||||
zap.String("eth_network", w.networkName),
|
||||
zap.String("block", block),
|
||||
zap.Any("batch", batch),
|
||||
)
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QueryRetryNeeded, nil)
|
||||
return
|
||||
}
|
||||
|
||||
resp := query.EthCallQueryResponse{
|
||||
BlockNumber: blockResult.Number.ToInt().Uint64(),
|
||||
Hash: blockResult.Hash,
|
||||
Time: time.Unix(int64(blockResult.Time), 0),
|
||||
Results: [][]byte{},
|
||||
}
|
||||
|
||||
errFound := false
|
||||
for idx := range req.CallData {
|
||||
if evmCallData[idx].callErr != nil {
|
||||
logger.Error("failed to process query call request",
|
||||
zap.Error(evmCallData[idx].callErr),
|
||||
zap.String("block", block),
|
||||
zap.Int("errorIdx", idx),
|
||||
zap.Any("batch", batch),
|
||||
)
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QueryRetryNeeded, nil)
|
||||
errFound = true
|
||||
break
|
||||
}
|
||||
|
||||
// Nil or Empty results are not valid
|
||||
// eth_call will return empty when the state doesn't exist for a block
|
||||
if len(*evmCallData[idx].callResult) == 0 {
|
||||
logger.Error("invalid call result",
|
||||
zap.String("eth_network", w.networkName),
|
||||
zap.String("block", block),
|
||||
zap.Int("errorIdx", idx),
|
||||
zap.Any("batch", batch),
|
||||
)
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QueryRetryNeeded, nil)
|
||||
errFound = true
|
||||
break
|
||||
}
|
||||
|
||||
logger.Info("query result",
|
||||
zap.String("eth_network", w.networkName),
|
||||
zap.String("block", block),
|
||||
zap.String("blockNumber", blockResult.Number.String()),
|
||||
zap.String("blockHash", blockResult.Hash.Hex()),
|
||||
zap.String("blockTime", blockResult.Time.String()),
|
||||
zap.Int("idx", idx),
|
||||
zap.String("to", evmCallData[idx].to.Hex()),
|
||||
zap.Any("data", evmCallData[idx].data),
|
||||
zap.String("result", evmCallData[idx].callResult.String()),
|
||||
)
|
||||
|
||||
resp.Results = append(resp.Results, *evmCallData[idx].callResult)
|
||||
}
|
||||
|
||||
if !errFound {
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QuerySuccess, &resp)
|
||||
}
|
||||
|
||||
default:
|
||||
logger.Warn("received unsupported request type",
|
||||
zap.Uint8("payload", uint8(queryRequest.Request.Query.Type())),
|
||||
)
|
||||
w.ccqSendQueryResponse(logger, queryRequest, query.QueryFatalError, nil)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package mock
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -42,6 +43,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
_ <-chan *query.PerChainQueryInternal,
|
||||
_ chan<- *query.PerChainQueryResponseInternal,
|
||||
setC chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package near
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -35,6 +36,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
_ <-chan *query.PerChainQueryInternal,
|
||||
_ chan<- *query.PerChainQueryResponseInternal,
|
||||
_ chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package solana
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -40,6 +41,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
_ <-chan *query.PerChainQueryInternal,
|
||||
_ chan<- *query.PerChainQueryResponseInternal,
|
||||
_ chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package sui
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
|
@ -36,6 +37,8 @@ func (wc *WatcherConfig) SetL1Finalizer(l1finalizer interfaces.L1Finalizer) {
|
|||
func (wc *WatcherConfig) Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
_ <-chan *query.PerChainQueryInternal,
|
||||
_ chan<- *query.PerChainQueryResponseInternal,
|
||||
_ chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package watchers
|
|||
import (
|
||||
"github.com/certusone/wormhole/node/pkg/common"
|
||||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
|
||||
"github.com/certusone/wormhole/node/pkg/query"
|
||||
"github.com/certusone/wormhole/node/pkg/supervisor"
|
||||
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
@ -20,6 +21,8 @@ type WatcherConfig interface {
|
|||
Create(
|
||||
msgC chan<- *common.MessagePublication,
|
||||
obsvReqC <-chan *gossipv1.ObservationRequest,
|
||||
queryReqC <-chan *query.PerChainQueryInternal,
|
||||
queryResponseC chan<- *query.PerChainQueryResponseInternal,
|
||||
setC chan<- *common.GuardianSet,
|
||||
env common.Environment,
|
||||
) (interfaces.L1Finalizer, supervisor.Runnable, error)
|
||||
|
|
|
@ -14,6 +14,8 @@ message GossipMessage {
|
|||
SignedBatchVAAWithQuorum signed_batch_vaa_with_quorum = 7;
|
||||
SignedChainGovernorConfig signed_chain_governor_config = 8;
|
||||
SignedChainGovernorStatus signed_chain_governor_status = 9;
|
||||
SignedQueryRequest signed_query_request = 10;
|
||||
SignedQueryResponse signed_query_response = 11;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,3 +233,19 @@ message ChainGovernorStatus {
|
|||
int64 timestamp = 3;
|
||||
repeated Chain chains = 4;
|
||||
}
|
||||
|
||||
message SignedQueryRequest {
|
||||
// Serialized QueryRequest message.
|
||||
bytes query_request = 1;
|
||||
|
||||
// ECDSA signature using the requestor's public key.
|
||||
bytes signature = 2;
|
||||
}
|
||||
|
||||
message SignedQueryResponse {
|
||||
// Serialized QueryResponse message.
|
||||
bytes query_response = 1;
|
||||
|
||||
// ECDSA signature using the node's guardian public key.
|
||||
bytes signature = 2;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue