diff --git a/cli/.mockery.yaml b/cli/.mockery.yaml index 77850efe9..90a488d84 100644 --- a/cli/.mockery.yaml +++ b/cli/.mockery.yaml @@ -37,14 +37,14 @@ packages: structname: "Mock{{.InterfaceName}}" interfaces: Attestor: - github.com/cosmos/ibc/cli/internal/relay/proofgen: + github.com/cosmos/ibc/cli/internal/relay/prover: config: dir: "internal/tests/mocks" - filename: "proofgen.go" + filename: "prover.go" pkgname: "mocks" structname: "Mock{{.InterfaceName}}" interfaces: - ProofGenerator: + Prover: github.com/cosmos/ibc/cli/internal/relay/txbuilder: config: dir: "internal/tests/mocks" diff --git a/cli/Makefile b/cli/Makefile index e5a55c5f9..71e005621 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -22,6 +22,10 @@ build: ## Build the binary mkdir -p bin go build -o bin/ibc ./cmd/ibc/... +build-test-bin: ## Build the binary used only by tests + mkdir -p bin + go build -o bin/testprover ./internal/testutil/proverservice/cmd/... + install: ## Install the binary go install ./cmd/ibc/... @@ -88,6 +92,8 @@ run-all-checks: ## Run "all-in-one" code validation step. @echo "==== 4. Testing ====" $(MAKE) test + +.PHONY: build-test-bin .PHONY: help build install docker-build lint lint-fix test .PHONY: codegen codegen-sql codegen-proto codegen-abi codegen-mocks .PHONY: check-stale run-all-checks diff --git a/cli/api/v2/prover/prover.connect.go b/cli/api/v2/prover/prover.connect.go new file mode 100644 index 000000000..35d6ca585 --- /dev/null +++ b/cli/api/v2/prover/prover.connect.go @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: prover.proto + +package prover + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // ProverServiceName is the fully-qualified name of the ProverService service. + ProverServiceName = "ibc.v2.prover.ProverService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // ProverServiceLatestProvableHeightProcedure is the fully-qualified name of the ProverService's + // LatestProvableHeight RPC. + ProverServiceLatestProvableHeightProcedure = "/ibc.v2.prover.ProverService/LatestProvableHeight" + // ProverServiceStateProofProcedure is the fully-qualified name of the ProverService's StateProof + // RPC. + ProverServiceStateProofProcedure = "/ibc.v2.prover.ProverService/StateProof" + // ProverServicePacketProofsProcedure is the fully-qualified name of the ProverService's + // PacketProofs RPC. + ProverServicePacketProofsProcedure = "/ibc.v2.prover.ProverService/PacketProofs" +) + +// ProverServiceClient is a client for the ibc.v2.prover.ProverService service. +type ProverServiceClient interface { + // LatestProvableHeight returns the highest height a subsequent StateProof + // and PacketProofs call sharing that height can currently succeed at, with + // that height's counterparty-chain timestamp. + LatestProvableHeight(context.Context, *connect.Request[LatestProvableHeightRequest]) (*connect.Response[LatestProvableHeightResponse], error) + // StateProof proves the light client's counterparty state at a height. + StateProof(context.Context, *connect.Request[StateProofRequest]) (*connect.Response[StateProofResponse], error) + // PacketProofs proves each packet's membership or non-membership at a + // height, one proof per packet with indices aligned to the request. + PacketProofs(context.Context, *connect.Request[PacketProofsRequest]) (*connect.Response[PacketProofsResponse], error) +} + +// NewProverServiceClient constructs a client for the ibc.v2.prover.ProverService service. By +// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, +// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the +// connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewProverServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ProverServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + proverServiceMethods := File_prover_proto.Services().ByName("ProverService").Methods() + return &proverServiceClient{ + latestProvableHeight: connect.NewClient[LatestProvableHeightRequest, LatestProvableHeightResponse]( + httpClient, + baseURL+ProverServiceLatestProvableHeightProcedure, + connect.WithSchema(proverServiceMethods.ByName("LatestProvableHeight")), + connect.WithClientOptions(opts...), + ), + stateProof: connect.NewClient[StateProofRequest, StateProofResponse]( + httpClient, + baseURL+ProverServiceStateProofProcedure, + connect.WithSchema(proverServiceMethods.ByName("StateProof")), + connect.WithClientOptions(opts...), + ), + packetProofs: connect.NewClient[PacketProofsRequest, PacketProofsResponse]( + httpClient, + baseURL+ProverServicePacketProofsProcedure, + connect.WithSchema(proverServiceMethods.ByName("PacketProofs")), + connect.WithClientOptions(opts...), + ), + } +} + +// proverServiceClient implements ProverServiceClient. +type proverServiceClient struct { + latestProvableHeight *connect.Client[LatestProvableHeightRequest, LatestProvableHeightResponse] + stateProof *connect.Client[StateProofRequest, StateProofResponse] + packetProofs *connect.Client[PacketProofsRequest, PacketProofsResponse] +} + +// LatestProvableHeight calls ibc.v2.prover.ProverService.LatestProvableHeight. +func (c *proverServiceClient) LatestProvableHeight(ctx context.Context, req *connect.Request[LatestProvableHeightRequest]) (*connect.Response[LatestProvableHeightResponse], error) { + return c.latestProvableHeight.CallUnary(ctx, req) +} + +// StateProof calls ibc.v2.prover.ProverService.StateProof. +func (c *proverServiceClient) StateProof(ctx context.Context, req *connect.Request[StateProofRequest]) (*connect.Response[StateProofResponse], error) { + return c.stateProof.CallUnary(ctx, req) +} + +// PacketProofs calls ibc.v2.prover.ProverService.PacketProofs. +func (c *proverServiceClient) PacketProofs(ctx context.Context, req *connect.Request[PacketProofsRequest]) (*connect.Response[PacketProofsResponse], error) { + return c.packetProofs.CallUnary(ctx, req) +} + +// ProverServiceHandler is an implementation of the ibc.v2.prover.ProverService service. +type ProverServiceHandler interface { + // LatestProvableHeight returns the highest height a subsequent StateProof + // and PacketProofs call sharing that height can currently succeed at, with + // that height's counterparty-chain timestamp. + LatestProvableHeight(context.Context, *connect.Request[LatestProvableHeightRequest]) (*connect.Response[LatestProvableHeightResponse], error) + // StateProof proves the light client's counterparty state at a height. + StateProof(context.Context, *connect.Request[StateProofRequest]) (*connect.Response[StateProofResponse], error) + // PacketProofs proves each packet's membership or non-membership at a + // height, one proof per packet with indices aligned to the request. + PacketProofs(context.Context, *connect.Request[PacketProofsRequest]) (*connect.Response[PacketProofsResponse], error) +} + +// NewProverServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewProverServiceHandler(svc ProverServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + proverServiceMethods := File_prover_proto.Services().ByName("ProverService").Methods() + proverServiceLatestProvableHeightHandler := connect.NewUnaryHandler( + ProverServiceLatestProvableHeightProcedure, + svc.LatestProvableHeight, + connect.WithSchema(proverServiceMethods.ByName("LatestProvableHeight")), + connect.WithHandlerOptions(opts...), + ) + proverServiceStateProofHandler := connect.NewUnaryHandler( + ProverServiceStateProofProcedure, + svc.StateProof, + connect.WithSchema(proverServiceMethods.ByName("StateProof")), + connect.WithHandlerOptions(opts...), + ) + proverServicePacketProofsHandler := connect.NewUnaryHandler( + ProverServicePacketProofsProcedure, + svc.PacketProofs, + connect.WithSchema(proverServiceMethods.ByName("PacketProofs")), + connect.WithHandlerOptions(opts...), + ) + return "/ibc.v2.prover.ProverService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case ProverServiceLatestProvableHeightProcedure: + proverServiceLatestProvableHeightHandler.ServeHTTP(w, r) + case ProverServiceStateProofProcedure: + proverServiceStateProofHandler.ServeHTTP(w, r) + case ProverServicePacketProofsProcedure: + proverServicePacketProofsHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedProverServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedProverServiceHandler struct{} + +func (UnimplementedProverServiceHandler) LatestProvableHeight(context.Context, *connect.Request[LatestProvableHeightRequest]) (*connect.Response[LatestProvableHeightResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ibc.v2.prover.ProverService.LatestProvableHeight is not implemented")) +} + +func (UnimplementedProverServiceHandler) StateProof(context.Context, *connect.Request[StateProofRequest]) (*connect.Response[StateProofResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ibc.v2.prover.ProverService.StateProof is not implemented")) +} + +func (UnimplementedProverServiceHandler) PacketProofs(context.Context, *connect.Request[PacketProofsRequest]) (*connect.Response[PacketProofsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ibc.v2.prover.ProverService.PacketProofs is not implemented")) +} diff --git a/cli/api/v2/prover/prover.pb.go b/cli/api/v2/prover/prover.pb.go new file mode 100644 index 000000000..7eb7071be --- /dev/null +++ b/cli/api/v2/prover/prover.pb.go @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: prover.proto + +package prover + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ProofKind int32 + +const ( + ProofKind_PROOF_KIND_UNSPECIFIED ProofKind = 0 + // The packet was sent. Proven to receive it. + ProofKind_PROOF_KIND_PACKET_COMMITMENT ProofKind = 1 + // The packet was received and acknowledged. Proven to acknowledge it. + ProofKind_PROOF_KIND_ACKNOWLEDGEMENT ProofKind = 2 + // The packet was never received. Proven to time it out. + ProofKind_PROOF_KIND_RECEIPT_ABSENCE ProofKind = 3 +) + +// Enum value maps for ProofKind. +var ( + ProofKind_name = map[int32]string{ + 0: "PROOF_KIND_UNSPECIFIED", + 1: "PROOF_KIND_PACKET_COMMITMENT", + 2: "PROOF_KIND_ACKNOWLEDGEMENT", + 3: "PROOF_KIND_RECEIPT_ABSENCE", + } + ProofKind_value = map[string]int32{ + "PROOF_KIND_UNSPECIFIED": 0, + "PROOF_KIND_PACKET_COMMITMENT": 1, + "PROOF_KIND_ACKNOWLEDGEMENT": 2, + "PROOF_KIND_RECEIPT_ABSENCE": 3, + } +) + +func (x ProofKind) Enum() *ProofKind { + p := new(ProofKind) + *p = x + return p +} + +func (x ProofKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProofKind) Descriptor() protoreflect.EnumDescriptor { + return file_prover_proto_enumTypes[0].Descriptor() +} + +func (ProofKind) Type() protoreflect.EnumType { + return &file_prover_proto_enumTypes[0] +} + +func (x ProofKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProofKind.Descriptor instead. +func (ProofKind) EnumDescriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{0} +} + +// Client identifies one configured light client: the chain it lives on and its +// client id. +type Client struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The chain the light client lives on. + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + // The light client's id on that chain. + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Client) Reset() { + *x = Client{} + mi := &file_prover_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Client) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Client) ProtoMessage() {} + +func (x *Client) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Client.ProtoReflect.Descriptor instead. +func (*Client) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{0} +} + +func (x *Client) GetChainId() string { + if x != nil { + return x.ChainId + } + return "" +} + +func (x *Client) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +type LatestProvableHeightRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The light client this call is scoped to. + Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LatestProvableHeightRequest) Reset() { + *x = LatestProvableHeightRequest{} + mi := &file_prover_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LatestProvableHeightRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LatestProvableHeightRequest) ProtoMessage() {} + +func (x *LatestProvableHeightRequest) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LatestProvableHeightRequest.ProtoReflect.Descriptor instead. +func (*LatestProvableHeightRequest) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{1} +} + +func (x *LatestProvableHeightRequest) GetClient() *Client { + if x != nil { + return x.Client + } + return nil +} + +type LatestProvableHeightResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The highest counterparty height currently provable. + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // counterparty-chain timestamp of height, in seconds. + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LatestProvableHeightResponse) Reset() { + *x = LatestProvableHeightResponse{} + mi := &file_prover_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LatestProvableHeightResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LatestProvableHeightResponse) ProtoMessage() {} + +func (x *LatestProvableHeightResponse) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LatestProvableHeightResponse.ProtoReflect.Descriptor instead. +func (*LatestProvableHeightResponse) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{2} +} + +func (x *LatestProvableHeightResponse) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *LatestProvableHeightResponse) GetTimestamp() uint64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type StateProofRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The light client this call is scoped to. + Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` + // The counterparty height to prove at. + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StateProofRequest) Reset() { + *x = StateProofRequest{} + mi := &file_prover_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StateProofRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateProofRequest) ProtoMessage() {} + +func (x *StateProofRequest) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateProofRequest.ProtoReflect.Descriptor instead. +func (*StateProofRequest) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{3} +} + +func (x *StateProofRequest) GetClient() *Client { + if x != nil { + return x.Client + } + return nil +} + +func (x *StateProofRequest) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +type StateProofResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The proof, opaque to the relayer and passed to the light client unchanged. + Proof []byte `protobuf:"bytes,1,opt,name=proof,proto3" json:"proof,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StateProofResponse) Reset() { + *x = StateProofResponse{} + mi := &file_prover_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StateProofResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateProofResponse) ProtoMessage() {} + +func (x *StateProofResponse) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateProofResponse.ProtoReflect.Descriptor instead. +func (*StateProofResponse) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{4} +} + +func (x *StateProofResponse) GetProof() []byte { + if x != nil { + return x.Proof + } + return nil +} + +type PacketProofsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The light client this call is scoped to. + Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` + // The counterparty height to prove at. + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + // Which commitment to prove for every packet in this request. + Kind ProofKind `protobuf:"varint,3,opt,name=kind,proto3,enum=ibc.v2.prover.ProofKind" json:"kind,omitempty"` + // The packets to prove, all under the same kind and height. + Packets []*Packet `protobuf:"bytes,4,rep,name=packets,proto3" json:"packets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PacketProofsRequest) Reset() { + *x = PacketProofsRequest{} + mi := &file_prover_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PacketProofsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PacketProofsRequest) ProtoMessage() {} + +func (x *PacketProofsRequest) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PacketProofsRequest.ProtoReflect.Descriptor instead. +func (*PacketProofsRequest) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{5} +} + +func (x *PacketProofsRequest) GetClient() *Client { + if x != nil { + return x.Client + } + return nil +} + +func (x *PacketProofsRequest) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *PacketProofsRequest) GetKind() ProofKind { + if x != nil { + return x.Kind + } + return ProofKind_PROOF_KIND_UNSPECIFIED +} + +func (x *PacketProofsRequest) GetPackets() []*Packet { + if x != nil { + return x.Packets + } + return nil +} + +type PacketProofsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // One proof per requested packet, in request order. + Proofs [][]byte `protobuf:"bytes,1,rep,name=proofs,proto3" json:"proofs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PacketProofsResponse) Reset() { + *x = PacketProofsResponse{} + mi := &file_prover_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PacketProofsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PacketProofsResponse) ProtoMessage() {} + +func (x *PacketProofsResponse) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PacketProofsResponse.ProtoReflect.Descriptor instead. +func (*PacketProofsResponse) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{6} +} + +func (x *PacketProofsResponse) GetProofs() [][]byte { + if x != nil { + return x.Proofs + } + return nil +} + +type Packet struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The packet's sequence on the source client. + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // The client the packet was sent from. + SourceClient string `protobuf:"bytes,2,opt,name=source_client,json=sourceClient,proto3" json:"source_client,omitempty"` + // The client the packet was sent to. + DestinationClient string `protobuf:"bytes,3,opt,name=destination_client,json=destinationClient,proto3" json:"destination_client,omitempty"` + // When the packet stops being receivable, in seconds. + TimeoutTimestamp uint64 `protobuf:"varint,4,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` + // The packet's application payloads. + Payloads []*Payload `protobuf:"bytes,5,rep,name=payloads,proto3" json:"payloads,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Packet) Reset() { + *x = Packet{} + mi := &file_prover_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Packet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Packet) ProtoMessage() {} + +func (x *Packet) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Packet.ProtoReflect.Descriptor instead. +func (*Packet) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{7} +} + +func (x *Packet) GetSequence() uint64 { + if x != nil { + return x.Sequence + } + return 0 +} + +func (x *Packet) GetSourceClient() string { + if x != nil { + return x.SourceClient + } + return "" +} + +func (x *Packet) GetDestinationClient() string { + if x != nil { + return x.DestinationClient + } + return "" +} + +func (x *Packet) GetTimeoutTimestamp() uint64 { + if x != nil { + return x.TimeoutTimestamp + } + return 0 +} + +func (x *Packet) GetPayloads() []*Payload { + if x != nil { + return x.Payloads + } + return nil +} + +type Payload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The application that sent the payload. + SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // The application that receives the payload. + DestinationPort string `protobuf:"bytes,2,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // The application version the payload is encoded for. + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + // How value is encoded. + Encoding string `protobuf:"bytes,4,opt,name=encoding,proto3" json:"encoding,omitempty"` + // The application data. + Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Payload) Reset() { + *x = Payload{} + mi := &file_prover_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Payload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Payload) ProtoMessage() {} + +func (x *Payload) ProtoReflect() protoreflect.Message { + mi := &file_prover_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Payload.ProtoReflect.Descriptor instead. +func (*Payload) Descriptor() ([]byte, []int) { + return file_prover_proto_rawDescGZIP(), []int{8} +} + +func (x *Payload) GetSourcePort() string { + if x != nil { + return x.SourcePort + } + return "" +} + +func (x *Payload) GetDestinationPort() string { + if x != nil { + return x.DestinationPort + } + return "" +} + +func (x *Payload) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *Payload) GetEncoding() string { + if x != nil { + return x.Encoding + } + return "" +} + +func (x *Payload) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +var File_prover_proto protoreflect.FileDescriptor + +const file_prover_proto_rawDesc = "" + + "\n" + + "\fprover.proto\x12\ribc.v2.prover\"@\n" + + "\x06Client\x12\x19\n" + + "\bchain_id\x18\x01 \x01(\tR\achainId\x12\x1b\n" + + "\tclient_id\x18\x02 \x01(\tR\bclientId\"L\n" + + "\x1bLatestProvableHeightRequest\x12-\n" + + "\x06client\x18\x01 \x01(\v2\x15.ibc.v2.prover.ClientR\x06client\"T\n" + + "\x1cLatestProvableHeightResponse\x12\x16\n" + + "\x06height\x18\x01 \x01(\x04R\x06height\x12\x1c\n" + + "\ttimestamp\x18\x02 \x01(\x04R\ttimestamp\"Z\n" + + "\x11StateProofRequest\x12-\n" + + "\x06client\x18\x01 \x01(\v2\x15.ibc.v2.prover.ClientR\x06client\x12\x16\n" + + "\x06height\x18\x02 \x01(\x04R\x06height\"*\n" + + "\x12StateProofResponse\x12\x14\n" + + "\x05proof\x18\x01 \x01(\fR\x05proof\"\xbb\x01\n" + + "\x13PacketProofsRequest\x12-\n" + + "\x06client\x18\x01 \x01(\v2\x15.ibc.v2.prover.ClientR\x06client\x12\x16\n" + + "\x06height\x18\x02 \x01(\x04R\x06height\x12,\n" + + "\x04kind\x18\x03 \x01(\x0e2\x18.ibc.v2.prover.ProofKindR\x04kind\x12/\n" + + "\apackets\x18\x04 \x03(\v2\x15.ibc.v2.prover.PacketR\apackets\".\n" + + "\x14PacketProofsResponse\x12\x16\n" + + "\x06proofs\x18\x01 \x03(\fR\x06proofs\"\xd9\x01\n" + + "\x06Packet\x12\x1a\n" + + "\bsequence\x18\x01 \x01(\x04R\bsequence\x12#\n" + + "\rsource_client\x18\x02 \x01(\tR\fsourceClient\x12-\n" + + "\x12destination_client\x18\x03 \x01(\tR\x11destinationClient\x12+\n" + + "\x11timeout_timestamp\x18\x04 \x01(\x04R\x10timeoutTimestamp\x122\n" + + "\bpayloads\x18\x05 \x03(\v2\x16.ibc.v2.prover.PayloadR\bpayloads\"\xa1\x01\n" + + "\aPayload\x12\x1f\n" + + "\vsource_port\x18\x01 \x01(\tR\n" + + "sourcePort\x12)\n" + + "\x10destination_port\x18\x02 \x01(\tR\x0fdestinationPort\x12\x18\n" + + "\aversion\x18\x03 \x01(\tR\aversion\x12\x1a\n" + + "\bencoding\x18\x04 \x01(\tR\bencoding\x12\x14\n" + + "\x05value\x18\x05 \x01(\fR\x05value*\x89\x01\n" + + "\tProofKind\x12\x1a\n" + + "\x16PROOF_KIND_UNSPECIFIED\x10\x00\x12 \n" + + "\x1cPROOF_KIND_PACKET_COMMITMENT\x10\x01\x12\x1e\n" + + "\x1aPROOF_KIND_ACKNOWLEDGEMENT\x10\x02\x12\x1e\n" + + "\x1aPROOF_KIND_RECEIPT_ABSENCE\x10\x032\xb2\x02\n" + + "\rProverService\x12q\n" + + "\x14LatestProvableHeight\x12*.ibc.v2.prover.LatestProvableHeightRequest\x1a+.ibc.v2.prover.LatestProvableHeightResponse\"\x00\x12S\n" + + "\n" + + "StateProof\x12 .ibc.v2.prover.StateProofRequest\x1a!.ibc.v2.prover.StateProofResponse\"\x00\x12Y\n" + + "\fPacketProofs\x12\".ibc.v2.prover.PacketProofsRequest\x1a#.ibc.v2.prover.PacketProofsResponse\"\x00B)Z'github.com/cosmos/ibc/cli/api/v2/proverb\x06proto3" + +var ( + file_prover_proto_rawDescOnce sync.Once + file_prover_proto_rawDescData []byte +) + +func file_prover_proto_rawDescGZIP() []byte { + file_prover_proto_rawDescOnce.Do(func() { + file_prover_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_prover_proto_rawDesc), len(file_prover_proto_rawDesc))) + }) + return file_prover_proto_rawDescData +} + +var file_prover_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_prover_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_prover_proto_goTypes = []any{ + (ProofKind)(0), // 0: ibc.v2.prover.ProofKind + (*Client)(nil), // 1: ibc.v2.prover.Client + (*LatestProvableHeightRequest)(nil), // 2: ibc.v2.prover.LatestProvableHeightRequest + (*LatestProvableHeightResponse)(nil), // 3: ibc.v2.prover.LatestProvableHeightResponse + (*StateProofRequest)(nil), // 4: ibc.v2.prover.StateProofRequest + (*StateProofResponse)(nil), // 5: ibc.v2.prover.StateProofResponse + (*PacketProofsRequest)(nil), // 6: ibc.v2.prover.PacketProofsRequest + (*PacketProofsResponse)(nil), // 7: ibc.v2.prover.PacketProofsResponse + (*Packet)(nil), // 8: ibc.v2.prover.Packet + (*Payload)(nil), // 9: ibc.v2.prover.Payload +} +var file_prover_proto_depIdxs = []int32{ + 1, // 0: ibc.v2.prover.LatestProvableHeightRequest.client:type_name -> ibc.v2.prover.Client + 1, // 1: ibc.v2.prover.StateProofRequest.client:type_name -> ibc.v2.prover.Client + 1, // 2: ibc.v2.prover.PacketProofsRequest.client:type_name -> ibc.v2.prover.Client + 0, // 3: ibc.v2.prover.PacketProofsRequest.kind:type_name -> ibc.v2.prover.ProofKind + 8, // 4: ibc.v2.prover.PacketProofsRequest.packets:type_name -> ibc.v2.prover.Packet + 9, // 5: ibc.v2.prover.Packet.payloads:type_name -> ibc.v2.prover.Payload + 2, // 6: ibc.v2.prover.ProverService.LatestProvableHeight:input_type -> ibc.v2.prover.LatestProvableHeightRequest + 4, // 7: ibc.v2.prover.ProverService.StateProof:input_type -> ibc.v2.prover.StateProofRequest + 6, // 8: ibc.v2.prover.ProverService.PacketProofs:input_type -> ibc.v2.prover.PacketProofsRequest + 3, // 9: ibc.v2.prover.ProverService.LatestProvableHeight:output_type -> ibc.v2.prover.LatestProvableHeightResponse + 5, // 10: ibc.v2.prover.ProverService.StateProof:output_type -> ibc.v2.prover.StateProofResponse + 7, // 11: ibc.v2.prover.ProverService.PacketProofs:output_type -> ibc.v2.prover.PacketProofsResponse + 9, // [9:12] is the sub-list for method output_type + 6, // [6:9] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_prover_proto_init() } +func file_prover_proto_init() { + if File_prover_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_prover_proto_rawDesc), len(file_prover_proto_rawDesc)), + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_prover_proto_goTypes, + DependencyIndexes: file_prover_proto_depIdxs, + EnumInfos: file_prover_proto_enumTypes, + MessageInfos: file_prover_proto_msgTypes, + }.Build() + File_prover_proto = out.File + file_prover_proto_goTypes = nil + file_prover_proto_depIdxs = nil +} diff --git a/cli/internal/bootstrap/bootstrap.go b/cli/internal/bootstrap/bootstrap.go index 1d7dbbb40..4dbeb9649 100644 --- a/cli/internal/bootstrap/bootstrap.go +++ b/cli/internal/bootstrap/bootstrap.go @@ -10,7 +10,7 @@ import ( "github.com/cosmos/ibc/cli/internal/config" "github.com/cosmos/ibc/cli/internal/relay/dispatch" "github.com/cosmos/ibc/cli/internal/relay/pipeline" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/relay/watcher" "github.com/cosmos/ibc/cli/internal/server" @@ -79,8 +79,8 @@ func BuildRelayer(cfg config.Config) (*Services, error) { } } - // Proof generators - proofGenerators, err := proofgen.NewSetFromConfig(ctx, cfg, clientSet, append(local, remote...)) + // Provers + provers, err := prover.NewSetFromConfig(ctx, cfg, clientSet, append(local, remote...)) if err != nil { return nil, err } @@ -99,11 +99,11 @@ func BuildRelayer(cfg config.Config) (*Services, error) { // Relaying dispatcher pipelines := dispatch.NewPipelineSet(logger, cfg, pipeline.Deps{ - Storage: db, - Chains: clientSet, - ProofGenerators: proofGenerators, - TxBuilders: txBuilders, - TxSubmitters: txSubmitters, + Storage: db, + Chains: clientSet, + Provers: provers, + TxBuilders: txBuilders, + TxSubmitters: txSubmitters, }) pollInterval := dispatch.DefaultPollInterval if cfg.Relayer.DispatchPollInterval != nil { diff --git a/cli/internal/config/ibc.yml b/cli/internal/config/ibc.yml index 2321baab9..d97ce72f4 100644 --- a/cli/internal/config/ibc.yml +++ b/cli/internal/config/ibc.yml @@ -17,10 +17,13 @@ chains: - chainId: "1" evm: rpc: https://ethereum-rpc.example.com + # ws is required by any client end that enables autoRelay + ws: wss://ethereum-rpc.example.com ics26Router: "0x0000000000000000000000000000000000000000" - chainId: "8453" evm: rpc: https://base-rpc.example.com + ws: wss://base-rpc.example.com ics26Router: "0x0000000000000000000000000000000000000000" relayer: @@ -51,6 +54,24 @@ relayer: enabled: true lookback: 100 + # a client end of type: remote delegates proof generation to a + # ProverService over gRPC instead of building proofs from attestations, so + # a light client IBC CLI does not implement can be relayed by serving that + # API + - alias: "eth-custom" + clientA: + chainId: "1" + signer: "eth-relayer-key" + clientId: "custom-0" + type: "remote" + params: + url: prover.example.com:9090 + clientB: + chainId: "8453" + signer: "base-relayer-key" + clientId: "ethereum-1" + type: "attestation" + # attestors -- both the ones this process runs itself (type: local) # and the ones it queries over gRPC (type: remote). attestors: diff --git a/cli/internal/config/relayer.go b/cli/internal/config/relayer.go index 58d6d6ca1..7deafe001 100644 --- a/cli/internal/config/relayer.go +++ b/cli/internal/config/relayer.go @@ -5,6 +5,7 @@ package config import ( "time" + "github.com/goccy/go-yaml" "github.com/pkg/errors" ) @@ -14,6 +15,8 @@ type ClientType string // Client types const ( ClientTypeAttestation ClientType = "attestation" + // ClientTypeRemote delegates proof generation to a remote service + ClientTypeRemote ClientType = "remote" ) // AttestorType how an attestor is reached. @@ -64,11 +67,70 @@ type ClientEnd struct { ClientID string `yaml:"clientId"` Type ClientType `yaml:"type"` + // Params is this client type's settings. + Params yaml.RawMessage `yaml:"params,omitempty"` + // AutoRelay configures auto-relay for packets flowing FROM this end's // chain TOWARD the counterparty end. AutoRelay AutoRelayConfig `yaml:"autoRelay,omitempty"` } +// ClientParams is a client type's decoded params +type ClientParams interface { + isClientParams() + Validate() error +} + +// AttestationParams is empty +type AttestationParams struct{} + +func (*AttestationParams) isClientParams() {} + +func (*AttestationParams) Validate() error { return nil } + +// RemoteParams is the params block a remote client declares. +type RemoteParams struct { + // URL is the ProverService endpoint. + URL string `yaml:"url"` +} + +func (*RemoteParams) isClientParams() {} + +func (p *RemoteParams) Validate() error { + if p.URL == "" { + return errors.New(".params.url required") + } + + return nil +} + +// ClientParams decodes this client's params +func (c ClientEnd) ClientParams() (ClientParams, error) { + switch c.Type { + case ClientTypeAttestation: + return decode[AttestationParams](c.Params) + case ClientTypeRemote: + return decode[RemoteParams](c.Params) + default: + return nil, errors.Errorf(".type unknown client type: %q", c.Type) + } +} + +// decode reads a params block into T +func decode[T any](raw yaml.RawMessage) (*T, error) { + var params T + + if len(raw) == 0 { + return ¶ms, nil + } + + if err := yaml.UnmarshalWithOptions(raw, ¶ms, yaml.DisallowUnknownField()); err != nil { + return nil, errors.Wrap(err, "decoding params") + } + + return ¶ms, nil +} + // AutoRelayConfig automatic relaying settings. type AutoRelayConfig struct { Enabled *bool `yaml:"enabled,omitempty"` @@ -217,11 +279,16 @@ func (c ClientEnd) Validate() error { return errors.New(".clientId required") case c.Signer == "": return errors.New(".signer required") - case c.Type != ClientTypeAttestation: + case c.Type != ClientTypeAttestation && c.Type != ClientTypeRemote: return errors.Errorf(".type unknown client type: %q", c.Type) } - return nil + params, err := c.ClientParams() + if err != nil { + return err + } + + return params.Validate() } func (c RelayerChainOverride) Validate() error { diff --git a/cli/internal/config/relayer_test.go b/cli/internal/config/relayer_test.go index c0a65ef74..e88c70450 100644 --- a/cli/internal/config/relayer_test.go +++ b/cli/internal/config/relayer_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/goccy/go-yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -380,3 +381,50 @@ func TestConnectionConfigSourceEnd(t *testing.T) { }) } } + +// Params are checked by Validate, not by parsing, so commands that load a +// config without validating it still work. +func TestClientEndParams(t *testing.T) { + t.Parallel() + + const base = "chainId: \"1\"\nsigner: relayer\nclientId: c-0\n" + + for _, tt := range []struct { + name string + doc string + wantURL string + wantErr string + }{ + {name: "remote", doc: "type: remote\nparams:\n url: http://prover:9090\n", wantURL: "http://prover:9090"}, + {name: "remote without params", doc: "type: remote\n", wantErr: ".params.url required"}, + {name: "remote with empty url", doc: "type: remote\nparams:\n url: \"\"\n", wantErr: ".params.url required"}, + {name: "remote with misspelled key", doc: "type: remote\nparams:\n endpoint: http://prover:9090\n", wantErr: "unknown field"}, + {name: "attestation", doc: "type: attestation\n"}, + {name: "attestation takes no params", doc: "type: attestation\nparams:\n url: http://prover:9090\n", wantErr: "unknown field"}, + {name: "unknown type", doc: "type: someFutureClient\n", wantErr: "someFutureClient"}, + } { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var client ClientEnd + require.NoError(t, yaml.Unmarshal([]byte(base+tt.doc), &client)) + + if tt.wantErr != "" { + require.ErrorContains(t, client.Validate(), tt.wantErr) + return + } + + require.NoError(t, client.Validate()) + + params, err := client.ClientParams() + require.NoError(t, err) + + if tt.wantURL == "" { + require.IsType(t, &AttestationParams{}, params) + return + } + + require.Equal(t, tt.wantURL, params.(*RemoteParams).URL) + }) + } +} diff --git a/cli/internal/livevalidate/quorum.go b/cli/internal/livevalidate/quorum.go index 33e7515f5..2eb97c45c 100644 --- a/cli/internal/livevalidate/quorum.go +++ b/cli/internal/livevalidate/quorum.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/ibc/cli/internal/chains" "github.com/cosmos/ibc/cli/internal/config" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/service/attestor" "github.com/cosmos/ibc/cli/internal/service/signer" ) @@ -33,7 +33,7 @@ func checkAttestorQuorum(ctx context.Context, cfg config.Config, clientSet *chai attestors = append(attestors, local...) attestors = append(attestors, remote...) - if _, err := proofgen.NewSetFromConfig(ctx, cfg, clientSet, attestors); err != nil { + if _, err := prover.NewSetFromConfig(ctx, cfg, clientSet, attestors); err != nil { return errors.Wrap(err, "attestor quorum") } diff --git a/cli/internal/relay/dispatch/fixtures_test.go b/cli/internal/relay/dispatch/fixtures_test.go index dcc340f39..f917bfdf2 100644 --- a/cli/internal/relay/dispatch/fixtures_test.go +++ b/cli/internal/relay/dispatch/fixtures_test.go @@ -13,7 +13,7 @@ import ( "github.com/cosmos/ibc/cli/internal/chains" "github.com/cosmos/ibc/cli/internal/relay/pipeline" "github.com/cosmos/ibc/cli/internal/relay/processors" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/tests/mocks" @@ -42,10 +42,10 @@ func (s staticChains) Get(chainID string) (chains.Client, bool) { return client, ok } -type staticProofGenerators map[string]proofgen.ProofGenerator +type staticProvers map[string]prover.Prover -func (s staticProofGenerators) Get(chainID, clientID string) (proofgen.ProofGenerator, bool) { - gen, ok := s[proofgen.Key(chainID, clientID)] +func (s staticProvers) Get(chainID, clientID string) (prover.Prover, bool) { + gen, ok := s[prover.Key(chainID, clientID)] return gen, ok } @@ -70,8 +70,8 @@ func newPipelineEnv(t *testing.T) (*pipelineEnv, pipeline.Deps) { _, err = db.MigrateUp() require.NoError(t, err) - destProofGen := mocks.NewMockProofGenerator(t) - sourceProofGen := mocks.NewMockProofGenerator(t) + destProver := mocks.NewMockProver(t) + sourceProver := mocks.NewMockProver(t) deps := pipeline.Deps{ Storage: db, @@ -79,9 +79,9 @@ func newPipelineEnv(t *testing.T) (*pipelineEnv, pipeline.Deps) { testRoute.SourceChainID: mocks.NewMockClient(t), testRoute.DestinationChainID: mocks.NewMockClient(t), }, - ProofGenerators: staticProofGenerators{ - proofgen.Key(testRoute.DestinationChainID, testRoute.DestinationClientID): destProofGen, - proofgen.Key(testRoute.SourceChainID, testRoute.SourceClientID): sourceProofGen, + Provers: staticProvers{ + prover.Key(testRoute.DestinationChainID, testRoute.DestinationClientID): destProver, + prover.Key(testRoute.SourceChainID, testRoute.SourceClientID): sourceProver, }, TxBuilders: staticTxBuilders{ testRoute.SourceChainID: mocks.NewMockTxBuilder(t), diff --git a/cli/internal/relay/pipeline/pipeline.go b/cli/internal/relay/pipeline/pipeline.go index 2372dc78e..f142a4519 100644 --- a/cli/internal/relay/pipeline/pipeline.go +++ b/cli/internal/relay/pipeline/pipeline.go @@ -41,11 +41,11 @@ type TxSubmitters interface { // Deps the external systems a pipeline relays through. type Deps struct { - Storage Storage - Chains processors.ChainClients - ProofGenerators processors.ProofGenerators - TxBuilders processors.TxBuilders - TxSubmitters TxSubmitters + Storage Storage + Chains processors.ChainClients + Provers processors.Provers + TxBuilders processors.TxBuilders + TxSubmitters TxSubmitters } // Pipeline relays transfers pushed to its input through the full packet @@ -115,7 +115,7 @@ func NewPipeline( // wait for the send tx's packet event to be at or before what the // destination client can currently prove - checkSendFinality, err := processors.NewCheckSendFinality(deps.Chains, deps.ProofGenerators, route) + checkSendFinality, err := processors.NewCheckSendFinality(deps.Chains, deps.Provers, route) if err != nil { return nil, errors.Wrap(err, "constructing check send finality processor") } @@ -125,7 +125,7 @@ func NewPipeline( // before timing out, wait for the source client to be able to prove a // destination-chain timestamp past the timeout - checkTimeoutFinality, err := processors.NewCheckTimeoutFinality(deps.ProofGenerators, route) + checkTimeoutFinality, err := processors.NewCheckTimeoutFinality(deps.Provers, route) if err != nil { return nil, errors.Wrap(err, "constructing check timeout finality processor") } @@ -135,7 +135,7 @@ func NewPipeline( // deliver timeouts in batches on the source chain batchTimeoutPacket, err := processors.NewBatchTimeoutPacket( - deps.Chains, deps.ProofGenerators, deps.TxBuilders, deps.Storage, srcTxSubmitter, route, + deps.Chains, deps.Provers, deps.TxBuilders, deps.Storage, srcTxSubmitter, route, ) if err != nil { return nil, errors.Wrap(err, "constructing batch timeout packet processor") @@ -157,7 +157,7 @@ func NewPipeline( // deliver recvs in batches on the destination chain batchRecvPacket, err := processors.NewBatchRecvPacket( - deps.Chains, deps.ProofGenerators, deps.TxBuilders, deps.Storage, dstTxSubmitter, route, + deps.Chains, deps.Provers, deps.TxBuilders, deps.Storage, dstTxSubmitter, route, ) if err != nil { return nil, errors.Wrap(err, "constructing batch recv packet processor") @@ -183,7 +183,7 @@ func NewPipeline( // wait for the write ack tx's packet event to be at or before what the // source client can currently prove - checkWriteAckFinality, err := processors.NewCheckWriteAckFinality(deps.Chains, deps.ProofGenerators, route) + checkWriteAckFinality, err := processors.NewCheckWriteAckFinality(deps.Chains, deps.Provers, route) if err != nil { return nil, errors.Wrap(err, "constructing check write ack finality processor") } @@ -197,7 +197,7 @@ func NewPipeline( // deliver acks in batches on the source chain batchAckPacket, err := processors.NewBatchAckPacket( - deps.Chains, deps.ProofGenerators, deps.TxBuilders, deps.Storage, srcTxSubmitter, route, + deps.Chains, deps.Provers, deps.TxBuilders, deps.Storage, srcTxSubmitter, route, ) if err != nil { return nil, errors.Wrap(err, "constructing batch ack packet processor") diff --git a/cli/internal/relay/pipeline/pipeline_test.go b/cli/internal/relay/pipeline/pipeline_test.go index 93c980006..aea8ad2ef 100644 --- a/cli/internal/relay/pipeline/pipeline_test.go +++ b/cli/internal/relay/pipeline/pipeline_test.go @@ -16,7 +16,7 @@ import ( channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" "github.com/cosmos/ibc/cli/internal/chains" "github.com/cosmos/ibc/cli/internal/relay/processors" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/tests/mocks" @@ -41,11 +41,11 @@ type pipelineEnv struct { store *store.SqliteDB srcClient *mocks.MockClient dstClient *mocks.MockClient - // dstProofGen/dstTxBuilder are resolved for the destination client/chain, - // used by recv delivery; srcProofGen/srcTxBuilder are resolved for the + // dstProver/dstTxBuilder are resolved for the destination client/chain, + // used by recv delivery; srcProver/srcTxBuilder are resolved for the // source client/chain, used by ack and timeout delivery. - dstProofGen *mocks.MockProofGenerator - srcProofGen *mocks.MockProofGenerator + dstProver *mocks.MockProver + srcProver *mocks.MockProver dstTxBuilder *mocks.MockTxBuilder srcTxBuilder *mocks.MockTxBuilder srcTxSubmitter *mocks.MockTxSubmitter @@ -59,10 +59,10 @@ func (s staticChains) Get(chainID string) (chains.Client, bool) { return client, ok } -type staticProofGenerators map[string]proofgen.ProofGenerator +type staticProvers map[string]prover.Prover -func (s staticProofGenerators) Get(chainID, clientID string) (proofgen.ProofGenerator, bool) { - gen, ok := s[proofgen.Key(chainID, clientID)] +func (s staticProvers) Get(chainID, clientID string) (prover.Prover, bool) { + gen, ok := s[prover.Key(chainID, clientID)] return gen, ok } @@ -87,8 +87,8 @@ func newPipelineEnv(t *testing.T) (*pipelineEnv, Deps) { store: db, srcClient: mocks.NewMockClient(t), dstClient: mocks.NewMockClient(t), - dstProofGen: mocks.NewMockProofGenerator(t), - srcProofGen: mocks.NewMockProofGenerator(t), + dstProver: mocks.NewMockProver(t), + srcProver: mocks.NewMockProver(t), dstTxBuilder: mocks.NewMockTxBuilder(t), srcTxBuilder: mocks.NewMockTxBuilder(t), srcTxSubmitter: mocks.NewMockTxSubmitter(t), @@ -98,9 +98,9 @@ func newPipelineEnv(t *testing.T) (*pipelineEnv, Deps) { deps := Deps{ Storage: db, Chains: staticChains{testRoute.SourceChainID: env.srcClient, testRoute.DestinationChainID: env.dstClient}, - ProofGenerators: staticProofGenerators{ - proofgen.Key(testRoute.DestinationChainID, testRoute.DestinationClientID): env.dstProofGen, - proofgen.Key(testRoute.SourceChainID, testRoute.SourceClientID): env.srcProofGen, + Provers: staticProvers{ + prover.Key(testRoute.DestinationChainID, testRoute.DestinationClientID): env.dstProver, + prover.Key(testRoute.SourceChainID, testRoute.SourceClientID): env.srcProver, }, TxBuilders: staticTxBuilders{ testRoute.DestinationChainID: env.dstTxBuilder, @@ -121,7 +121,7 @@ func newPipelineEnv(t *testing.T) (*pipelineEnv, Deps) { // for whichever chain the batch reads packet events from. func mockRelay( client *mocks.MockClient, - proofGen *mocks.MockProofGenerator, + mockProver *mocks.MockProver, txBuilder *mocks.MockTxBuilder, events []v2.PacketEvent, to string, @@ -129,9 +129,9 @@ func mockRelay( const height = uint64(100) client.EXPECT().TxPacketEvents(mock.Anything, mock.Anything).Return(events, nil).Once() - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(height, time.Now(), nil).Once() - proofGen.EXPECT().StateProof(mock.Anything, height).Return([]byte{0x01}, nil).Once() - proofGen.EXPECT().PacketProofs(mock.Anything, height, mock.Anything, mock.Anything). + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(height, time.Now(), nil).Once() + mockProver.EXPECT().StateProof(mock.Anything, height).Return([]byte{0x01}, nil).Once() + mockProver.EXPECT().PacketProofs(mock.Anything, height, mock.Anything, mock.Anything). Return(make([][]byte, len(events)), nil).Once() txBuilder.EXPECT().BuildRelayTxs(mock.Anything, mock.Anything). Return([]v2.RelayTx{{To: common.HexToAddress(to).Bytes(), Data: []byte{0xca, 0x11}}}, nil).Once() @@ -255,12 +255,12 @@ func TestPipelineLifecycle(t *testing.T) { Return(true, nil). Times(2) - // check send finality: destination client's proof generator must be able to prove the send tx - env.dstProofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() + // check send finality: destination client's prover must be able to prove the send tx + env.dstProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() env.srcClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() // recv delivery - mockRelay(env.srcClient, env.dstProofGen, env.dstTxBuilder, []v2.PacketEvent{sendPacketEvent(42)}, "0xrouter") + mockRelay(env.srcClient, env.dstProver, env.dstTxBuilder, []v2.PacketEvent{sendPacketEvent(42)}, "0xrouter") env.dstClient.EXPECT().WaitForChain(mock.Anything).Return(nil).Once() env.dstTxSubmitter.EXPECT().Submit(mock.Anything, mock.Anything).Return(&v2.Submission{ TxHash: recvTxHash, @@ -275,12 +275,12 @@ func TestPipelineLifecycle(t *testing.T) { Return(chainsWriteAckSuccess(), nil). Once() - // check write ack finality: source client's proof generator must be able to prove the write ack tx - env.srcProofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() + // check write ack finality: source client's prover must be able to prove the write ack tx + env.srcProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() env.dstClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() // ack delivery on the source chain - mockRelay(env.dstClient, env.srcProofGen, env.srcTxBuilder, []v2.PacketEvent{writeAckEvent(42)}, "0xrouter") + mockRelay(env.dstClient, env.srcProver, env.srcTxBuilder, []v2.PacketEvent{writeAckEvent(42)}, "0xrouter") env.srcClient.EXPECT().WaitForChain(mock.Anything).Return(nil).Once() env.srcTxSubmitter.EXPECT().Submit(mock.Anything, mock.Anything).Return(&v2.Submission{ TxHash: ackTxHash, SubmittedAt: time.Now().UTC(), RelayerAddress: "0xrelayer", @@ -314,12 +314,12 @@ func TestPipelineLifecycle(t *testing.T) { Return(true, nil). Times(2) - // check send finality: destination client's proof generator must be able to prove the send tx - env.dstProofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() + // check send finality: destination client's prover must be able to prove the send tx + env.dstProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() env.srcClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() // recv delivery - mockRelay(env.srcClient, env.dstProofGen, env.dstTxBuilder, []v2.PacketEvent{sendPacketEvent(42)}, "0xrouter") + mockRelay(env.srcClient, env.dstProver, env.dstTxBuilder, []v2.PacketEvent{sendPacketEvent(42)}, "0xrouter") env.dstClient.EXPECT().WaitForChain(mock.Anything).Return(nil).Once() env.dstTxSubmitter.EXPECT().Submit(mock.Anything, mock.Anything).Return(&v2.Submission{ TxHash: recvTxHash, SubmittedAt: time.Now().UTC(), RelayerAddress: "0xrelayer", @@ -332,12 +332,12 @@ func TestPipelineLifecycle(t *testing.T) { Return(chainsWriteAckError(), nil). Once() - // check write ack finality: source client's proof generator must be able to prove the write ack tx - env.srcProofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() + // check write ack finality: source client's prover must be able to prove the write ack tx + env.srcProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() env.dstClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() // ack delivery on the source chain - mockRelay(env.dstClient, env.srcProofGen, env.srcTxBuilder, []v2.PacketEvent{writeAckEvent(42)}, "0xrouter") + mockRelay(env.dstClient, env.srcProver, env.srcTxBuilder, []v2.PacketEvent{writeAckEvent(42)}, "0xrouter") env.srcClient.EXPECT().WaitForChain(mock.Anything).Return(nil).Once() env.srcTxSubmitter.EXPECT().Submit(mock.Anything, mock.Anything).Return(&v2.Submission{ TxHash: ackTxHash, SubmittedAt: time.Now().UTC(), RelayerAddress: "0xrelayer", @@ -368,17 +368,17 @@ func TestPipelineLifecycle(t *testing.T) { Return(true, nil). Once() - // check send finality: destination client's proof generator must be able to prove the send tx - env.dstProofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() + // check send finality: destination client's prover must be able to prove the send tx + env.dstProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() env.srcClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() - // check timeout finality: source client's proof generator must be able to prove a destination timestamp past the timeout - env.srcProofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() + // check timeout finality: source client's prover must be able to prove a destination timestamp past the timeout + env.srcProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Now(), nil).Once() // timeout delivery on the source chain timeoutEvent := sendPacketEvent(42) timeoutEvent.Height = 150 // source height is unrelated to the destination proof height - mockRelay(env.srcClient, env.srcProofGen, env.srcTxBuilder, []v2.PacketEvent{timeoutEvent}, "0xrouter") + mockRelay(env.srcClient, env.srcProver, env.srcTxBuilder, []v2.PacketEvent{timeoutEvent}, "0xrouter") env.srcClient.EXPECT().WaitForChain(mock.Anything).Return(nil).Once() env.srcTxSubmitter.EXPECT().Submit(mock.Anything, mock.Anything).Return(&v2.Submission{ TxHash: timeoutTxHash, SubmittedAt: time.Now().UTC(), RelayerAddress: "0xrelayer", @@ -410,8 +410,8 @@ func TestPipelineLifecycle(t *testing.T) { Return(true, nil). Once() - // check send finality: the send event's height is past what the destination proof generator can prove - env.dstProofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(50), time.Now(), nil).Once() + // check send finality: the send event's height is past what the destination prover can prove + env.dstProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(50), time.Now(), nil).Once() env.srcClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() out := runPipeline(t, deps, fastOpts(), tr) diff --git a/cli/internal/relay/processors/batch_ack_packet.go b/cli/internal/relay/processors/batch_ack_packet.go index b3fedd948..a388d2dd4 100644 --- a/cli/internal/relay/processors/batch_ack_packet.go +++ b/cli/internal/relay/processors/batch_ack_packet.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/txsubmitter" @@ -24,7 +24,7 @@ type BatchAckPacket struct { destinationChainClient chains.Client sourceChainClient chains.Client route Route - proofGen proofgen.ProofGenerator + prover prover.Prover txBuilder txbuilder.TxBuilder txSubmitter txsubmitter.TxSubmitter storage TxStorage @@ -32,7 +32,7 @@ type BatchAckPacket struct { func NewBatchAckPacket( chainClients ChainClients, - proofGenerators ProofGenerators, + provers Provers, txBuilders TxBuilders, storage TxStorage, txSubmitter txsubmitter.TxSubmitter, @@ -48,10 +48,10 @@ func NewBatchAckPacket( return BatchAckPacket{}, errors.Errorf("no configured chain client for chain %s", route.SourceChainID) } - proofGen, ok := proofGenerators.Get(route.SourceChainID, route.SourceClientID) + prover, ok := provers.Get(route.SourceChainID, route.SourceClientID) if !ok { return BatchAckPacket{}, errors.Errorf( - "no proof generator configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, + "no prover configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, ) } @@ -64,7 +64,7 @@ func NewBatchAckPacket( destinationChainClient: destinationChainClient, sourceChainClient: sourceChainClient, route: route, - proofGen: proofGen, + prover: prover, txBuilder: txBuilder, txSubmitter: txSubmitter, storage: storage, @@ -72,7 +72,7 @@ func NewBatchAckPacket( } func (p BatchAckPacket) Process(ctx context.Context, transfers []*Transfer) ([]*Transfer, error) { - proofHeight, _, err := p.proofGen.LatestProvableHeight(ctx) + proofHeight, _, err := p.prover.LatestProvableHeight(ctx) if err != nil { return nil, errors.Wrap(err, "resolving latest provable height") } @@ -122,7 +122,7 @@ func (p BatchAckPacket) Process(ctx context.Context, transfers []*Transfer) ([]* } submission, err := relayPackets( - ctx, p.sourceChainClient, p.proofGen, p.txBuilder, p.txSubmitter, + ctx, p.sourceChainClient, p.prover, p.txBuilder, p.txSubmitter, p.route.SourceClientID, v2.RelayKindAck, proofHeight, events, ) diff --git a/cli/internal/relay/processors/batch_recv_packet.go b/cli/internal/relay/processors/batch_recv_packet.go index 5197eebc3..e09b08830 100644 --- a/cli/internal/relay/processors/batch_recv_packet.go +++ b/cli/internal/relay/processors/batch_recv_packet.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/txsubmitter" @@ -24,7 +24,7 @@ type BatchRecvPacket struct { sourceChainClient chains.Client destinationChainClient chains.Client route Route - proofGen proofgen.ProofGenerator + prover prover.Prover txBuilder txbuilder.TxBuilder txSubmitter txsubmitter.TxSubmitter storage TxStorage @@ -32,7 +32,7 @@ type BatchRecvPacket struct { func NewBatchRecvPacket( chainClients ChainClients, - proofGenerators ProofGenerators, + provers Provers, txBuilders TxBuilders, storage TxStorage, txSubmitter txsubmitter.TxSubmitter, @@ -48,10 +48,10 @@ func NewBatchRecvPacket( return BatchRecvPacket{}, errors.Errorf("no configured chain client for chain %s", route.DestinationChainID) } - proofGen, ok := proofGenerators.Get(route.DestinationChainID, route.DestinationClientID) + prover, ok := provers.Get(route.DestinationChainID, route.DestinationClientID) if !ok { return BatchRecvPacket{}, errors.Errorf( - "no proof generator configured for client %q on chain %q", + "no prover configured for client %q on chain %q", route.DestinationClientID, route.DestinationChainID, ) @@ -66,7 +66,7 @@ func NewBatchRecvPacket( sourceChainClient: sourceChainClient, destinationChainClient: destinationChainClient, route: route, - proofGen: proofGen, + prover: prover, txBuilder: txBuilder, txSubmitter: txSubmitter, storage: storage, @@ -74,7 +74,7 @@ func NewBatchRecvPacket( } func (p BatchRecvPacket) Process(ctx context.Context, transfers []*Transfer) ([]*Transfer, error) { - proofHeight, _, err := p.proofGen.LatestProvableHeight(ctx) + proofHeight, _, err := p.prover.LatestProvableHeight(ctx) if err != nil { return nil, errors.Wrap(err, "resolving latest provable height") } @@ -116,7 +116,7 @@ func (p BatchRecvPacket) Process(ctx context.Context, transfers []*Transfer) ([] } submission, err := relayPackets( - ctx, p.destinationChainClient, p.proofGen, p.txBuilder, p.txSubmitter, + ctx, p.destinationChainClient, p.prover, p.txBuilder, p.txSubmitter, p.route.DestinationClientID, v2.RelayKindRecv, proofHeight, events, ) diff --git a/cli/internal/relay/processors/batch_recv_packet_test.go b/cli/internal/relay/processors/batch_recv_packet_test.go index c6122389f..39885780f 100644 --- a/cli/internal/relay/processors/batch_recv_packet_test.go +++ b/cli/internal/relay/processors/batch_recv_packet_test.go @@ -16,7 +16,7 @@ import ( channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/tests/mocks" @@ -30,10 +30,10 @@ func (s staticChains) Get(chainID string) (chains.Client, bool) { return client, ok } -type staticProofGenerators map[string]proofgen.ProofGenerator +type staticProvers map[string]prover.Prover -func (s staticProofGenerators) Get(chainID, clientID string) (proofgen.ProofGenerator, bool) { - gen, ok := s[proofgen.Key(chainID, clientID)] +func (s staticProvers) Get(chainID, clientID string) (prover.Prover, bool) { + gen, ok := s[prover.Key(chainID, clientID)] return gen, ok } @@ -101,10 +101,10 @@ func TestBatchRecvPacketSequenceAlignment(t *testing.T) { }, nil }).Once() - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil) - proofGen.EXPECT().StateProof(mock.Anything, uint64(100)).Return([]byte{0x01}, nil) - proofGen.EXPECT().PacketProofs(mock.Anything, uint64(100), v2.ProofKindPacketCommitment, mock.Anything). + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil) + mockProver.EXPECT().StateProof(mock.Anything, uint64(100)).Return([]byte{0x01}, nil) + mockProver.EXPECT().PacketProofs(mock.Anything, uint64(100), v2.ProofKindPacketCommitment, mock.Anything). Return([][]byte{{0x02}}, nil) txBuilder := mocks.NewMockTxBuilder(t) @@ -120,7 +120,7 @@ func TestBatchRecvPacketSequenceAlignment(t *testing.T) { p, err := NewBatchRecvPacket( staticChains{route.SourceChainID: sourceChainClient, route.DestinationChainID: destinationChainClient}, - staticProofGenerators{proofgen.Key(route.DestinationChainID, route.DestinationClientID): proofGen}, + staticProvers{prover.Key(route.DestinationChainID, route.DestinationClientID): mockProver}, staticTxBuilders{route.DestinationChainID: txBuilder}, db, txSubmitter, @@ -198,10 +198,10 @@ func TestBatchRecvPacketToleratesPartialEventFetchFailure(t *testing.T) { }, nil).Once() sourceChainClient.EXPECT().TxPacketEvents(mock.Anything, failingTxID).Return(nil, assert.AnError).Once() - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil) - proofGen.EXPECT().StateProof(mock.Anything, uint64(100)).Return([]byte{0x01}, nil) - proofGen.EXPECT().PacketProofs(mock.Anything, uint64(100), v2.ProofKindPacketCommitment, mock.Anything). + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil) + mockProver.EXPECT().StateProof(mock.Anything, uint64(100)).Return([]byte{0x01}, nil) + mockProver.EXPECT().PacketProofs(mock.Anything, uint64(100), v2.ProofKindPacketCommitment, mock.Anything). Return([][]byte{{0x02}}, nil) txBuilder := mocks.NewMockTxBuilder(t) @@ -217,7 +217,7 @@ func TestBatchRecvPacketToleratesPartialEventFetchFailure(t *testing.T) { p, err := NewBatchRecvPacket( staticChains{route.SourceChainID: sourceChainClient, route.DestinationChainID: destinationChainClient}, - staticProofGenerators{proofgen.Key(route.DestinationChainID, route.DestinationClientID): proofGen}, + staticProvers{prover.Key(route.DestinationChainID, route.DestinationClientID): mockProver}, staticTxBuilders{route.DestinationChainID: txBuilder}, db, txSubmitter, @@ -303,10 +303,10 @@ func TestBatchRecvPacketExcludesNotYetProvablePackets(t *testing.T) { }, }, nil).Once() - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil) - proofGen.EXPECT().StateProof(mock.Anything, uint64(100)).Return([]byte{0x01}, nil) - proofGen.EXPECT().PacketProofs(mock.Anything, uint64(100), v2.ProofKindPacketCommitment, mock.Anything). + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil) + mockProver.EXPECT().StateProof(mock.Anything, uint64(100)).Return([]byte{0x01}, nil) + mockProver.EXPECT().PacketProofs(mock.Anything, uint64(100), v2.ProofKindPacketCommitment, mock.Anything). Return([][]byte{{0x02}}, nil) txBuilder := mocks.NewMockTxBuilder(t) @@ -322,7 +322,7 @@ func TestBatchRecvPacketExcludesNotYetProvablePackets(t *testing.T) { p, err := NewBatchRecvPacket( staticChains{route.SourceChainID: sourceChainClient, route.DestinationChainID: destinationChainClient}, - staticProofGenerators{proofgen.Key(route.DestinationChainID, route.DestinationClientID): proofGen}, + staticProvers{prover.Key(route.DestinationChainID, route.DestinationClientID): mockProver}, staticTxBuilders{route.DestinationChainID: txBuilder}, db, txSubmitter, diff --git a/cli/internal/relay/processors/batch_relay.go b/cli/internal/relay/processors/batch_relay.go index 4be733a75..605527b46 100644 --- a/cli/internal/relay/processors/batch_relay.go +++ b/cli/internal/relay/processors/batch_relay.go @@ -10,7 +10,7 @@ import ( channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/txsubmitter" v2 "github.com/cosmos/ibc/cli/internal/types/v2" @@ -71,7 +71,7 @@ func proofKindFor(relayKind v2.RelayKind) v2.ProofKind { func relayPackets( ctx context.Context, chainClient chains.Client, - proofGen proofgen.ProofGenerator, + prover prover.Prover, txBuilder txbuilder.TxBuilder, txSubmitter txsubmitter.TxSubmitter, clientID string, @@ -79,7 +79,7 @@ func relayPackets( proofHeight uint64, events []v2.PacketEvent, ) (*v2.Submission, error) { - stateProof, err := proofGen.StateProof(ctx, proofHeight) + stateProof, err := prover.StateProof(ctx, proofHeight) if err != nil { return nil, errors.Wrap(err, "generating state proof") } @@ -89,7 +89,7 @@ func relayPackets( packets[i] = event.Packet } - packetProofs, err := proofGen.PacketProofs(ctx, proofHeight, proofKindFor(relayKind), packets) + packetProofs, err := prover.PacketProofs(ctx, proofHeight, proofKindFor(relayKind), packets) if err != nil { return nil, errors.Wrap(err, "generating packet proofs") } diff --git a/cli/internal/relay/processors/check_send_finality.go b/cli/internal/relay/processors/check_send_finality.go index 66613a619..8b39f1693 100644 --- a/cli/internal/relay/processors/check_send_finality.go +++ b/cli/internal/relay/processors/check_send_finality.go @@ -11,21 +11,21 @@ import ( "github.com/pkg/errors" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/store" ) // CheckSendFinality gates relaying on the send tx's height being at or -// before the height the destination client's proof generator can currently +// before the height the destination client's prover can currently // prove. type CheckSendFinality struct { sourceChainClient chains.Client - proofGen proofgen.ProofGenerator + prover prover.Prover } func NewCheckSendFinality( chainClients ChainClients, - proofGenerators ProofGenerators, + provers Provers, route Route, ) (CheckSendFinality, error) { sourceChainClient, ok := chainClients.Get(route.SourceChainID) @@ -33,19 +33,19 @@ func NewCheckSendFinality( return CheckSendFinality{}, errors.Errorf("no configured chain client for source chain %s", route.SourceChainID) } - proofGen, ok := proofGenerators.Get(route.DestinationChainID, route.DestinationClientID) + prover, ok := provers.Get(route.DestinationChainID, route.DestinationClientID) if !ok { return CheckSendFinality{}, errors.Errorf( - "no proof generator configured for client %q on chain %q", + "no prover configured for client %q on chain %q", route.DestinationClientID, route.DestinationChainID, ) } - return CheckSendFinality{sourceChainClient: sourceChainClient, proofGen: proofGen}, nil + return CheckSendFinality{sourceChainClient: sourceChainClient, prover: prover}, nil } func (p CheckSendFinality) Process(ctx context.Context, tr *Transfer) (*Transfer, error) { - proofHeight, _, err := p.proofGen.LatestProvableHeight(ctx) + proofHeight, _, err := p.prover.LatestProvableHeight(ctx) if err != nil { return nil, errors.Wrap(err, "resolving latest provable height") } diff --git a/cli/internal/relay/processors/check_send_finality_test.go b/cli/internal/relay/processors/check_send_finality_test.go index 79d39b1d4..b0f13693d 100644 --- a/cli/internal/relay/processors/check_send_finality_test.go +++ b/cli/internal/relay/processors/check_send_finality_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/tests/mocks" ) @@ -31,18 +31,18 @@ func TestNewCheckSendFinality(t *testing.T) { t.Run("missingChainClientErrors", func(t *testing.T) { _, err := NewCheckSendFinality( staticChains{}, - staticProofGenerators{ - proofgen.Key(route.DestinationChainID, route.DestinationClientID): mocks.NewMockProofGenerator(t), + staticProvers{ + prover.Key(route.DestinationChainID, route.DestinationClientID): mocks.NewMockProver(t), }, route, ) require.Error(t, err) }) - t.Run("missingProofGeneratorErrors", func(t *testing.T) { + t.Run("missingProverErrors", func(t *testing.T) { _, err := NewCheckSendFinality( staticChains{route.SourceChainID: mocks.NewMockClient(t)}, - staticProofGenerators{}, + staticProvers{}, route, ) require.Error(t, err) @@ -68,12 +68,12 @@ func TestCheckSendFinalityProcess(t *testing.T) { sourceChainClient := mocks.NewMockClient(t) sourceChainClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() p, err := NewCheckSendFinality( staticChains{route.SourceChainID: sourceChainClient}, - staticProofGenerators{proofgen.Key(route.DestinationChainID, route.DestinationClientID): proofGen}, + staticProvers{prover.Key(route.DestinationChainID, route.DestinationClientID): mockProver}, route, ) require.NoError(t, err) @@ -88,12 +88,12 @@ func TestCheckSendFinalityProcess(t *testing.T) { sourceChainClient := mocks.NewMockClient(t) sourceChainClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(150), nil).Once() - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() p, err := NewCheckSendFinality( staticChains{route.SourceChainID: sourceChainClient}, - staticProofGenerators{proofgen.Key(route.DestinationChainID, route.DestinationClientID): proofGen}, + staticProvers{prover.Key(route.DestinationChainID, route.DestinationClientID): mockProver}, route, ) require.NoError(t, err) diff --git a/cli/internal/relay/processors/check_timeout_finality.go b/cli/internal/relay/processors/check_timeout_finality.go index d6ff13cd1..a6074b20c 100644 --- a/cli/internal/relay/processors/check_timeout_finality.go +++ b/cli/internal/relay/processors/check_timeout_finality.go @@ -8,30 +8,30 @@ import ( "github.com/pkg/errors" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/store" ) // CheckTimeoutFinality gates timing out a packet on the source client's -// proof generator currently being able to prove a destination-chain +// prover currently being able to prove a destination-chain // timestamp past the timeout. type CheckTimeoutFinality struct { - proofGen proofgen.ProofGenerator + prover prover.Prover } -func NewCheckTimeoutFinality(proofGenerators ProofGenerators, route Route) (CheckTimeoutFinality, error) { - proofGen, ok := proofGenerators.Get(route.SourceChainID, route.SourceClientID) +func NewCheckTimeoutFinality(provers Provers, route Route) (CheckTimeoutFinality, error) { + prover, ok := provers.Get(route.SourceChainID, route.SourceClientID) if !ok { return CheckTimeoutFinality{}, errors.Errorf( - "no proof generator configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, + "no prover configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, ) } - return CheckTimeoutFinality{proofGen: proofGen}, nil + return CheckTimeoutFinality{prover: prover}, nil } func (p CheckTimeoutFinality) Process(ctx context.Context, tr *Transfer) (*Transfer, error) { - _, timestamp, err := p.proofGen.LatestProvableHeight(ctx) + _, timestamp, err := p.prover.LatestProvableHeight(ctx) if err != nil { return nil, errors.Wrap(err, "resolving latest provable height") } diff --git a/cli/internal/relay/processors/check_timeout_finality_test.go b/cli/internal/relay/processors/check_timeout_finality_test.go index fa73273a4..d872128bd 100644 --- a/cli/internal/relay/processors/check_timeout_finality_test.go +++ b/cli/internal/relay/processors/check_timeout_finality_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/tests/mocks" ) @@ -19,15 +19,15 @@ import ( func TestNewCheckTimeoutFinality(t *testing.T) { route := testSendFinalityRoute() - t.Run("missingProofGeneratorErrors", func(t *testing.T) { - _, err := NewCheckTimeoutFinality(staticProofGenerators{}, route) + t.Run("missingProverErrors", func(t *testing.T) { + _, err := NewCheckTimeoutFinality(staticProvers{}, route) require.Error(t, err) }) - t.Run("resolvesProofGenerator", func(t *testing.T) { + t.Run("resolvesProver", func(t *testing.T) { _, err := NewCheckTimeoutFinality( - staticProofGenerators{ - proofgen.Key(route.SourceChainID, route.SourceClientID): mocks.NewMockProofGenerator(t), + staticProvers{ + prover.Key(route.SourceChainID, route.SourceClientID): mocks.NewMockProver(t), }, route, ) @@ -50,11 +50,11 @@ func TestCheckTimeoutFinalityProcess(t *testing.T) { } t.Run("timestampPastTimeoutIsFinalized", func(t *testing.T) { - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Unix(2000, 0), nil).Once() + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Unix(2000, 0), nil).Once() p, err := NewCheckTimeoutFinality( - staticProofGenerators{proofgen.Key(route.SourceChainID, route.SourceClientID): proofGen}, + staticProvers{prover.Key(route.SourceChainID, route.SourceClientID): mockProver}, route, ) require.NoError(t, err) @@ -66,11 +66,11 @@ func TestCheckTimeoutFinalityProcess(t *testing.T) { }) t.Run("timestampBeforeTimeoutErrorsRetryable", func(t *testing.T) { - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Unix(500, 0), nil).Once() + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Unix(500, 0), nil).Once() p, err := NewCheckTimeoutFinality( - staticProofGenerators{proofgen.Key(route.SourceChainID, route.SourceClientID): proofGen}, + staticProvers{prover.Key(route.SourceChainID, route.SourceClientID): mockProver}, route, ) require.NoError(t, err) diff --git a/cli/internal/relay/processors/check_write_ack_finality.go b/cli/internal/relay/processors/check_write_ack_finality.go index 475d3318e..c8eac1e58 100644 --- a/cli/internal/relay/processors/check_write_ack_finality.go +++ b/cli/internal/relay/processors/check_write_ack_finality.go @@ -11,21 +11,21 @@ import ( "github.com/pkg/errors" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/store" ) // CheckWriteAckFinality gates ack relaying on the write ack tx's height -// being at or before the height the source client's proof generator can +// being at or before the height the source client's prover can // currently prove. type CheckWriteAckFinality struct { destinationChainClient chains.Client - proofGen proofgen.ProofGenerator + prover prover.Prover } func NewCheckWriteAckFinality( chainClients ChainClients, - proofGenerators ProofGenerators, + provers Provers, route Route, ) (CheckWriteAckFinality, error) { destinationChainClient, ok := chainClients.Get(route.DestinationChainID) @@ -35,14 +35,14 @@ func NewCheckWriteAckFinality( ) } - proofGen, ok := proofGenerators.Get(route.SourceChainID, route.SourceClientID) + prover, ok := provers.Get(route.SourceChainID, route.SourceClientID) if !ok { return CheckWriteAckFinality{}, errors.Errorf( - "no proof generator configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, + "no prover configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, ) } - return CheckWriteAckFinality{destinationChainClient: destinationChainClient, proofGen: proofGen}, nil + return CheckWriteAckFinality{destinationChainClient: destinationChainClient, prover: prover}, nil } func (p CheckWriteAckFinality) Process(ctx context.Context, tr *Transfer) (*Transfer, error) { @@ -50,7 +50,7 @@ func (p CheckWriteAckFinality) Process(ctx context.Context, tr *Transfer) (*Tran return nil, errors.New("transfer has no write ack tx hash, violates ShouldProcess") } - proofHeight, _, err := p.proofGen.LatestProvableHeight(ctx) + proofHeight, _, err := p.prover.LatestProvableHeight(ctx) if err != nil { return nil, errors.Wrap(err, "resolving latest provable height") } diff --git a/cli/internal/relay/processors/check_write_ack_finality_test.go b/cli/internal/relay/processors/check_write_ack_finality_test.go index 0d7dba020..a99ea1269 100644 --- a/cli/internal/relay/processors/check_write_ack_finality_test.go +++ b/cli/internal/relay/processors/check_write_ack_finality_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/tests/mocks" ) @@ -22,18 +22,18 @@ func TestNewCheckWriteAckFinality(t *testing.T) { t.Run("missingChainClientErrors", func(t *testing.T) { _, err := NewCheckWriteAckFinality( staticChains{}, - staticProofGenerators{ - proofgen.Key(route.SourceChainID, route.SourceClientID): mocks.NewMockProofGenerator(t), + staticProvers{ + prover.Key(route.SourceChainID, route.SourceClientID): mocks.NewMockProver(t), }, route, ) require.Error(t, err) }) - t.Run("missingProofGeneratorErrors", func(t *testing.T) { + t.Run("missingProverErrors", func(t *testing.T) { _, err := NewCheckWriteAckFinality( staticChains{route.DestinationChainID: mocks.NewMockClient(t)}, - staticProofGenerators{}, + staticProvers{}, route, ) require.Error(t, err) @@ -60,12 +60,12 @@ func TestCheckWriteAckFinalityProcess(t *testing.T) { destinationChainClient := mocks.NewMockClient(t) destinationChainClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(100), nil).Once() - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() p, err := NewCheckWriteAckFinality( staticChains{route.DestinationChainID: destinationChainClient}, - staticProofGenerators{proofgen.Key(route.SourceChainID, route.SourceClientID): proofGen}, + staticProvers{prover.Key(route.SourceChainID, route.SourceClientID): mockProver}, route, ) require.NoError(t, err) @@ -80,12 +80,12 @@ func TestCheckWriteAckFinalityProcess(t *testing.T) { destinationChainClient := mocks.NewMockClient(t) destinationChainClient.EXPECT().TxHeight(mock.Anything, mock.Anything).Return(uint64(150), nil).Once() - proofGen := mocks.NewMockProofGenerator(t) - proofGen.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() + mockProver := mocks.NewMockProver(t) + mockProver.EXPECT().LatestProvableHeight(mock.Anything).Return(uint64(100), time.Time{}, nil).Once() p, err := NewCheckWriteAckFinality( staticChains{route.DestinationChainID: destinationChainClient}, - staticProofGenerators{proofgen.Key(route.SourceChainID, route.SourceClientID): proofGen}, + staticProvers{prover.Key(route.SourceChainID, route.SourceClientID): mockProver}, route, ) require.NoError(t, err) diff --git a/cli/internal/relay/processors/processors.go b/cli/internal/relay/processors/processors.go index 79fd5a74e..2768599ca 100644 --- a/cli/internal/relay/processors/processors.go +++ b/cli/internal/relay/processors/processors.go @@ -7,7 +7,7 @@ import ( "time" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/store" ) @@ -26,9 +26,9 @@ type ChainClients interface { Get(chainID string) (chains.Client, bool) } -// ProofGenerators resolves proof generators by (chainIDclientID). -type ProofGenerators interface { - Get(chainID, clientID string) (proofgen.ProofGenerator, bool) +// Provers resolves provers by (chainIDclientID). +type Provers interface { + Get(chainID, clientID string) (prover.Prover, bool) } // TxBuilders resolves tx builders by chain id. diff --git a/cli/internal/relay/processors/timeout_packet.go b/cli/internal/relay/processors/timeout_packet.go index aad754c34..db120fa8b 100644 --- a/cli/internal/relay/processors/timeout_packet.go +++ b/cli/internal/relay/processors/timeout_packet.go @@ -10,7 +10,7 @@ import ( "github.com/pkg/errors" "github.com/cosmos/ibc/cli/internal/chains" - "github.com/cosmos/ibc/cli/internal/relay/proofgen" + "github.com/cosmos/ibc/cli/internal/relay/prover" "github.com/cosmos/ibc/cli/internal/relay/txbuilder" "github.com/cosmos/ibc/cli/internal/store" "github.com/cosmos/ibc/cli/internal/txsubmitter" @@ -22,7 +22,7 @@ import ( type BatchTimeoutPacket struct { sourceChainClient chains.Client route Route - proofGen proofgen.ProofGenerator + prover prover.Prover txBuilder txbuilder.TxBuilder txSubmitter txsubmitter.TxSubmitter storage TxStorage @@ -30,7 +30,7 @@ type BatchTimeoutPacket struct { func NewBatchTimeoutPacket( chainClients ChainClients, - proofGenerators ProofGenerators, + provers Provers, txBuilders TxBuilders, storage TxStorage, txSubmitter txsubmitter.TxSubmitter, @@ -41,10 +41,10 @@ func NewBatchTimeoutPacket( return BatchTimeoutPacket{}, errors.Errorf("no configured chain client for chain %s", route.SourceChainID) } - proofGen, ok := proofGenerators.Get(route.SourceChainID, route.SourceClientID) + prover, ok := provers.Get(route.SourceChainID, route.SourceClientID) if !ok { return BatchTimeoutPacket{}, errors.Errorf( - "no proof generator configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, + "no prover configured for client %q on chain %q", route.SourceClientID, route.SourceChainID, ) } @@ -56,7 +56,7 @@ func NewBatchTimeoutPacket( return BatchTimeoutPacket{ sourceChainClient: sourceChainClient, route: route, - proofGen: proofGen, + prover: prover, txBuilder: txBuilder, txSubmitter: txSubmitter, storage: storage, @@ -64,7 +64,7 @@ func NewBatchTimeoutPacket( } func (p BatchTimeoutPacket) Process(ctx context.Context, transfers []*Transfer) ([]*Transfer, error) { - proofHeight, timestamp, err := p.proofGen.LatestProvableHeight(ctx) + proofHeight, timestamp, err := p.prover.LatestProvableHeight(ctx) if err != nil { return nil, errors.Wrap(err, "resolving latest provable height") } @@ -111,7 +111,7 @@ func (p BatchTimeoutPacket) Process(ctx context.Context, transfers []*Transfer) } submission, err := relayPackets( - ctx, p.sourceChainClient, p.proofGen, p.txBuilder, p.txSubmitter, + ctx, p.sourceChainClient, p.prover, p.txBuilder, p.txSubmitter, p.route.SourceClientID, v2.RelayKindTimeout, proofHeight, events, ) diff --git a/cli/internal/relay/proofgen/attestation/generator.go b/cli/internal/relay/prover/attestation/generator.go similarity index 98% rename from cli/internal/relay/proofgen/attestation/generator.go rename to cli/internal/relay/prover/attestation/generator.go index 871e6b967..12219fdc0 100644 --- a/cli/internal/relay/proofgen/attestation/generator.go +++ b/cli/internal/relay/prover/attestation/generator.go @@ -16,7 +16,7 @@ import ( v2 "github.com/cosmos/ibc/cli/internal/types/v2" ) -// Generator implements proofgen.ProofGenerator for one configured +// Generator implements prover.Prover for one configured // attestation light client: LatestProvableHeight/StateProof/PacketProofs all // query the same fixed attestor set with the same quorum threshold type Generator struct { diff --git a/cli/internal/relay/proofgen/attestation/generator_test.go b/cli/internal/relay/prover/attestation/generator_test.go similarity index 100% rename from cli/internal/relay/proofgen/attestation/generator_test.go rename to cli/internal/relay/prover/attestation/generator_test.go diff --git a/cli/internal/relay/proofgen/attestation/quorum.go b/cli/internal/relay/prover/attestation/quorum.go similarity index 100% rename from cli/internal/relay/proofgen/attestation/quorum.go rename to cli/internal/relay/prover/attestation/quorum.go diff --git a/cli/internal/relay/proofgen/attestation/quorum_test.go b/cli/internal/relay/prover/attestation/quorum_test.go similarity index 100% rename from cli/internal/relay/proofgen/attestation/quorum_test.go rename to cli/internal/relay/prover/attestation/quorum_test.go diff --git a/cli/internal/relay/proofgen/attestation/resolve.go b/cli/internal/relay/prover/attestation/resolve.go similarity index 100% rename from cli/internal/relay/proofgen/attestation/resolve.go rename to cli/internal/relay/prover/attestation/resolve.go diff --git a/cli/internal/relay/proofgen/attestation/resolve_test.go b/cli/internal/relay/prover/attestation/resolve_test.go similarity index 100% rename from cli/internal/relay/proofgen/attestation/resolve_test.go rename to cli/internal/relay/prover/attestation/resolve_test.go diff --git a/cli/internal/relay/proofgen/proofgen.go b/cli/internal/relay/prover/prover.go similarity index 71% rename from cli/internal/relay/proofgen/proofgen.go rename to cli/internal/relay/prover/prover.go index 28cf5c38e..be79e2926 100644 --- a/cli/internal/relay/proofgen/proofgen.go +++ b/cli/internal/relay/prover/prover.go @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 -// Package proofgen generates packet membership/non-membership proofs and +// Package prover generates packet membership/non-membership proofs and // light-client state proofs. There is one implementation per light-client // type. -package proofgen +package prover import ( "context" @@ -14,14 +14,15 @@ import ( channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" "github.com/cosmos/ibc/cli/internal/chains" "github.com/cosmos/ibc/cli/internal/config" - "github.com/cosmos/ibc/cli/internal/relay/proofgen/attestation" + "github.com/cosmos/ibc/cli/internal/relay/prover/attestation" + "github.com/cosmos/ibc/cli/internal/relay/prover/remote" "github.com/cosmos/ibc/cli/internal/service/attestor" v2 "github.com/cosmos/ibc/cli/internal/types/v2" ) -// ProofGenerator generates packet membership/non-membership proofs and state +// Prover generates packet membership/non-membership proofs and state // proofs for one configured light client. -type ProofGenerator interface { +type Prover interface { // LatestProvableHeight resolves the highest height a subsequent StateProof // and PacketProofs call sharing that height can currently succeed at, // along with that height's counterparty-chain timestamp @@ -41,33 +42,33 @@ type ProofGenerator interface { ) ([][]byte, error) } -var _ ProofGenerator = (*attestation.Generator)(nil) +var _ Prover = (*attestation.Generator)(nil) // Key identifies one configured light client by the chain it lives on and -// its client id, the composite key ProofGenerator instances are scoped by. +// its client id, the composite key Prover instances are scoped by. func Key(chainID, clientID string) string { return chainID + "/" + clientID } -// Set resolves a ProofGenerator by (chainID, clientID). +// Set resolves a Prover by (chainID, clientID). type Set struct { - generators map[string]ProofGenerator + generators map[string]Prover } -func NewSet(generators map[string]ProofGenerator) *Set { +func NewSet(generators map[string]Prover) *Set { if generators == nil { - generators = make(map[string]ProofGenerator) + generators = make(map[string]Prover) } return &Set{generators: generators} } -func (s *Set) Get(chainID, clientID string) (ProofGenerator, bool) { +func (s *Set) Get(chainID, clientID string) (Prover, bool) { generator, ok := s.generators[Key(chainID, clientID)] return generator, ok } -// NewSetFromConfig resolves a ProofGenerator for every client end of every +// NewSetFromConfig resolves a Prover for every client end of every // configured connection, matching against attestors (this process's own // local attestors plus every resolved remote one). func NewSetFromConfig( @@ -76,7 +77,7 @@ func NewSetFromConfig( clientSet *chains.ClientSet, attestors []attestor.Attestor, ) (*Set, error) { - generators := make(map[string]ProofGenerator, len(cfg.Relayer.Connections)*2) + generators := make(map[string]Prover, len(cfg.Relayer.Connections)*2) err := forEachClientEnd(cfg, func(connAlias string, self, counterparty config.ClientEnd) error { return addGenerator(ctx, generators, connAlias, self, counterparty, clientSet, attestors) @@ -109,7 +110,7 @@ func forEachClientEnd(cfg config.Config, fn func(connAlias string, self, counter func addGenerator( ctx context.Context, - generators map[string]ProofGenerator, + generators map[string]Prover, connAlias string, client, clientCounterparty config.ClientEnd, clientSet *chains.ClientSet, @@ -124,6 +125,22 @@ func addGenerator( generators[Key(client.ChainID, client.ClientID)] = gen + return nil + case config.ClientTypeRemote: + params, err := client.ClientParams() + if err != nil { + return errors.Wrapf(err, "connection %q", connAlias) + } + + remoteParams, ok := params.(*config.RemoteParams) + if !ok { + return errors.Errorf("connection %q: %T is not remote prover params", connAlias, params) + } + + generators[Key(client.ChainID, client.ClientID)] = remote.NewFromURL( + remoteParams.URL, client.ChainID, client.ClientID, + ) + return nil default: return errors.Errorf("connection %q: unsupported client type %q for proof generation", connAlias, client.Type) diff --git a/cli/internal/relay/proofgen/proofgen_test.go b/cli/internal/relay/prover/prover_test.go similarity index 98% rename from cli/internal/relay/proofgen/proofgen_test.go rename to cli/internal/relay/prover/prover_test.go index aff6581d2..31d8baaee 100644 --- a/cli/internal/relay/proofgen/proofgen_test.go +++ b/cli/internal/relay/prover/prover_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package proofgen +package prover import ( "context" @@ -47,7 +47,7 @@ func localCandidate(t *testing.T, alias, watchedChainID, address string) attesto // testConfig builds a config, matching *ClientSet, and candidate list whose // connection is trivially satisfiable, isolating dispatch-level coverage // from the attestor-matching specifics (covered in -// internal/relay/proofgen/attestation). +// internal/relay/prover/attestation). func testConfig(t *testing.T) (config.Config, *chains.ClientSet, []attestor.Attestor) { t.Helper() diff --git a/cli/internal/relay/prover/remote/remote.go b/cli/internal/relay/prover/remote/remote.go new file mode 100644 index 000000000..c98598e10 --- /dev/null +++ b/cli/internal/relay/prover/remote/remote.go @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package remote implements the relayer's Prover against a ProverService, so a +// custom light client is a service rather than code in the relayer. +package remote + +import ( + "context" + "net/http" + "time" + + "connectrpc.com/connect" + "github.com/pkg/errors" + + channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" + proverv2 "github.com/cosmos/ibc/cli/api/v2/prover" + v2 "github.com/cosmos/ibc/cli/internal/types/v2" +) + +const requestTimeout = time.Minute + +// Prover proves one light client remotely. Every request names the client, so +// one service can serve many. +type Prover struct { + client proverv2.ProverServiceClient + chainID string + clientID string +} + +func New(httpClient connect.HTTPClient, url, chainID, clientID string) *Prover { + return &Prover{ + client: proverv2.NewProverServiceClient(httpClient, url, connect.WithGRPC()), + chainID: chainID, + clientID: clientID, + } +} + +// NewFromURL dials url with a client that can negotiate h2c, which gRPC +// requires over plaintext. +func NewFromURL(url, chainID, clientID string) *Prover { + return New(newHTTPClient(), url, chainID, clientID) +} + +func newHTTPClient() *http.Client { + protocols := new(http.Protocols) + protocols.SetHTTP2(true) + protocols.SetUnencryptedHTTP2(true) + + return &http.Client{Transport: &http.Transport{Protocols: protocols}} +} + +func (p *Prover) target() *proverv2.Client { + return &proverv2.Client{ChainId: p.chainID, ClientId: p.clientID} +} + +func (p *Prover) LatestProvableHeight(ctx context.Context) (uint64, time.Time, error) { + ctx, cancel := context.WithTimeout(ctx, requestTimeout) + defer cancel() + + res, err := p.client.LatestProvableHeight(ctx, connect.NewRequest(&proverv2.LatestProvableHeightRequest{ + Client: p.target(), + })) + if err != nil { + return 0, time.Time{}, errors.Wrap(err, "remote prover: latest provable height") + } + + //nolint:gosec // seconds since the epoch, matching the ibc packet timestamp + seconds := int64(res.Msg.GetTimestamp()) + + return res.Msg.GetHeight(), time.Unix(seconds, 0).UTC(), nil +} + +func (p *Prover) StateProof(ctx context.Context, height uint64) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, requestTimeout) + defer cancel() + + res, err := p.client.StateProof(ctx, connect.NewRequest(&proverv2.StateProofRequest{ + Client: p.target(), + Height: height, + })) + if err != nil { + return nil, errors.Wrap(err, "remote prover: state proof") + } + + return res.Msg.GetProof(), nil +} + +func (p *Prover) PacketProofs( + ctx context.Context, + height uint64, + kind v2.ProofKind, + packets []channeltypesv2.Packet, +) ([][]byte, error) { + protoKind, err := proofKindToProto(kind) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithTimeout(ctx, requestTimeout) + defer cancel() + + res, err := p.client.PacketProofs(ctx, connect.NewRequest(&proverv2.PacketProofsRequest{ + Client: p.target(), + Height: height, + Kind: protoKind, + Packets: packetsToProto(packets), + })) + if err != nil { + return nil, errors.Wrap(err, "remote prover: packet proofs") + } + + proofs := res.Msg.GetProofs() + if len(proofs) != len(packets) { + return nil, errors.Errorf( + "remote prover returned %d proofs for %d packets", len(proofs), len(packets), + ) + } + + return proofs, nil +} + +func proofKindToProto(kind v2.ProofKind) (proverv2.ProofKind, error) { + switch kind { + case v2.ProofKindPacketCommitment: + return proverv2.ProofKind_PROOF_KIND_PACKET_COMMITMENT, nil + case v2.ProofKindAcknowledgement: + return proverv2.ProofKind_PROOF_KIND_ACKNOWLEDGEMENT, nil + case v2.ProofKindReceiptAbsence: + return proverv2.ProofKind_PROOF_KIND_RECEIPT_ABSENCE, nil + default: + return proverv2.ProofKind_PROOF_KIND_UNSPECIFIED, + errors.Errorf("remote prover: proof kind %d has no wire representation", kind) + } +} + +func packetsToProto(packets []channeltypesv2.Packet) []*proverv2.Packet { + if len(packets) == 0 { + return nil + } + + out := make([]*proverv2.Packet, len(packets)) + for i, packet := range packets { + out[i] = &proverv2.Packet{ + Sequence: packet.Sequence, + SourceClient: packet.SourceClient, + DestinationClient: packet.DestinationClient, + TimeoutTimestamp: packet.TimeoutTimestamp, + Payloads: payloadsToProto(packet.Payloads), + } + } + + return out +} + +func payloadsToProto(payloads []channeltypesv2.Payload) []*proverv2.Payload { + if len(payloads) == 0 { + return nil + } + + out := make([]*proverv2.Payload, len(payloads)) + for i, payload := range payloads { + out[i] = &proverv2.Payload{ + SourcePort: payload.SourcePort, + DestinationPort: payload.DestinationPort, + Version: payload.Version, + Encoding: payload.Encoding, + Value: payload.Value, + } + } + + return out +} diff --git a/cli/internal/relay/prover/remote/remote_test.go b/cli/internal/relay/prover/remote/remote_test.go new file mode 100644 index 000000000..55304761f --- /dev/null +++ b/cli/internal/relay/prover/remote/remote_test.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 + +package remote + +import ( + "context" + "testing" + "time" + + "connectrpc.com/connect" + "github.com/stretchr/testify/require" + + channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" + proverv2 "github.com/cosmos/ibc/cli/api/v2/prover" + v2 "github.com/cosmos/ibc/cli/internal/types/v2" +) + +func TestProverRequestTimeout(t *testing.T) { + client := timeoutProverClient{t: t} + prover := &Prover{client: client, chainID: "chain-a", clientID: "client-0"} + ctx := context.Background() + + _, _, err := prover.LatestProvableHeight(ctx) + require.NoError(t, err) + + _, err = prover.StateProof(ctx, 1) + require.NoError(t, err) + + _, err = prover.PacketProofs(ctx, 1, v2.ProofKindPacketCommitment, []channeltypesv2.Packet{{Sequence: 1}}) + require.NoError(t, err) +} + +type timeoutProverClient struct { + t *testing.T +} + +func (c timeoutProverClient) LatestProvableHeight( + ctx context.Context, + _ *connect.Request[proverv2.LatestProvableHeightRequest], +) (*connect.Response[proverv2.LatestProvableHeightResponse], error) { + c.requireDeadline(ctx) + return connect.NewResponse(&proverv2.LatestProvableHeightResponse{}), nil +} + +func (c timeoutProverClient) StateProof( + ctx context.Context, + _ *connect.Request[proverv2.StateProofRequest], +) (*connect.Response[proverv2.StateProofResponse], error) { + c.requireDeadline(ctx) + return connect.NewResponse(&proverv2.StateProofResponse{}), nil +} + +func (c timeoutProverClient) PacketProofs( + ctx context.Context, + _ *connect.Request[proverv2.PacketProofsRequest], +) (*connect.Response[proverv2.PacketProofsResponse], error) { + c.requireDeadline(ctx) + return connect.NewResponse(&proverv2.PacketProofsResponse{Proofs: [][]byte{{0x1}}}), nil +} + +func (c timeoutProverClient) requireDeadline(ctx context.Context) { + c.t.Helper() + + deadline, ok := ctx.Deadline() + require.True(c.t, ok) + require.WithinDuration(c.t, time.Now().Add(requestTimeout), deadline, time.Second) +} diff --git a/cli/internal/relay/txbuilder/evm/evm.go b/cli/internal/relay/txbuilder/evm/evm.go index 1063c5930..345808f23 100644 --- a/cli/internal/relay/txbuilder/evm/evm.go +++ b/cli/internal/relay/txbuilder/evm/evm.go @@ -123,7 +123,7 @@ func height(h uint64) ics26router.IICS02ClientMsgsHeight { } // packUpdateClient packs a call to updateClient(clientId, updateMsg), where -// updateMsg is the already-encoded proof produced by proofgen.ProofGenerator.StateProof. +// updateMsg is the already-encoded proof produced by prover.Prover.StateProof. func packUpdateClient(clientID string, updateMsg []byte) ([]byte, error) { packed, err := calldata(func(opts *bind.TransactOpts) (*types.Transaction, error) { return router.UpdateClient(opts, clientID, updateMsg) diff --git a/cli/internal/tests/mocks/proofgen.go b/cli/internal/tests/mocks/prover.go similarity index 52% rename from cli/internal/tests/mocks/proofgen.go rename to cli/internal/tests/mocks/prover.go index 92a9ae01c..233e1e0fb 100644 --- a/cli/internal/tests/mocks/proofgen.go +++ b/cli/internal/tests/mocks/prover.go @@ -12,13 +12,13 @@ import ( "time" ) -// NewMockProofGenerator creates a new instance of MockProofGenerator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewMockProver creates a new instance of MockProver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewMockProofGenerator(t interface { +func NewMockProver(t interface { mock.TestingT Cleanup(func()) -}) *MockProofGenerator { - mock := &MockProofGenerator{} +}) *MockProver { + mock := &MockProver{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) @@ -26,21 +26,21 @@ func NewMockProofGenerator(t interface { return mock } -// MockProofGenerator is an autogenerated mock type for the ProofGenerator type -type MockProofGenerator struct { +// MockProver is an autogenerated mock type for the Prover type +type MockProver struct { mock.Mock } -type MockProofGenerator_Expecter struct { +type MockProver_Expecter struct { mock *mock.Mock } -func (_m *MockProofGenerator) EXPECT() *MockProofGenerator_Expecter { - return &MockProofGenerator_Expecter{mock: &_m.Mock} +func (_m *MockProver) EXPECT() *MockProver_Expecter { + return &MockProver_Expecter{mock: &_m.Mock} } -// LatestProvableHeight provides a mock function for the type MockProofGenerator -func (_mock *MockProofGenerator) LatestProvableHeight(ctx context.Context) (uint64, time.Time, error) { +// LatestProvableHeight provides a mock function for the type MockProver +func (_mock *MockProver) LatestProvableHeight(ctx context.Context) (uint64, time.Time, error) { ret := _mock.Called(ctx) if len(ret) == 0 { @@ -71,18 +71,18 @@ func (_mock *MockProofGenerator) LatestProvableHeight(ctx context.Context) (uint return r0, r1, r2 } -// MockProofGenerator_LatestProvableHeight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestProvableHeight' -type MockProofGenerator_LatestProvableHeight_Call struct { +// MockProver_LatestProvableHeight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestProvableHeight' +type MockProver_LatestProvableHeight_Call struct { *mock.Call } // LatestProvableHeight is a helper method to define mock.On call // - ctx context.Context -func (_e *MockProofGenerator_Expecter) LatestProvableHeight(ctx any) *MockProofGenerator_LatestProvableHeight_Call { - return &MockProofGenerator_LatestProvableHeight_Call{Call: _e.mock.On("LatestProvableHeight", ctx)} +func (_e *MockProver_Expecter) LatestProvableHeight(ctx any) *MockProver_LatestProvableHeight_Call { + return &MockProver_LatestProvableHeight_Call{Call: _e.mock.On("LatestProvableHeight", ctx)} } -func (_c *MockProofGenerator_LatestProvableHeight_Call) Run(run func(ctx context.Context)) *MockProofGenerator_LatestProvableHeight_Call { +func (_c *MockProver_LatestProvableHeight_Call) Run(run func(ctx context.Context)) *MockProver_LatestProvableHeight_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -95,18 +95,18 @@ func (_c *MockProofGenerator_LatestProvableHeight_Call) Run(run func(ctx context return _c } -func (_c *MockProofGenerator_LatestProvableHeight_Call) Return(v uint64, time1 time.Time, err error) *MockProofGenerator_LatestProvableHeight_Call { +func (_c *MockProver_LatestProvableHeight_Call) Return(v uint64, time1 time.Time, err error) *MockProver_LatestProvableHeight_Call { _c.Call.Return(v, time1, err) return _c } -func (_c *MockProofGenerator_LatestProvableHeight_Call) RunAndReturn(run func(ctx context.Context) (uint64, time.Time, error)) *MockProofGenerator_LatestProvableHeight_Call { +func (_c *MockProver_LatestProvableHeight_Call) RunAndReturn(run func(ctx context.Context) (uint64, time.Time, error)) *MockProver_LatestProvableHeight_Call { _c.Call.Return(run) return _c } -// PacketProofs provides a mock function for the type MockProofGenerator -func (_mock *MockProofGenerator) PacketProofs(ctx context.Context, height uint64, kind v2.ProofKind, packets []types.Packet) ([][]byte, error) { +// PacketProofs provides a mock function for the type MockProver +func (_mock *MockProver) PacketProofs(ctx context.Context, height uint64, kind v2.ProofKind, packets []types.Packet) ([][]byte, error) { ret := _mock.Called(ctx, height, kind, packets) if len(ret) == 0 { @@ -133,8 +133,8 @@ func (_mock *MockProofGenerator) PacketProofs(ctx context.Context, height uint64 return r0, r1 } -// MockProofGenerator_PacketProofs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PacketProofs' -type MockProofGenerator_PacketProofs_Call struct { +// MockProver_PacketProofs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PacketProofs' +type MockProver_PacketProofs_Call struct { *mock.Call } @@ -143,11 +143,11 @@ type MockProofGenerator_PacketProofs_Call struct { // - height uint64 // - kind v2.ProofKind // - packets []types.Packet -func (_e *MockProofGenerator_Expecter) PacketProofs(ctx any, height any, kind any, packets any) *MockProofGenerator_PacketProofs_Call { - return &MockProofGenerator_PacketProofs_Call{Call: _e.mock.On("PacketProofs", ctx, height, kind, packets)} +func (_e *MockProver_Expecter) PacketProofs(ctx any, height any, kind any, packets any) *MockProver_PacketProofs_Call { + return &MockProver_PacketProofs_Call{Call: _e.mock.On("PacketProofs", ctx, height, kind, packets)} } -func (_c *MockProofGenerator_PacketProofs_Call) Run(run func(ctx context.Context, height uint64, kind v2.ProofKind, packets []types.Packet)) *MockProofGenerator_PacketProofs_Call { +func (_c *MockProver_PacketProofs_Call) Run(run func(ctx context.Context, height uint64, kind v2.ProofKind, packets []types.Packet)) *MockProver_PacketProofs_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -175,18 +175,18 @@ func (_c *MockProofGenerator_PacketProofs_Call) Run(run func(ctx context.Context return _c } -func (_c *MockProofGenerator_PacketProofs_Call) Return(bytess [][]byte, err error) *MockProofGenerator_PacketProofs_Call { +func (_c *MockProver_PacketProofs_Call) Return(bytess [][]byte, err error) *MockProver_PacketProofs_Call { _c.Call.Return(bytess, err) return _c } -func (_c *MockProofGenerator_PacketProofs_Call) RunAndReturn(run func(ctx context.Context, height uint64, kind v2.ProofKind, packets []types.Packet) ([][]byte, error)) *MockProofGenerator_PacketProofs_Call { +func (_c *MockProver_PacketProofs_Call) RunAndReturn(run func(ctx context.Context, height uint64, kind v2.ProofKind, packets []types.Packet) ([][]byte, error)) *MockProver_PacketProofs_Call { _c.Call.Return(run) return _c } -// StateProof provides a mock function for the type MockProofGenerator -func (_mock *MockProofGenerator) StateProof(ctx context.Context, height uint64) ([]byte, error) { +// StateProof provides a mock function for the type MockProver +func (_mock *MockProver) StateProof(ctx context.Context, height uint64) ([]byte, error) { ret := _mock.Called(ctx, height) if len(ret) == 0 { @@ -213,19 +213,19 @@ func (_mock *MockProofGenerator) StateProof(ctx context.Context, height uint64) return r0, r1 } -// MockProofGenerator_StateProof_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StateProof' -type MockProofGenerator_StateProof_Call struct { +// MockProver_StateProof_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StateProof' +type MockProver_StateProof_Call struct { *mock.Call } // StateProof is a helper method to define mock.On call // - ctx context.Context // - height uint64 -func (_e *MockProofGenerator_Expecter) StateProof(ctx any, height any) *MockProofGenerator_StateProof_Call { - return &MockProofGenerator_StateProof_Call{Call: _e.mock.On("StateProof", ctx, height)} +func (_e *MockProver_Expecter) StateProof(ctx any, height any) *MockProver_StateProof_Call { + return &MockProver_StateProof_Call{Call: _e.mock.On("StateProof", ctx, height)} } -func (_c *MockProofGenerator_StateProof_Call) Run(run func(ctx context.Context, height uint64)) *MockProofGenerator_StateProof_Call { +func (_c *MockProver_StateProof_Call) Run(run func(ctx context.Context, height uint64)) *MockProver_StateProof_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -243,12 +243,12 @@ func (_c *MockProofGenerator_StateProof_Call) Run(run func(ctx context.Context, return _c } -func (_c *MockProofGenerator_StateProof_Call) Return(bytes []byte, err error) *MockProofGenerator_StateProof_Call { +func (_c *MockProver_StateProof_Call) Return(bytes []byte, err error) *MockProver_StateProof_Call { _c.Call.Return(bytes, err) return _c } -func (_c *MockProofGenerator_StateProof_Call) RunAndReturn(run func(ctx context.Context, height uint64) ([]byte, error)) *MockProofGenerator_StateProof_Call { +func (_c *MockProver_StateProof_Call) RunAndReturn(run func(ctx context.Context, height uint64) ([]byte, error)) *MockProver_StateProof_Call { _c.Call.Return(run) return _c } diff --git a/cli/internal/testutil/proverservice/cmd/main.go b/cli/internal/testutil/proverservice/cmd/main.go new file mode 100644 index 000000000..d08f617b1 --- /dev/null +++ b/cli/internal/testutil/proverservice/cmd/main.go @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Command testprover serves the attestation prover over ProverService so the +// wire contract can be exercised end to end. +// +// It exists for tests and demonstration: it is not part of the ibc CLI and is +// not built by the default build target. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/cosmos/ibc/cli/internal/testutil/proverservice" +) + +func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, "testprover:", err) + os.Exit(1) + } +} + +func run() error { + configPath := flag.String("config", "", "relayer config to build provers from") + listen := flag.String("listen", "127.0.0.1:0", "address to serve on") + flag.Parse() + + if *configPath == "" { + return errors.New("--config is required") + } + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + server, err := proverservice.NewAttestationServer(ctx, *configPath) + if err != nil { + return err + } + + listener, err := net.Listen("tcp", *listen) + if err != nil { + return err + } + + // The address is announced so a caller that asked for port 0 can find it. + fmt.Println("listening", listener.Addr().String()) + + served := make(chan error, 1) + go func() { served <- server.Serve(listener) }() + + select { + case err := <-served: + if errors.Is(err, http.ErrServerClosed) { + return nil + } + + return err + case <-ctx.Done(): + return server.Close() + } +} diff --git a/cli/internal/testutil/proverservice/server.go b/cli/internal/testutil/proverservice/server.go new file mode 100644 index 000000000..d053efda5 --- /dev/null +++ b/cli/internal/testutil/proverservice/server.go @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package proverservice serves the attestation prover over ProverService, so +// the contract can be exercised without a second light-client implementation. +// +// A real custom light client implements the proto contract in whatever language +// it likes; it does not import this package. +package proverservice + +import ( + "context" + "log/slog" + "net/http" + "time" + + "connectrpc.com/connect" + "github.com/pkg/errors" + + channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" + proverv2 "github.com/cosmos/ibc/cli/api/v2/prover" + "github.com/cosmos/ibc/cli/internal/chains" + "github.com/cosmos/ibc/cli/internal/config" + "github.com/cosmos/ibc/cli/internal/relay/prover" + "github.com/cosmos/ibc/cli/internal/relay/prover/attestation" + attestorservice "github.com/cosmos/ibc/cli/internal/service/attestor" + "github.com/cosmos/ibc/cli/internal/service/signer" + v2 "github.com/cosmos/ibc/cli/internal/types/v2" +) + +const readHeaderTimeout = 5 * time.Second + +var errInternal = connect.NewError(connect.CodeInternal, errors.New("internal server error")) + +// handler answers ProverService requests from a set of provers. A custom light +// client replaces it with its own implementation of the same contract. +type handler struct { + logger *slog.Logger + set *prover.Set +} + +var _ proverv2.ProverServiceHandler = (*handler)(nil) + +// newServer serves provers over the ProverService contract. +func newServer(set *prover.Set) *http.Server { + mux := http.NewServeMux() + path, handler := proverv2.NewProverServiceHandler(&handler{ + logger: slog.With("module", "prover"), + set: set, + }) + mux.Handle(path, handler) + + // gRPC needs HTTP/2, and the relayer dials over plain http. + protocols := new(http.Protocols) + protocols.SetHTTP1(true) + protocols.SetUnencryptedHTTP2(true) + + return &http.Server{ + Handler: mux, + Protocols: protocols, + ReadHeaderTimeout: readHeaderTimeout, + } +} + +// NewAttestationServer serves an attestation prover per client end in the +// config. +func NewAttestationServer(ctx context.Context, configPath string) (*http.Server, error) { + cfg, err := config.LoadFromFile(configPath, true, true) + if err != nil { + return nil, errors.Wrap(err, "load config") + } + + clients, err := chains.NewClientSetFromConfig(cfg) + if err != nil { + return nil, errors.Wrap(err, "build chain clients") + } + + signers, err := signer.NewSetFromConfig(ctx, cfg.Signers) + if err != nil { + return nil, errors.Wrap(err, "build signers") + } + + local, remoteAttestors, err := attestorservice.ResolveFromConfig(ctx, cfg.Attestors, clients, signers) + if err != nil { + return nil, errors.Wrap(err, "resolve attestors") + } + + attestors := make([]attestorservice.Attestor, 0, len(local)+len(remoteAttestors)) + attestors = append(attestors, local...) + attestors = append(attestors, remoteAttestors...) + + provers := make(map[string]prover.Prover) + + for _, conn := range cfg.Relayer.Connections { + for _, end := range []struct{ self, counterparty config.ClientEnd }{ + {conn.ClientA, conn.ClientB}, + {conn.ClientB, conn.ClientA}, + } { + gen, resolveErr := attestation.ResolveGenerator(ctx, end.self, end.counterparty, clients, attestors) + if resolveErr != nil { + return nil, errors.Wrapf(resolveErr, "connection %q", conn.Alias) + } + + provers[prover.Key(end.self.ChainID, end.self.ClientID)] = gen + } + } + + return newServer(prover.NewSet(provers)), nil +} + +// prover resolves the client a request names; an unknown one is a caller error. +func (h *handler) prover(client *proverv2.Client) (prover.Prover, error) { + chainID, clientID := client.GetChainId(), client.GetClientId() + + found, ok := h.set.Get(chainID, clientID) + if !ok { + return nil, connect.NewError( + connect.CodeNotFound, + errors.Errorf("no prover for client %q on chain %q", clientID, chainID), + ) + } + + return found, nil +} + +func (h *handler) LatestProvableHeight( + ctx context.Context, + req *connect.Request[proverv2.LatestProvableHeightRequest], +) (*connect.Response[proverv2.LatestProvableHeightResponse], error) { + target, err := h.prover(req.Msg.GetClient()) + if err != nil { + return nil, err + } + + height, timestamp, err := target.LatestProvableHeight(ctx) + if err != nil { + h.logger.Error("LatestProvableHeight", "err", err) + return nil, errInternal + } + + return connect.NewResponse(&proverv2.LatestProvableHeightResponse{ + Height: height, + //nolint:gosec // seconds since the epoch, matching the ibc packet timestamp + Timestamp: uint64(timestamp.Unix()), + }), nil +} + +func (h *handler) StateProof( + ctx context.Context, + req *connect.Request[proverv2.StateProofRequest], +) (*connect.Response[proverv2.StateProofResponse], error) { + target, err := h.prover(req.Msg.GetClient()) + if err != nil { + return nil, err + } + + proof, err := target.StateProof(ctx, req.Msg.GetHeight()) + if err != nil { + h.logger.Error("StateProof", "err", err) + return nil, errInternal + } + + return connect.NewResponse(&proverv2.StateProofResponse{Proof: proof}), nil +} + +func (h *handler) PacketProofs( + ctx context.Context, + req *connect.Request[proverv2.PacketProofsRequest], +) (*connect.Response[proverv2.PacketProofsResponse], error) { + target, err := h.prover(req.Msg.GetClient()) + if err != nil { + return nil, err + } + + kind, err := proofKindFromProto(req.Msg.GetKind()) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + + proofs, err := target.PacketProofs( + ctx, req.Msg.GetHeight(), kind, packetsFromProto(req.Msg.GetPackets()), + ) + if err != nil { + h.logger.Error("PacketProofs", "err", err) + return nil, errInternal + } + + return connect.NewResponse(&proverv2.PacketProofsResponse{Proofs: proofs}), nil +} + +func proofKindFromProto(kind proverv2.ProofKind) (v2.ProofKind, error) { + switch kind { + case proverv2.ProofKind_PROOF_KIND_PACKET_COMMITMENT: + return v2.ProofKindPacketCommitment, nil + case proverv2.ProofKind_PROOF_KIND_ACKNOWLEDGEMENT: + return v2.ProofKindAcknowledgement, nil + case proverv2.ProofKind_PROOF_KIND_RECEIPT_ABSENCE: + return v2.ProofKindReceiptAbsence, nil + default: + return v2.ProofKindUnknown, errors.Errorf("unknown proof kind %v", kind) + } +} + +// packetsFromProto converts without reshaping, so a proof covers exactly the +// packet that was sent. +func packetsFromProto(packets []*proverv2.Packet) []channeltypesv2.Packet { + if len(packets) == 0 { + return nil + } + + out := make([]channeltypesv2.Packet, len(packets)) + for i, packet := range packets { + out[i] = channeltypesv2.Packet{ + Sequence: packet.GetSequence(), + SourceClient: packet.GetSourceClient(), + DestinationClient: packet.GetDestinationClient(), + TimeoutTimestamp: packet.GetTimeoutTimestamp(), + Payloads: payloadsFromProto(packet.GetPayloads()), + } + } + + return out +} + +func payloadsFromProto(payloads []*proverv2.Payload) []channeltypesv2.Payload { + if len(payloads) == 0 { + return nil + } + + out := make([]channeltypesv2.Payload, len(payloads)) + for i, payload := range payloads { + out[i] = channeltypesv2.Payload{ + SourcePort: payload.GetSourcePort(), + DestinationPort: payload.GetDestinationPort(), + Version: payload.GetVersion(), + Encoding: payload.GetEncoding(), + Value: payload.GetValue(), + } + } + + return out +} diff --git a/cli/internal/testutil/proverservice/server_test.go b/cli/internal/testutil/proverservice/server_test.go new file mode 100644 index 000000000..4e0871704 --- /dev/null +++ b/cli/internal/testutil/proverservice/server_test.go @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 + +package proverservice + +import ( + "context" + "net/http/httptest" + "testing" + "time" + + "connectrpc.com/connect" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + channeltypesv2 "github.com/cosmos/ibc-go/v11/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc/cli/internal/relay/prover" + "github.com/cosmos/ibc/cli/internal/relay/prover/remote" + v2 "github.com/cosmos/ibc/cli/internal/types/v2" +) + +// stubProver records what it was asked, so the far side of the wire can assert it. +type stubProver struct { + height uint64 + timestamp time.Time + stateProof []byte + proofs [][]byte + + gotHeight uint64 + gotKind v2.ProofKind + gotPackets []channeltypesv2.Packet +} + +func (s *stubProver) LatestProvableHeight(context.Context) (uint64, time.Time, error) { + return s.height, s.timestamp, nil +} + +func (s *stubProver) StateProof(_ context.Context, height uint64) ([]byte, error) { + s.gotHeight = height + return s.stateProof, nil +} + +func (s *stubProver) PacketProofs( + _ context.Context, + height uint64, + kind v2.ProofKind, + packets []channeltypesv2.Packet, +) ([][]byte, error) { + s.gotHeight, s.gotKind, s.gotPackets = height, kind, packets + return s.proofs, nil +} + +func newClient(t *testing.T, set *prover.Set, chainID, clientID string) *remote.Prover { + t.Helper() + + server := httptest.NewUnstartedServer(newServer(set).Handler) + server.EnableHTTP2 = true + server.StartTLS() + t.Cleanup(server.Close) + + return remote.New(server.Client(), server.URL, chainID, clientID) +} + +// A custom light client is reached only through this contract, so the round trip +// must preserve every value the internal interface carries. +func TestProverServiceRoundTrip(t *testing.T) { + ctx := context.Background() + stub := &stubProver{ + height: 4321, + timestamp: time.Unix(1700000000, 0).UTC(), + stateProof: []byte("state-proof"), + proofs: [][]byte{[]byte("proof-a"), []byte("proof-b")}, + } + set := prover.NewSet(map[string]prover.Prover{prover.Key("chain-a", "client-0"): stub}) + client := newClient(t, set, "chain-a", "client-0") + + t.Run("latest provable height", func(t *testing.T) { + height, timestamp, err := client.LatestProvableHeight(ctx) + require.NoError(t, err) + require.Equal(t, uint64(4321), height) + require.Equal(t, stub.timestamp, timestamp) + }) + + t.Run("state proof", func(t *testing.T) { + proof, err := client.StateProof(ctx, 99) + require.NoError(t, err) + require.Equal(t, []byte("state-proof"), proof) + require.Equal(t, uint64(99), stub.gotHeight) + }) + + t.Run("packet proofs", func(t *testing.T) { + packets := []channeltypesv2.Packet{ + { + Sequence: 7, + SourceClient: "client-0", + DestinationClient: "client-1", + TimeoutTimestamp: 1800000000, + Payloads: []channeltypesv2.Payload{{ + SourcePort: "transfer", + DestinationPort: "transfer", + Version: "ics20-2", + Encoding: "application/x-solidity-abi", + Value: []byte("payload"), + }}, + }, + {Sequence: 8, SourceClient: "client-0", DestinationClient: "client-1"}, + } + + proofs, err := client.PacketProofs(ctx, 4321, v2.ProofKindReceiptAbsence, packets) + require.NoError(t, err) + require.Equal(t, [][]byte{[]byte("proof-a"), []byte("proof-b")}, proofs) + + // A dropped field proves a different packet than the one sent. + require.Equal(t, packets, stub.gotPackets) + require.Equal(t, v2.ProofKindReceiptAbsence, stub.gotKind) + require.Equal(t, uint64(4321), stub.gotHeight) + }) +} + +func TestProverServiceUnknownClient(t *testing.T) { + client := newClient(t, prover.NewSet(nil), "chain-z", "client-9") + + _, _, err := client.LatestProvableHeight(context.Background()) + require.Error(t, err) + require.Equal(t, connect.CodeNotFound, connect.CodeOf(errors.Cause(err))) +} + +// A short response would attach one packet's proof to another packet. +func TestProverServiceRejectsMismatchedProofCount(t *testing.T) { + stub := &stubProver{proofs: [][]byte{[]byte("only-one")}} + set := prover.NewSet(map[string]prover.Prover{prover.Key("chain-a", "client-0"): stub}) + client := newClient(t, set, "chain-a", "client-0") + + _, err := client.PacketProofs(context.Background(), 1, v2.ProofKindPacketCommitment, + []channeltypesv2.Packet{{Sequence: 1}, {Sequence: 2}}) + require.ErrorContains(t, err, "returned 1 proofs for 2 packets") +} diff --git a/docs/6-ibc-cli/1-overview.md b/docs/6-ibc-cli/1-overview.md index e6559cd24..b16c7e4c8 100644 --- a/docs/6-ibc-cli/1-overview.md +++ b/docs/6-ibc-cli/1-overview.md @@ -56,7 +56,7 @@ flowchart TB API -->|"records each packet"| STORE[("store")] STORE -.->|"picked up for delivery"| DELIV["delivery"] DELIV -->|"re-reads the send transaction"| SRC["source chain"] - DELIV --> PG["proof generator"] + DELIV --> PG["prover"] PG -->|"in process or over gRPC"| ATT["attestors"] DELIV -->|"one transaction per batch"| DST["destination chain"] DELIV -.->|"records progress"| STORE @@ -73,7 +73,7 @@ Packets heading the same way are batched and delivered together, rather than one ### Proof generation and transaction building -Before it can deliver anything, the relayer needs proof of the packets for the light client. It runs one proof generator for each light client it submits to. Currently the only supported light client type is the attestation light client, with more light client types planned. +Before it can deliver anything, the relayer needs proof of the packets for the light client. It runs one prover for each light client it submits to. Currently the only supported light client type is the attestation light client, with more light client types planned. The generator asks the client's attestors to attest to the chain's state at a height. It checks the signatures, and once enough attestors have signed the same attestation to meet the client's threshold, it packages that attestation and its signatures together. That package is the proof. diff --git a/docs/6-ibc-cli/5-configuration.md b/docs/6-ibc-cli/5-configuration.md index 95fa58125..cb0740776 100644 --- a/docs/6-ibc-cli/5-configuration.md +++ b/docs/6-ibc-cli/5-configuration.md @@ -176,10 +176,11 @@ The deployer must be a local signer, because deployment requires direct access t | `connections[].clientA.chainId, connections[].clientB.chainId` | `string` | **required** | The chain this end's client lives on. | | `connections[].clientA.signer, connections[].clientB.signer` | `string` | **required** | `signers` alias that submits relay transactions on this chain. | | `connections[].clientA.clientId, connections[].clientB.clientId` | `string` | **required** | The light client's id on this chain. | -| `connections[].clientA.type, connections[].clientB.type` | `attestation` | **required** | Light client type. | +| `connections[].clientA.type, connections[].clientB.type` | `attestation` \| `remote` | **required** | Light client type. | +| `connections[].clientA.params, connections[].clientB.params` | `yaml.RawMessage` | optional | This client type's settings. | | `connections[].clientA.autoRelay.enabled, connections[].clientB.autoRelay.enabled` | `bool` | optional | Whether the relayer carries packets leaving this end without being asked. | - + @@ -197,9 +198,9 @@ The relayer uses these defaults unless you override them. | Key | Type | Default or required | Description | |---|---|---|---| -| `dispatchPollInterval` | `duration` | `5s` | How often the dispatcher polls the store for unfinished packets. | +| `dispatchPollInterval` | `duration` | `1s` | How often the dispatcher polls the store for unfinished packets. | - + @@ -210,11 +211,11 @@ The relayer uses these defaults unless you override them. | `chainOverrides[].chainId` | `string` | **required** | The chain these settings apply to. | | `chainOverrides[].txSubmissionDelay` | `duration` | `2s` | Minimum delay between two transaction submissions on the chain. | | `chainOverrides[].packetBatchSize` | `int` | `50` | How many packets the relayer puts in one transaction. | -| `chainOverrides[].packetBatchTimeout` | `duration` | `10s` (receive and acknowledge), `1m` (timeout) | How long the relayer waits to fill a batch before submitting it. | +| `chainOverrides[].packetBatchTimeout` | `duration` | `3s` (receive and acknowledge), `1m` (timeout) | How long the relayer waits to fill a batch before submitting it. | | `chainOverrides[].evm.gasFeeCapMultiplier` | `float64` | optional | Multiplies the fee cap the node suggests. | | `chainOverrides[].evm.gasTipCapMultiplier` | `float64` | optional | Multiplies the tip cap the node suggests. | - + diff --git a/docs/6-ibc-cli/7-api.md b/docs/6-ibc-cli/7-api.md index 6bae1623c..f67a1aeac 100644 --- a/docs/6-ibc-cli/7-api.md +++ b/docs/6-ibc-cli/7-api.md @@ -270,7 +270,7 @@ Both attestation calls return this shape. -`attested_data` is what was signed. A light client accepts the attestation when its threshold of attestors or more sign the same data. +`attested_data` is what was signed. A light client accepts the attestation when its threshold of attestors or more sign the same data. ### `StateAttestation` @@ -449,6 +449,176 @@ grpcurl -plaintext -d '{"attestor":"attestor-41002"}' \ } ``` +## Prover service + +`ibc.v2.prover.ProverService`. A relayer runs one prover per light client it +submits to. Pointing a client end at this service replaces the built-in prover +with a remote one, so a light client the CLI does not implement can be supported +by serving these three calls. Every request names the client it is scoped to, so +one service can serve many clients across many chains. + +### `Client` + +Every request carries one, identifying the light client the call is scoped to. + + + +| Field | Type | Description | +|---|---|---| +| `chain_id` | `string` | The chain the light client lives on. | +| `client_id` | `string` | The light client's id on that chain. | + + + + + +### `LatestProvableHeight` + + + +Returns the highest height a subsequent StateProof and PacketProofs call sharing that height can currently succeed at, with that height's counterparty-chain timestamp. + + + + + + + +| Field | Type | Description | +|---|---|---| +| `client` | `Client` | The light client this call is scoped to. | + + + + + + + +| Field | Type | Description | +|---|---|---| +| `height` | `uint64` | The highest counterparty height currently provable. | +| `timestamp` | `uint64` | Counterparty-chain timestamp of `height`, in seconds. | + + + + + +The relayer calls this first and proves at the height it returns, so a prover +paces the relayer by holding the height back until it can prove at it. + +### `StateProof` + + + +Proves the light client's counterparty state at a height. + + + + + + + +| Field | Type | Description | +|---|---|---| +| `client` | `Client` | The light client this call is scoped to. | +| `height` | `uint64` | The counterparty height to prove at. | + + + + + + + +| Field | Type | Description | +|---|---|---| +| `proof` | `bytes` | The proof, opaque to the relayer and passed to the light client unchanged. | + + + + + +`proof` is opaque to the relayer, which passes it to the light client unchanged. + +### `PacketProofs` + + + +Proves each packet's membership or non-membership at a height, one proof per packet with indices aligned to the request. + + + + + + + +| Field | Type | Description | +|---|---|---| +| `client` | `Client` | The light client this call is scoped to. | +| `height` | `uint64` | The counterparty height to prove at. | +| `kind` | `ProofKind` | Which commitment to prove for every packet in this request. | +| `packets` | `Packet[]` | The packets to prove, all under the same `kind` and `height`. | + + + + + + + +| Field | Type | Description | +|---|---|---| +| `proofs` | `bytes[]` | One proof per requested packet, in request order. | + + + + + +`proofs` is one proof per requested packet, in request order, so a response of a +different length than the request is an error. + + + +| Value | Meaning | +|---|---| +| `PROOF_KIND_PACKET_COMMITMENT` | The packet was sent. Proven to receive it. | +| `PROOF_KIND_ACKNOWLEDGEMENT` | The packet was received and acknowledged. Proven to acknowledge it. | +| `PROOF_KIND_RECEIPT_ABSENCE` | The packet was never received. Proven to time it out. | + + + + + +### `Packet` + +The packet a proof is requested for. + + + +| Field | Type | Description | +|---|---|---| +| `sequence` | `uint64` | The packet's sequence on the source client. | +| `source_client` | `string` | The client the packet was sent from. | +| `destination_client` | `string` | The client the packet was sent to. | +| `timeout_timestamp` | `uint64` | When the packet stops being receivable, in seconds. | +| `payloads` | `Payload[]` | The packet's application payloads. | + + + + + + + +| Field | Type | Description | +|---|---|---| +| `source_port` | `string` | The application that sent the payload. | +| `destination_port` | `string` | The application that receives the payload. | +| `version` | `string` | The application version the payload is encoded for. | +| `encoding` | `string` | How `value` is encoded. | +| `value` | `bytes` | The application data. | + + + + + ## Next steps - [Configuration](5-configuration.md) for `server.listenAddr` and the `attestors` block these calls read. diff --git a/docs/6-ibc-cli/tools/refgen.py b/docs/6-ibc-cli/tools/refgen.py index 45668577a..670f53bef 100644 --- a/docs/6-ibc-cli/tools/refgen.py +++ b/docs/6-ibc-cli/tools/refgen.py @@ -414,7 +414,8 @@ def _proto_short(path): # The proto files sort alphabetically and the service type is RelayerApiService, # so both the order and the display name are a human's call. A service missing # from here raises, and an entry naming a service that is gone raises too. -SERVICES = [("relayer", "Relayer service"), ("attestor", "Attestation service")] +SERVICES = [("relayer", "Relayer service"), ("attestor", "Attestation service"), + ("prover", "Prover service")] # Descriptions for fields the protos leave undocumented. Nearly all of these # were prose on the page already, moved into the cell they belong in. Values diff --git a/docs/6-ibc-cli/tools/test-refgen-e2e.py b/docs/6-ibc-cli/tools/test-refgen-e2e.py index 7a2b1fdb2..e650b511d 100644 --- a/docs/6-ibc-cli/tools/test-refgen-e2e.py +++ b/docs/6-ibc-cli/tools/test-refgen-e2e.py @@ -233,7 +233,7 @@ def _(): def _(): with Sandbox() as box: box.edit("cli/internal/relay/dispatch/dispatcher.go", - "const DefaultPollInterval = 5 * time.Second", + "const DefaultPollInterval = 1 * time.Second", "const DefaultPollInterval = 9 * time.Second") red_then_healed(box, "config", "`9s`") diff --git a/docs/6-ibc-cli/tools/test-refgen.py b/docs/6-ibc-cli/tools/test-refgen.py index aad8d8fdc..2ad62c2e0 100644 --- a/docs/6-ibc-cli/tools/test-refgen.py +++ b/docs/6-ibc-cli/tools/test-refgen.py @@ -336,7 +336,7 @@ def _(): def _(): b = refgen.gen_config() assert "`0.0.0.0:3000`" in b["config:server"] - assert "`5s`" in b["config:relayer"] # dispatch.DefaultPollInterval + assert "`1s`" in b["config:relayer"] # dispatch.DefaultPollInterval assert "`50`" in b["config:relayer:chainOverrides"] # pipeline.DefaultBatchSize diff --git a/e2e/Makefile b/e2e/Makefile index 79bc68c17..f6c10ad49 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -82,7 +82,7 @@ run-all-checks: doctor doctor-tools ## Run "all-in-one" code validation step. # Hidden commands _build-cli: - $(MAKE) -C ../cli build + $(MAKE) -C ../cli build build-test-bin .PHONY: help doctor doctor-tools lint lint-fix .PHONY: test-harness test-apps test diff --git a/e2e/go.mod b/e2e/go.mod index fb097953d..96a55ffd2 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -17,6 +17,7 @@ require ( ) require ( + connectrpc.com/grpcreflect v1.3.0 // indirect cosmossdk.io/api v1.0.0 // indirect cosmossdk.io/collections v1.4.0 // indirect cosmossdk.io/core v1.1.0 // indirect @@ -41,6 +42,7 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cockroachdb/crlib v0.0.0-20241112164430-1264a2edc35b // indirect github.com/cockroachdb/errors v1.13.0 // indirect @@ -69,6 +71,7 @@ require ( github.com/danieljoos/wincred v1.2.3 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/dchest/siphash v1.2.3 // indirect + github.com/deliveryhero/pipeline/v2 v2.2.0 // indirect github.com/desertbit/timer v1.0.1 // indirect github.com/dgraph-io/badger/v4 v4.9.1 // indirect github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect @@ -82,10 +85,12 @@ require ( github.com/fjl/jsonw v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/getsentry/sentry-go v0.46.0 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -122,6 +127,10 @@ require ( github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/go-cid v0.5.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.10.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect @@ -164,6 +173,7 @@ require ( github.com/multiformats/go-multistream v0.6.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20251114093237-2ab5a27a1729 // indirect github.com/oklog/run v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -198,9 +208,11 @@ require ( github.com/quic-go/quic-go v0.60.0 // indirect github.com/quic-go/webtransport-go v0.11.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/zerolog v1.35.0 // indirect + github.com/rubenv/sql-migrate v1.8.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.9 // indirect @@ -211,6 +223,7 @@ require ( github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect + github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect @@ -271,6 +284,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gotest.tools/v3 v3.5.2 // indirect lukechampine.com/blake3 v1.4.1 // indirect + modernc.org/libc v1.73.4 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.53.0 // indirect nhooyr.io/websocket v1.8.17 // indirect pgregory.net/rapid v1.3.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect @@ -348,6 +365,9 @@ require ( ) replace ( + // Matches link: the prover service pulls in link's signer package, which + // needs this cometbft. + github.com/cometbft/cometbft => github.com/cometbft/cometbft v0.39.0-rc1.0.20260615134937-9ea34470f336 github.com/cosmos/ibc/gen/go/solidity-abi => ../gen/go/solidity-abi github.com/cosmos/ibc/cli => ../cli ) diff --git a/e2e/go.sum b/e2e/go.sum index f0410e926..6abf34535 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -19,6 +19,8 @@ cloud.google.com/go/storage v1.62.1 h1:Os0G3XbUbjZumkpDUf2Y0rLoXJTCF1kU2kWUujKYX cloud.google.com/go/storage v1.62.1/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA= connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= +connectrpc.com/grpcreflect v1.3.0 h1:Y4V+ACf8/vOb1XOc251Qun7jMB75gCUNw6llvB9csXc= +connectrpc.com/grpcreflect v1.3.0/go.mod h1:nfloOtCS8VUQOQ1+GTdFzVg2CJo4ZGaat8JIovCtDYs= cosmossdk.io/api v1.0.0 h1:qTV8OPVEwcBPwp2b9p4Qy4noZyihJ+sOMaWL/VT+RCc= cosmossdk.io/api v1.0.0/go.mod h1:fKRljeYk+04p4T8Shdyv+uH2fSVyzHoWrvDAs/7OxfI= cosmossdk.io/collections v1.4.0 h1:b373bkxCxKiRbapxZ42TRmcKJEnBVBebdQVk9I5IkkE= @@ -153,8 +155,8 @@ github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoG github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= -github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= -github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btcd/btcutil v1.2.0 h1:p3+S2g3Q+7G5NOh4Ji+2UrBOrg5Z0Q4ykzShWG1Dhgs= +github.com/btcsuite/btcd/btcutil v1.2.0/go.mod h1:/Taflm113pYjUpbWKKQEfa6XOtI/+WS8awxeMZpY75k= github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= @@ -182,6 +184,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -217,8 +221,8 @@ github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b/go.mod h1:yBRu/c github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g= github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/cometbft/cometbft v0.39.3 h1:UegHXskZNomsijmm29nL5NkeXtnzkme6fg+q1hPQnEI= -github.com/cometbft/cometbft v0.39.3/go.mod h1:PmNfvtw256BC41ad0FABts236CSZnvZ0kjPOciBwTdM= +github.com/cometbft/cometbft v0.39.0-rc1.0.20260615134937-9ea34470f336 h1:Nyc8hg/+dXwmSrilPEmGJS/qMgybivfkGa8JMdV1vkQ= +github.com/cometbft/cometbft v0.39.0-rc1.0.20260615134937-9ea34470f336/go.mod h1:JxBvpWV9MU+ZqHdZaZmuFfrqgqyMGV8AYJWhJ/nPPh4= github.com/cometbft/cometbft-db v1.0.4 h1:cezb8yx/ZWcF124wqUtAFjAuDksS1y1yXedvtprUFxs= github.com/cometbft/cometbft-db v1.0.4/go.mod h1:M+BtHAGU2XLrpUxo3Nn1nOCcnVCiLM9yx5OuT0u5SCA= github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI= @@ -296,6 +300,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3h github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deliveryhero/pipeline/v2 v2.2.0 h1:l9F+e4Q3qMu0zw7fi0JVg2FilgnGyohnwJ5d1oBqoYs= +github.com/deliveryhero/pipeline/v2 v2.2.0/go.mod h1:GghgCAlOoG8IdwybJpe2E3LBTuR+o5wgTdm0kPv3GPU= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo= github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE= @@ -381,6 +387,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -409,6 +417,8 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= @@ -416,6 +426,8 @@ github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= @@ -482,6 +494,8 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= @@ -601,6 +615,14 @@ github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7 github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= @@ -699,6 +721,8 @@ github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= +github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= @@ -795,6 +819,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -914,6 +940,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -957,6 +985,8 @@ github.com/quic-go/webtransport-go v0.11.1/go.mod h1:SHgEzUFVyj+9WUSuGB1P6Zd351P github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -970,6 +1000,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0= +github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1051,6 +1083,8 @@ github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/testcontainers/testcontainers-go v0.44.0 h1:/Fwh6HY1mIikhnm9e7HwoxGycx0lzRAE0f5VQpjFxzI= github.com/testcontainers/testcontainers-go v0.44.0/go.mod h1:IcnwQrYTO86xHXu5bvMaBH7ATlbS3Qn1M1QWW3c66rE= +github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0 h1:8fdv/9y3JMxjQ+ULAcOG8RtgeNu5t9XF9LolSXDuTwM= +github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0/go.mod h1:CFr2LncGYokw+OKjXcr8ARCKG1SaC2UEnGxFBovE86g= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= @@ -1497,6 +1531,34 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= +modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= +modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= +modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= +modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= +modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= +modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y= nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/e2e/internal/e2etest/traffic_prover.go b/e2e/internal/e2etest/traffic_prover.go new file mode 100644 index 000000000..5c3c81241 --- /dev/null +++ b/e2e/internal/e2etest/traffic_prover.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 + +package e2etest + +import ( + "path/filepath" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc/e2e/internal/harness/environment" + "github.com/cosmos/ibc/e2e/internal/harness/ibccli" +) + +func buildProverConfig( + t testing.TB, + env *environment.Environment, + signerKeyPath string, +) ibccli.RelayerConfig { + t.Helper() + + config := ibccli.RelayerConfig{ + DBPath: filepath.Join(t.TempDir(), "prover.db"), + SignerAlias: relayerSignerAlias, + SignerKeyFile: signerKeyPath, + FinalityOffset: ibccli.HarnessFinalityOffset, + } + + for _, id := range env.Chains() { + chain, err := env.Chain(id) + require.NoError(t, err, "e2etest: resolve Chain %q", id) + + instance, err := env.IBCInstanceForChain(id) + require.NoError(t, err, "e2etest: resolve IBC instance for Chain %q", id) + + config.Chains = append(config.Chains, ibccli.RelayerChain{ + ChainID: chainEVMID(t, env, id), + RPC: chain.RPCURL(), + ICS26Router: string(instance.Locator()), + }) + } + + for _, id := range env.Attestors() { + attestor, err := env.Attestor(id) + require.NoError(t, err, "e2etest: resolve Attestor %q", id) + + config.Attestors = append(config.Attestors, ibccli.RelayerAttestor{ + Name: string(attestor.ID()), Type: ibccli.RelayerAttestorRemote, GRPC: attestor.Endpoint(), + }) + } + + for _, id := range env.Connections() { + connection, err := env.Connection(id) + require.NoError(t, err, "e2etest: resolve Connection %q", id) + + config.Connections = append(config.Connections, ibccli.RelayerConnection{ + ChainA: chainEVMID(t, env, connection.A().IBCInstance().Chain().ID()), + ClientA: connection.A().ID(), + ChainB: chainEVMID(t, env, connection.B().IBCInstance().Chain().ID()), + ClientB: connection.B().ID(), + }) + } + + return config +} + +// StartProver writes the prover's config and runs it. +func StartProver(t testing.TB, env *environment.Environment, signer Signer) *ibccli.Prover { + t.Helper() + + dir := t.TempDir() + signerKeyPath := filepath.Join(dir, "prover-signer.json") + require.NoError(t, signer.storeKey(signerKeyPath), "e2etest: store prover signer key") + + config := buildProverConfig(t, env, signerKeyPath) + configPath := filepath.Join(dir, "prover.config.yaml") + require.NoError(t, ibccli.WriteRelayerConfig(configPath, config), "e2etest: write prover config") + + prover, err := ibccli.StartProver(configPath) + require.NoError(t, err, "e2etest: start prover service") + + t.Cleanup(func() { assert.NoError(t, prover.Stop(), "e2etest: stop prover service") }) + + return prover +} + +func chainEVMID(t testing.TB, env *environment.Environment, id environment.ChainID) string { + t.Helper() + + chain, err := env.Chain(id) + require.NoError(t, err, "e2etest: resolve Chain %q", id) + + return strconv.FormatUint(chain.EVMChainID(), 10) +} diff --git a/e2e/internal/harness/ibccli/prover.go b/e2e/internal/harness/ibccli/prover.go new file mode 100644 index 000000000..2d8e7d05f --- /dev/null +++ b/e2e/internal/harness/ibccli/prover.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 + +package ibccli + +import ( + "bufio" + "fmt" + "io" + "net/netip" + "os" + "os/exec" + "strings" + "syscall" + "time" +) + +const ( + proverBinEnv = "IBC_TEST_PROVER_BIN" + proverReadyWait = 30 * time.Second + proverReadyToken = "listening" +) + +// Prover is a running prover service. +type Prover struct { + cmd *exec.Cmd + address string +} + +// Address is where the prover is serving, known only once it has bound. +func (p *Prover) Address() string { + if p == nil { + return "" + } + + return p.address +} + +// StartProver runs the test prover on an ephemeral port, waiting for it to +// announce the one it got +func StartProver(configPath string) (*Prover, error) { + cmd := exec.Command(resolvedProverBin(), "--config", configPath, "--listen", loopbackAnyPort) + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("ibc prover: stdout pipe: %w", err) + } + + if err = cmd.Start(); err != nil { + return nil, fmt.Errorf("ibc prover: start: %w", err) + } + + prover := &Prover{cmd: cmd} + + address, err := awaitProverAddress(stdout) + if err != nil { + _ = prover.Stop() + + return nil, err + } + + prover.address = address + + return prover, nil +} + +// Stop signals the prover's process group and waits for it to exit. +func (p *Prover) Stop() error { + if p == nil || p.cmd == nil || p.cmd.Process == nil { + return nil + } + + _ = syscall.Kill(-p.cmd.Process.Pid, syscall.SIGTERM) + _ = p.cmd.Wait() + + return nil +} + +// awaitProverAddress reads the address announced on the prover's first stdout line. +func awaitProverAddress(stdout io.ReadCloser) (string, error) { + type result struct { + address string + err error + } + + announced := make(chan result, 1) + + go func() { + scanner := bufio.NewScanner(stdout) + if !scanner.Scan() { + announced <- result{err: fmt.Errorf("ibc prover: no readiness line: %w", scanner.Err())} + + return + } + + fields := strings.Fields(scanner.Text()) + if len(fields) != 2 || fields[0] != proverReadyToken { + announced <- result{err: fmt.Errorf("ibc prover: unexpected readiness line %q", scanner.Text())} + + return + } + + announced <- result{address: fields[1]} + }() + + select { + case got := <-announced: + if got.err != nil { + return "", got.err + } + + parsed, err := netip.ParseAddrPort(got.address) + if err != nil || !parsed.Addr().IsLoopback() || parsed.Port() == 0 { + return "", fmt.Errorf("ibc prover: announced invalid address %q", got.address) + } + + return got.address, nil + case <-time.After(proverReadyWait): + return "", fmt.Errorf("ibc prover: did not announce an address within %s", proverReadyWait) + } +} + +func resolvedProverBin() string { + if v := os.Getenv(proverBinEnv); v != "" { + return v + } + + return defaultBinPath("testprover") +} diff --git a/e2e/internal/harness/ibccli/relayer_config.go b/e2e/internal/harness/ibccli/relayer_config.go index b5bb075a5..8c2617101 100644 --- a/e2e/internal/harness/ibccli/relayer_config.go +++ b/e2e/internal/harness/ibccli/relayer_config.go @@ -56,6 +56,10 @@ type RelayerConnection struct { ClientB string AutoRelayA bool AutoRelayB bool + + // ProverURL points both client ends at a ProverService. Empty keeps + // attestation. + ProverURL string } // RelayerAttestor describes one candidate attestor: a local entry runs in @@ -160,20 +164,31 @@ func buildRelayerFileConfig(cfg RelayerConfig) (fileConfig, error) { } for _, connection := range cfg.Connections { + clientType := relayerClientAttestation + + var params map[string]any + + if connection.ProverURL != "" { + clientType = relayerClientRemote + params = map[string]any{"url": connection.ProverURL} + } + file.Relayer.Connections = append(file.Relayer.Connections, connectionFileConfig{ Alias: connection.ClientA + "-" + connection.ClientB, ClientA: clientEndFileConfig{ ChainID: connection.ChainA, Signer: cfg.SignerAlias, ClientID: connection.ClientA, - Type: "attestation", + Type: clientType, + Params: params, AutoRelay: autoRelay(connection.AutoRelayA), }, ClientB: clientEndFileConfig{ ChainID: connection.ChainB, Signer: cfg.SignerAlias, ClientID: connection.ClientB, - Type: "attestation", + Type: clientType, + Params: params, AutoRelay: autoRelay(connection.AutoRelayB), }, }) @@ -234,7 +249,10 @@ func localAttestorName(chainID string) string { } const ( - RelayerSignerLocal = "local" + RelayerSignerLocal = "local" + relayerClientAttestation = "attestation" + relayerClientRemote = "remote" + RelayerSignerRemote = "remote" RelayerAttestorLocal = "local" RelayerAttestorRemote = "remote" @@ -264,6 +282,7 @@ type clientEndFileConfig struct { Signer string `yaml:"signer"` ClientID string `yaml:"clientId"` Type string `yaml:"type"` + Params map[string]any `yaml:"params,omitempty"` AutoRelay *autoRelayFileConfig `yaml:"autoRelay,omitempty"` } diff --git a/e2e/remote_prover_test.go b/e2e/remote_prover_test.go new file mode 100644 index 000000000..756be61a4 --- /dev/null +++ b/e2e/remote_prover_test.go @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + relayerv2 "github.com/cosmos/ibc/cli/api/v2/relayer" + "github.com/cosmos/ibc/e2e/internal/e2etest" + "github.com/cosmos/ibc/e2e/internal/harness/environment" + "github.com/cosmos/ibc/e2e/internal/harness/ibccli" +) + +type remoteProverEnv struct { + env *environment.Environment + driver *ibccli.Driver + deployment *e2etest.Deployment + route e2etest.Route + sender e2etest.Signer +} + +func startRemoteProverEnv(t *testing.T, requirements e2etest.EVMRequirements) remoteProverEnv { + t.Helper() + + spec, runtime := attestedMesh(e2etest.EVMChains(t, requirements, e2etest.ChainA, e2etest.ChainB)) + env := e2etest.Start(t, spec, runtime) + sender := e2etest.NewSigner(t) + relayerSigner := e2etest.NewSigner(t) + route := e2etest.AtoB(e2etest.ChainA, e2etest.ChainB) + + prover := e2etest.StartProver(t, env, relayerSigner) + + driver, deployment := e2etest.DeployWithRelayerConfig(t, env, sender, relayerSigner, + func(cfg *ibccli.RelayerConfig) { + for i := range cfg.Connections { + cfg.Connections[i].ProverURL = "http://" + prover.Address() + } + }, route) + + return remoteProverEnv{env: env, driver: driver, deployment: deployment, route: route, sender: sender} +} + +// Relays a real packet with every proof fetched over gRPC from a service the +// relayer does not host. +func TestRemoteProver_RelaysPacket(t *testing.T) { + t.Parallel() + + setup := startRemoteProverEnv(t, e2etest.EVMRequirements{}) + + transferApp := e2etest.NewTransfer(t, setup.env, setup.deployment, setup.sender, setup.route) + relayer := e2etest.StartRelayer(t, setup.driver, setup.env) + ctx := t.Context() + + amount := new(big.Int).Mul(big.NewInt(500_000), big.NewInt(1_000_000_000_000_000_000)) + transfer, err := transferApp.Send(ctx, e2etest.TransferRequest{Amount: amount}) + require.NoError(t, err) + require.NoError(t, transfer.VerifyEscrowed(ctx)) + + status, err := e2etest.AwaitState(ctx, relayer, transfer.PacketTx(), + relayerv2.PacketState_PACKET_STATE_SUCCEEDED) + require.NoError(t, err) + + require.NoError(t, transfer.VerifyDelivered(ctx)) + require.NoError(t, transfer.VerifyCommitmentCreated(ctx)) + require.NoError(t, transfer.VerifyReceiptCreated(ctx)) + require.NoError(t, transfer.VerifyCommitmentCleared(ctx)) + require.NoError(t, transfer.VerifyAcknowledgementWritten(ctx, status.GetRecvTx().GetTxHash())) + require.NoError(t, transfer.VerifyAcknowledgementExecuted(ctx, status.GetAckTx().GetTxHash())) +} + +func TestRemoteProver_TimesOutPacket(t *testing.T) { + t.Parallel() + + setup := startRemoteProverEnv(t, e2etest.EVMRequirements{ControlledMining: true}) + + transferApp := e2etest.NewTransfer(t, setup.env, setup.deployment, setup.sender, setup.route) + relayer := e2etest.StartRelayer(t, setup.driver, setup.env) + ctx := t.Context() + + require.NoError(t, relayer.Stop(ctx)) + + transfer, err := transferApp.Send(ctx, e2etest.TransferRequest{ + Amount: big.NewInt(3_000_000), + Timeout: packetTimeout, + }) + require.NoError(t, err) + + chainB, err := setup.env.Chain(setup.route.Destination) + require.NoError(t, err) + + mining, err := chainB.Mining() + require.NoError(t, err) + require.NoError(t, mining.AdvanceTime(ctx, packetTimeoutAdvance)) + + relayer = e2etest.StartRelayer(t, setup.driver, setup.env) + + status, err := e2etest.AwaitState(ctx, relayer, transfer.PacketTx(), + relayerv2.PacketState_PACKET_STATE_TIMED_OUT) + require.NoError(t, err) + require.NoError(t, transfer.VerifyRefunded(ctx, status.GetTimeoutTx().GetTxHash())) + require.NoError(t, transfer.VerifyNotMinted(ctx)) + require.NoError(t, transfer.VerifyCommitmentCleared(ctx)) +} + +func TestRemoteProver_ErrorAcknowledgement(t *testing.T) { + t.Parallel() + + setup := startRemoteProverEnv(t, e2etest.EVMRequirements{}) + + gmp := e2etest.NewGMP(t, setup.env, setup.deployment, setup.sender, setup.route) + relayer := e2etest.StartRelayer(t, setup.driver, setup.env) + ctx := t.Context() + + call, err := gmp.Call(ctx, e2etest.GMPRequest{Payload: invalidGMPPayload}) + require.NoError(t, err) + + _, err = e2etest.AwaitState(ctx, relayer, call.PacketTx(), + relayerv2.PacketState_PACKET_STATE_REJECTED) + require.NoError(t, err) + require.NoError(t, call.VerifyCounterUnchanged(ctx)) +} diff --git a/e2e/test-matrix.md b/e2e/test-matrix.md index cdede660f..d0228c308 100644 --- a/e2e/test-matrix.md +++ b/e2e/test-matrix.md @@ -36,6 +36,9 @@ | `TestRelay_FilteredSequences` | EVM portable | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Besu; 2 IBC instances; 1 connection; 2 attestors | | `TestRelay_FilteredTimeoutSequences` | EVM portable | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Besu; 2 IBC instances; 1 connection; 2 attestors | | `TestRelayerRecoversAfterNodeRestart` | EVM (node lifecycle) | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | +| `TestRemoteProver_ErrorAcknowledgement` | EVM portable | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Besu; 2 IBC instances; 1 connection; 2 attestors | +| `TestRemoteProver_RelaysPacket` | EVM portable | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Besu; 2 IBC instances; 1 connection; 2 attestors | +| `TestRemoteProver_TimesOutPacket` | EVM (controlled mining) | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | | `TestRemoteSignerFixtureRequiresKeyID` | None | No environment | No environment | No environment | | `TestTransferTimeout_Refund` | EVM (controlled mining) | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | | `TestTransfer_AutoRelay` | EVM portable | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Anvil; 2 IBC instances; 1 connection; 2 attestors | 2× Besu; 2 IBC instances; 1 connection; 2 attestors | diff --git a/proto/cli/prover.proto b/proto/cli/prover.proto new file mode 100644 index 000000000..0a11a8b4d --- /dev/null +++ b/proto/cli/prover.proto @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package ibc.v2.prover; + +option go_package = "github.com/cosmos/ibc/cli/api/v2/prover"; + +// ProverService generates the proofs a relayer submits for one light client. +// It mirrors the relayer's internal prover interface so a custom light client +// can be supported by implementing this service. +// +// Every request names the light client it is scoped to, so one service can +// serve many clients across many chains. +service ProverService { + // LatestProvableHeight returns the highest height a subsequent StateProof + // and PacketProofs call sharing that height can currently succeed at, with + // that height's counterparty-chain timestamp. + rpc LatestProvableHeight(LatestProvableHeightRequest) returns (LatestProvableHeightResponse) {} + + // StateProof proves the light client's counterparty state at a height. + rpc StateProof(StateProofRequest) returns (StateProofResponse) {} + + // PacketProofs proves each packet's membership or non-membership at a + // height, one proof per packet with indices aligned to the request. + rpc PacketProofs(PacketProofsRequest) returns (PacketProofsResponse) {} +} + +// Client identifies one configured light client: the chain it lives on and its +// client id. +message Client { + // The chain the light client lives on. + string chain_id = 1; + // The light client's id on that chain. + string client_id = 2; +} + +message LatestProvableHeightRequest { + // The light client this call is scoped to. + Client client = 1; +} + +message LatestProvableHeightResponse { + // The highest counterparty height currently provable. + uint64 height = 1; + // counterparty-chain timestamp of height, in seconds. + uint64 timestamp = 2; +} + +message StateProofRequest { + // The light client this call is scoped to. + Client client = 1; + // The counterparty height to prove at. + uint64 height = 2; +} + +message StateProofResponse { + // The proof, opaque to the relayer and passed to the light client unchanged. + bytes proof = 1; +} + +message PacketProofsRequest { + // The light client this call is scoped to. + Client client = 1; + // The counterparty height to prove at. + uint64 height = 2; + // Which commitment to prove for every packet in this request. + ProofKind kind = 3; + // The packets to prove, all under the same kind and height. + repeated Packet packets = 4; +} + +message PacketProofsResponse { + // One proof per requested packet, in request order. + repeated bytes proofs = 1; +} + +enum ProofKind { + PROOF_KIND_UNSPECIFIED = 0; + // The packet was sent. Proven to receive it. + PROOF_KIND_PACKET_COMMITMENT = 1; + // The packet was received and acknowledged. Proven to acknowledge it. + PROOF_KIND_ACKNOWLEDGEMENT = 2; + // The packet was never received. Proven to time it out. + PROOF_KIND_RECEIPT_ABSENCE = 3; +} + +message Packet { + // The packet's sequence on the source client. + uint64 sequence = 1; + // The client the packet was sent from. + string source_client = 2; + // The client the packet was sent to. + string destination_client = 3; + // When the packet stops being receivable, in seconds. + uint64 timeout_timestamp = 4; + // The packet's application payloads. + repeated Payload payloads = 5; +} + +message Payload { + // The application that sent the payload. + string source_port = 1; + // The application that receives the payload. + string destination_port = 2; + // The application version the payload is encoded for. + string version = 3; + // How value is encoded. + string encoding = 4; + // The application data. + bytes value = 5; +}