diff --git a/api/uexecutor/v1/query.pulsar.go b/api/uexecutor/v1/query.pulsar.go index c1dd3ea3e..f81871dbb 100644 --- a/api/uexecutor/v1/query.pulsar.go +++ b/api/uexecutor/v1/query.pulsar.go @@ -7389,10 +7389,11 @@ func (x *fastReflection_QueryAllUniversalTxResponse) ProtoMethods() *protoiface. } var ( - md_PendingOutboundEntry protoreflect.MessageDescriptor - fd_PendingOutboundEntry_outbound_id protoreflect.FieldDescriptor - fd_PendingOutboundEntry_universal_tx_id protoreflect.FieldDescriptor - fd_PendingOutboundEntry_created_at protoreflect.FieldDescriptor + md_PendingOutboundEntry protoreflect.MessageDescriptor + fd_PendingOutboundEntry_outbound_id protoreflect.FieldDescriptor + fd_PendingOutboundEntry_universal_tx_id protoreflect.FieldDescriptor + fd_PendingOutboundEntry_created_at protoreflect.FieldDescriptor + fd_PendingOutboundEntry_signing_deadline protoreflect.FieldDescriptor ) func init() { @@ -7401,6 +7402,7 @@ func init() { fd_PendingOutboundEntry_outbound_id = md_PendingOutboundEntry.Fields().ByName("outbound_id") fd_PendingOutboundEntry_universal_tx_id = md_PendingOutboundEntry.Fields().ByName("universal_tx_id") fd_PendingOutboundEntry_created_at = md_PendingOutboundEntry.Fields().ByName("created_at") + fd_PendingOutboundEntry_signing_deadline = md_PendingOutboundEntry.Fields().ByName("signing_deadline") } var _ protoreflect.Message = (*fastReflection_PendingOutboundEntry)(nil) @@ -7486,6 +7488,12 @@ func (x *fastReflection_PendingOutboundEntry) Range(f func(protoreflect.FieldDes return } } + if x.SigningDeadline != int64(0) { + value := protoreflect.ValueOfInt64(x.SigningDeadline) + if !f(fd_PendingOutboundEntry_signing_deadline, value) { + return + } + } } // Has reports whether a field is populated. @@ -7507,6 +7515,8 @@ func (x *fastReflection_PendingOutboundEntry) Has(fd protoreflect.FieldDescripto return x.UniversalTxId != "" case "uexecutor.v1.PendingOutboundEntry.created_at": return x.CreatedAt != int64(0) + case "uexecutor.v1.PendingOutboundEntry.signing_deadline": + return x.SigningDeadline != int64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.PendingOutboundEntry")) @@ -7529,6 +7539,8 @@ func (x *fastReflection_PendingOutboundEntry) Clear(fd protoreflect.FieldDescrip x.UniversalTxId = "" case "uexecutor.v1.PendingOutboundEntry.created_at": x.CreatedAt = int64(0) + case "uexecutor.v1.PendingOutboundEntry.signing_deadline": + x.SigningDeadline = int64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.PendingOutboundEntry")) @@ -7554,6 +7566,9 @@ func (x *fastReflection_PendingOutboundEntry) Get(descriptor protoreflect.FieldD case "uexecutor.v1.PendingOutboundEntry.created_at": value := x.CreatedAt return protoreflect.ValueOfInt64(value) + case "uexecutor.v1.PendingOutboundEntry.signing_deadline": + value := x.SigningDeadline + return protoreflect.ValueOfInt64(value) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.PendingOutboundEntry")) @@ -7580,6 +7595,8 @@ func (x *fastReflection_PendingOutboundEntry) Set(fd protoreflect.FieldDescripto x.UniversalTxId = value.Interface().(string) case "uexecutor.v1.PendingOutboundEntry.created_at": x.CreatedAt = value.Int() + case "uexecutor.v1.PendingOutboundEntry.signing_deadline": + x.SigningDeadline = value.Int() default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.PendingOutboundEntry")) @@ -7606,6 +7623,8 @@ func (x *fastReflection_PendingOutboundEntry) Mutable(fd protoreflect.FieldDescr panic(fmt.Errorf("field universal_tx_id of message uexecutor.v1.PendingOutboundEntry is not mutable")) case "uexecutor.v1.PendingOutboundEntry.created_at": panic(fmt.Errorf("field created_at of message uexecutor.v1.PendingOutboundEntry is not mutable")) + case "uexecutor.v1.PendingOutboundEntry.signing_deadline": + panic(fmt.Errorf("field signing_deadline of message uexecutor.v1.PendingOutboundEntry is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.PendingOutboundEntry")) @@ -7625,6 +7644,8 @@ func (x *fastReflection_PendingOutboundEntry) NewField(fd protoreflect.FieldDesc return protoreflect.ValueOfString("") case "uexecutor.v1.PendingOutboundEntry.created_at": return protoreflect.ValueOfInt64(int64(0)) + case "uexecutor.v1.PendingOutboundEntry.signing_deadline": + return protoreflect.ValueOfInt64(int64(0)) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.PendingOutboundEntry")) @@ -7705,6 +7726,9 @@ func (x *fastReflection_PendingOutboundEntry) ProtoMethods() *protoiface.Methods if x.CreatedAt != 0 { n += 1 + runtime.Sov(uint64(x.CreatedAt)) } + if x.SigningDeadline != 0 { + n += 1 + runtime.Sov(uint64(x.SigningDeadline)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -7734,6 +7758,11 @@ func (x *fastReflection_PendingOutboundEntry) ProtoMethods() *protoiface.Methods i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.SigningDeadline != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.SigningDeadline)) + i-- + dAtA[i] = 0x20 + } if x.CreatedAt != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.CreatedAt)) i-- @@ -7885,6 +7914,25 @@ func (x *fastReflection_PendingOutboundEntry) ProtoMethods() *protoiface.Methods break } } + case 4: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field SigningDeadline", wireType) + } + x.SigningDeadline = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.SigningDeadline |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -10611,9 +10659,10 @@ type PendingOutboundEntry struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - OutboundId string `protobuf:"bytes,1,opt,name=outbound_id,json=outboundId,proto3" json:"outbound_id,omitempty"` - UniversalTxId string `protobuf:"bytes,2,opt,name=universal_tx_id,json=universalTxId,proto3" json:"universal_tx_id,omitempty"` - CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + OutboundId string `protobuf:"bytes,1,opt,name=outbound_id,json=outboundId,proto3" json:"outbound_id,omitempty"` + UniversalTxId string `protobuf:"bytes,2,opt,name=universal_tx_id,json=universalTxId,proto3" json:"universal_tx_id,omitempty"` + CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + SigningDeadline int64 `protobuf:"varint,4,opt,name=signing_deadline,json=signingDeadline,proto3" json:"signing_deadline,omitempty"` // unix timestamp after which the TSS signature expires on the destination chain (0 = no expiry) } func (x *PendingOutboundEntry) Reset() { @@ -10657,6 +10706,13 @@ func (x *PendingOutboundEntry) GetCreatedAt() int64 { return 0 } +func (x *PendingOutboundEntry) GetSigningDeadline() int64 { + if x != nil { + return x.SigningDeadline + } + return 0 +} + type QueryGetPendingOutboundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -10932,150 +10988,153 @@ var file_uexecutor_v1_query_proto_rawDesc = []byte{ 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x14, - 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x41, 0x0a, 0x1e, + 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa9, 0x01, 0x0a, + 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x29, 0x0a, + 0x10, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x41, 0x0a, 0x1e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x47, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x22, 0x91, 0x01, 0x0a, 0x1f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x22, - 0x91, 0x01, 0x0a, 0x1f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x34, 0x0a, - 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x22, 0x69, 0x0a, 0x1f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x46, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x73, - 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xe1, - 0x01, 0x0a, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, - 0x73, 0x12, 0x36, 0x0a, 0x09, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x09, - 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x47, 0x0a, 0x0a, 0x70, 0x61, 0x67, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, - 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x32, 0x8c, 0x0b, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x6b, 0x0a, 0x06, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x20, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, - 0x76, 0x31, 0x2f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x99, 0x01, 0x0a, 0x12, 0x41, 0x6c, - 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, - 0x12, 0x2c, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, - 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x8f, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x28, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, - 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x61, 0x6c, 0x54, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, - 0x78, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x8a, 0x01, 0x0a, 0x0e, 0x41, 0x6c, 0x6c, 0x55, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x28, 0x2e, 0x75, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, - 0x6c, 0x6c, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x55, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, - 0x5f, 0x74, 0x78, 0x73, 0x12, 0x7f, 0x0a, 0x08, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, - 0x12, 0x22, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x24, 0x12, 0x22, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x2f, 0x7b, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x81, 0x01, 0x0a, 0x0c, 0x41, 0x6c, 0x6c, 0x47, 0x61, 0x73, - 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x47, 0x61, - 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, - 0x18, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x73, 0x12, 0x83, 0x01, 0x0a, 0x09, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x75, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x38, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x34, 0x0a, 0x08, 0x6f, 0x75, 0x74, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, + 0x69, 0x0a, 0x1f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x46, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, + 0x62, 0x61, 0x73, 0x65, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, + 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xe1, 0x01, 0x0a, 0x20, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3c, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x36, 0x0a, + 0x09, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x47, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x73, 0x6d, + 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x8c, + 0x0b, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x6b, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x73, 0x12, 0x20, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, + 0x14, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x99, 0x01, 0x0a, 0x12, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x2c, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x75, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x7b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, - 0x85, 0x01, 0x0a, 0x0d, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, - 0x73, 0x12, 0x27, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, - 0x74, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x65, 0x78, + 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, - 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x75, - 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x73, 0x12, 0xa7, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2c, + 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x20, 0x12, 0x1e, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, + 0x2f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x73, 0x12, 0x8f, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x61, 0x6c, 0x54, 0x78, 0x12, 0x28, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, 0x55, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x75, + 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, + 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x22, 0x12, 0x20, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, + 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x73, 0x2f, 0x7b, + 0x69, 0x64, 0x7d, 0x12, 0x8a, 0x01, 0x0a, 0x0e, 0x41, 0x6c, 0x6c, 0x55, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x28, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x55, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x29, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x54, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, + 0x76, 0x31, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x73, + 0x12, 0x7f, 0x0a, 0x08, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x22, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x47, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x2e, 0x12, 0x2c, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, - 0x76, 0x31, 0x2f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x2f, 0x7b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, - 0x7d, 0x12, 0x9d, 0x01, 0x0a, 0x13, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x2d, 0x2e, 0x75, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, - 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, - 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, - 0x12, 0x1f, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x73, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, - 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x0c, - 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x55, - 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x55, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, + 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x61, 0x73, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x2f, 0x7b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x7d, 0x12, 0x81, 0x01, 0x0a, 0x0c, 0x41, 0x6c, 0x6c, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x26, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, + 0x6c, 0x6c, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x61, 0x73, 0x5f, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x73, 0x12, 0x83, 0x01, 0x0a, 0x09, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, + 0x65, 0x74, 0x61, 0x12, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x61, + 0x2f, 0x7b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x85, 0x01, 0x0a, 0x0d, + 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x12, 0x27, 0x2e, + 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x43, 0x68, + 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x65, + 0x74, 0x61, 0x73, 0x12, 0xa7, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2c, 0x2e, 0x75, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, + 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x47, 0x65, 0x74, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x12, + 0x2c, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, + 0x7b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x9d, 0x01, + 0x0a, 0x13, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x2d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x42, 0xb2, 0x01, + 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x42, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, + 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/uregistry/v1/types.pulsar.go b/api/uregistry/v1/types.pulsar.go index 50d04888d..ba34deb4f 100644 --- a/api/uregistry/v1/types.pulsar.go +++ b/api/uregistry/v1/types.pulsar.go @@ -2656,6 +2656,7 @@ var ( fd_ChainConfig_enabled protoreflect.FieldDescriptor fd_ChainConfig_gas_oracle_fetch_interval protoreflect.FieldDescriptor fd_ChainConfig_vault_methods protoreflect.FieldDescriptor + fd_ChainConfig_tss_signing_deadline protoreflect.FieldDescriptor ) func init() { @@ -2670,6 +2671,7 @@ func init() { fd_ChainConfig_enabled = md_ChainConfig.Fields().ByName("enabled") fd_ChainConfig_gas_oracle_fetch_interval = md_ChainConfig.Fields().ByName("gas_oracle_fetch_interval") fd_ChainConfig_vault_methods = md_ChainConfig.Fields().ByName("vault_methods") + fd_ChainConfig_tss_signing_deadline = md_ChainConfig.Fields().ByName("tss_signing_deadline") } var _ protoreflect.Message = (*fastReflection_ChainConfig)(nil) @@ -2791,6 +2793,12 @@ func (x *fastReflection_ChainConfig) Range(f func(protoreflect.FieldDescriptor, return } } + if x.TssSigningDeadline != nil { + value := protoreflect.ValueOfMessage(x.TssSigningDeadline.ProtoReflect()) + if !f(fd_ChainConfig_tss_signing_deadline, value) { + return + } + } } // Has reports whether a field is populated. @@ -2824,6 +2832,8 @@ func (x *fastReflection_ChainConfig) Has(fd protoreflect.FieldDescriptor) bool { return x.GasOracleFetchInterval != nil case "uregistry.v1.ChainConfig.vault_methods": return len(x.VaultMethods) != 0 + case "uregistry.v1.ChainConfig.tss_signing_deadline": + return x.TssSigningDeadline != nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uregistry.v1.ChainConfig")) @@ -2858,6 +2868,8 @@ func (x *fastReflection_ChainConfig) Clear(fd protoreflect.FieldDescriptor) { x.GasOracleFetchInterval = nil case "uregistry.v1.ChainConfig.vault_methods": x.VaultMethods = nil + case "uregistry.v1.ChainConfig.tss_signing_deadline": + x.TssSigningDeadline = nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uregistry.v1.ChainConfig")) @@ -2907,6 +2919,9 @@ func (x *fastReflection_ChainConfig) Get(descriptor protoreflect.FieldDescriptor } listValue := &_ChainConfig_9_list{list: &x.VaultMethods} return protoreflect.ValueOfList(listValue) + case "uregistry.v1.ChainConfig.tss_signing_deadline": + value := x.TssSigningDeadline + return protoreflect.ValueOfMessage(value.ProtoReflect()) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uregistry.v1.ChainConfig")) @@ -2949,6 +2964,8 @@ func (x *fastReflection_ChainConfig) Set(fd protoreflect.FieldDescriptor, value lv := value.List() clv := lv.(*_ChainConfig_9_list) x.VaultMethods = *clv.list + case "uregistry.v1.ChainConfig.tss_signing_deadline": + x.TssSigningDeadline = value.Message().Interface().(*durationpb.Duration) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uregistry.v1.ChainConfig")) @@ -2996,6 +3013,11 @@ func (x *fastReflection_ChainConfig) Mutable(fd protoreflect.FieldDescriptor) pr } value := &_ChainConfig_9_list{list: &x.VaultMethods} return protoreflect.ValueOfList(value) + case "uregistry.v1.ChainConfig.tss_signing_deadline": + if x.TssSigningDeadline == nil { + x.TssSigningDeadline = new(durationpb.Duration) + } + return protoreflect.ValueOfMessage(x.TssSigningDeadline.ProtoReflect()) case "uregistry.v1.ChainConfig.chain": panic(fmt.Errorf("field chain of message uregistry.v1.ChainConfig is not mutable")) case "uregistry.v1.ChainConfig.vm_type": @@ -3040,6 +3062,9 @@ func (x *fastReflection_ChainConfig) NewField(fd protoreflect.FieldDescriptor) p case "uregistry.v1.ChainConfig.vault_methods": list := []*VaultMethods{} return protoreflect.ValueOfList(&_ChainConfig_9_list{list: &list}) + case "uregistry.v1.ChainConfig.tss_signing_deadline": + m := new(durationpb.Duration) + return protoreflect.ValueOfMessage(m.ProtoReflect()) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uregistry.v1.ChainConfig")) @@ -3148,6 +3173,10 @@ func (x *fastReflection_ChainConfig) ProtoMethods() *protoiface.Methods { n += 1 + l + runtime.Sov(uint64(l)) } } + if x.TssSigningDeadline != nil { + l = options.Size(x.TssSigningDeadline) + n += 1 + l + runtime.Sov(uint64(l)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -3177,6 +3206,20 @@ func (x *fastReflection_ChainConfig) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.TssSigningDeadline != nil { + encoded, err := options.Marshal(x.TssSigningDeadline) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x52 + } if len(x.VaultMethods) > 0 { for iNdEx := len(x.VaultMethods) - 1; iNdEx >= 0; iNdEx-- { encoded, err := options.Marshal(x.VaultMethods[iNdEx]) @@ -3617,6 +3660,42 @@ func (x *fastReflection_ChainConfig) ProtoMethods() *protoiface.Methods { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err } iNdEx = postIndex + case 10: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field TssSigningDeadline", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.TssSigningDeadline == nil { + x.TssSigningDeadline = &durationpb.Duration{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.TssSigningDeadline); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -5492,6 +5571,7 @@ type ChainConfig struct { Enabled *ChainEnabled `protobuf:"bytes,7,opt,name=enabled,proto3" json:"enabled,omitempty"` // Whether this chain is currently enabled or not GasOracleFetchInterval *durationpb.Duration `protobuf:"bytes,8,opt,name=gas_oracle_fetch_interval,json=gasOracleFetchInterval,proto3" json:"gas_oracle_fetch_interval,omitempty"` // how often relayers should fetch gas prices VaultMethods []*VaultMethods `protobuf:"bytes,9,rep,name=vault_methods,json=vaultMethods,proto3" json:"vault_methods,omitempty"` // List of methods exposed by the vault contract (optional) + TssSigningDeadline *durationpb.Duration `protobuf:"bytes,10,opt,name=tss_signing_deadline,json=tssSigningDeadline,proto3" json:"tss_signing_deadline,omitempty"` // duration added to block time to compute the signature expiry deadline on the destination chain (zero = no expiry) } func (x *ChainConfig) Reset() { @@ -5577,6 +5657,13 @@ func (x *ChainConfig) GetVaultMethods() []*VaultMethods { return nil } +func (x *ChainConfig) GetTssSigningDeadline() *durationpb.Duration { + if x != nil { + return x.TssSigningDeadline + } + return nil +} + type NativeRepresentation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -5779,7 +5866,7 @@ var file_uregistry_v1_types_proto_rawDesc = []byte{ 0x08, 0x52, 0x11, 0x69, 0x73, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x3a, 0x24, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x17, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2f, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xb4, 0x04, 0x0a, 0x0b, 0x43, + 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x87, 0x05, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x2d, 0x0a, 0x07, 0x76, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, @@ -5812,74 +5899,80 @@ var file_uregistry_v1_types_proto_rawDesc = []byte{ 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x0c, - 0x76, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x3a, 0x23, 0x98, 0xa0, - 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x72, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x79, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x70, 0x72, - 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, - 0x6e, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x64, 0x65, 0x6e, 0x6f, 0x6d, - 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3a, 0x2c, 0x98, 0xa0, 0x1f, - 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1f, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x2f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x72, 0x65, - 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xfa, 0x02, 0x0a, 0x0b, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, - 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, - 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, - 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, - 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x61, 0x70, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x43, 0x61, 0x70, - 0x12, 0x36, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x6e, 0x61, 0x74, 0x69, - 0x76, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x70, - 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x14, 0x6e, 0x61, 0x74, - 0x69, 0x76, 0x65, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, - 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2a, 0x91, 0x01, 0x0a, 0x06, 0x56, 0x6d, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x56, 0x4d, 0x10, - 0x00, 0x12, 0x07, 0x0a, 0x03, 0x45, 0x56, 0x4d, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x56, - 0x4d, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x56, 0x4d, 0x10, 0x03, - 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x53, 0x4d, 0x5f, 0x56, 0x4d, 0x10, 0x04, 0x12, 0x0c, 0x0a, - 0x08, 0x43, 0x41, 0x49, 0x52, 0x4f, 0x5f, 0x56, 0x4d, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x54, - 0x52, 0x4f, 0x4e, 0x5f, 0x56, 0x4d, 0x10, 0x06, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x45, 0x4c, - 0x4c, 0x41, 0x52, 0x5f, 0x56, 0x4d, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x49, 0x54, 0x43, - 0x4f, 0x49, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, - 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x56, 0x4d, 0x10, 0x09, 0x2a, 0x4b, 0x0a, 0x09, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, - 0x43, 0x32, 0x30, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x52, 0x43, 0x37, 0x32, 0x31, 0x10, - 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x52, 0x43, 0x31, 0x31, 0x35, 0x35, 0x10, 0x03, 0x12, 0x07, - 0x0a, 0x03, 0x53, 0x50, 0x4c, 0x10, 0x04, 0x2a, 0x68, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x43, - 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, - 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, - 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x4e, 0x44, - 0x41, 0x52, 0x44, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, - 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x41, 0x53, 0x54, 0x10, - 0x02, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x79, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x0c, - 0x55, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x55, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x55, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x79, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x76, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x51, 0x0a, 0x14, + 0x74, 0x73, 0x73, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x04, 0x98, 0xdf, 0x1f, 0x01, 0x52, 0x12, 0x74, 0x73, 0x73, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x3a, + 0x23, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, + 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, + 0x05, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x64, 0x65, + 0x6e, 0x6f, 0x6d, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3a, 0x2c, + 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1f, 0x75, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, + 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xfa, 0x02, 0x0a, + 0x0b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x63, 0x69, + 0x6d, 0x61, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, + 0x6d, 0x61, 0x6c, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x23, + 0x0a, 0x0d, 0x6c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x61, 0x70, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, + 0x43, 0x61, 0x70, 0x12, 0x36, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x6e, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x14, + 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, + 0xb0, 0x2a, 0x16, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2a, 0x91, 0x01, 0x0a, 0x06, 0x56, 0x6d, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, + 0x56, 0x4d, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x45, 0x56, 0x4d, 0x10, 0x01, 0x12, 0x07, 0x0a, + 0x03, 0x53, 0x56, 0x4d, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x56, + 0x4d, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x53, 0x4d, 0x5f, 0x56, 0x4d, 0x10, 0x04, + 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x49, 0x52, 0x4f, 0x5f, 0x56, 0x4d, 0x10, 0x05, 0x12, 0x0b, + 0x0a, 0x07, 0x54, 0x52, 0x4f, 0x4e, 0x5f, 0x56, 0x4d, 0x10, 0x06, 0x12, 0x0e, 0x0a, 0x0a, 0x53, + 0x54, 0x45, 0x4c, 0x4c, 0x41, 0x52, 0x5f, 0x56, 0x4d, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x42, + 0x49, 0x54, 0x43, 0x4f, 0x49, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x10, 0x08, 0x12, + 0x0c, 0x0a, 0x08, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x56, 0x4d, 0x10, 0x09, 0x2a, 0x4b, 0x0a, + 0x09, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, + 0x05, 0x45, 0x52, 0x43, 0x32, 0x30, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x52, 0x43, 0x37, + 0x32, 0x31, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x52, 0x43, 0x31, 0x31, 0x35, 0x35, 0x10, + 0x03, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x50, 0x4c, 0x10, 0x04, 0x2a, 0x68, 0x0a, 0x10, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, + 0x0a, 0x14, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x46, + 0x49, 0x52, 0x4d, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, + 0x41, 0x4e, 0x44, 0x41, 0x52, 0x44, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x46, + 0x49, 0x52, 0x4d, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x41, + 0x53, 0x54, 0x10, 0x02, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, + 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x75, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x75, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, + 0xaa, 0x02, 0x0c, 0x55, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x56, 0x31, 0xca, + 0x02, 0x0c, 0x55, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5c, 0x56, 0x31, 0xe2, 0x02, + 0x18, 0x55, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x79, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -5919,13 +6012,14 @@ var file_uregistry_v1_types_proto_depIdxs = []int32{ 7, // 5: uregistry.v1.ChainConfig.enabled:type_name -> uregistry.v1.ChainEnabled 11, // 6: uregistry.v1.ChainConfig.gas_oracle_fetch_interval:type_name -> google.protobuf.Duration 5, // 7: uregistry.v1.ChainConfig.vault_methods:type_name -> uregistry.v1.VaultMethods - 1, // 8: uregistry.v1.TokenConfig.token_type:type_name -> uregistry.v1.TokenType - 9, // 9: uregistry.v1.TokenConfig.native_representation:type_name -> uregistry.v1.NativeRepresentation - 10, // [10:10] is the sub-list for method output_type - 10, // [10:10] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 11, // 8: uregistry.v1.ChainConfig.tss_signing_deadline:type_name -> google.protobuf.Duration + 1, // 9: uregistry.v1.TokenConfig.token_type:type_name -> uregistry.v1.TokenType + 9, // 10: uregistry.v1.TokenConfig.native_representation:type_name -> uregistry.v1.NativeRepresentation + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_uregistry_v1_types_proto_init() } diff --git a/app/upgrades.go b/app/upgrades.go index 9525acb67..3441c2538 100755 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -20,6 +20,7 @@ import ( contractauditchanges "github.com/pushchain/push-chain-node/app/upgrades/contract-audit-changes" evmparamsmigration "github.com/pushchain/push-chain-node/app/upgrades/evm-params-migration" evmchainidffix "github.com/pushchain/push-chain-node/app/upgrades/evm-chainid-fix" + evmpreinstalls "github.com/pushchain/push-chain-node/app/upgrades/evm-preinstalls" ethhashfix "github.com/pushchain/push-chain-node/app/upgrades/eth-hash-fix" evmrpcfix "github.com/pushchain/push-chain-node/app/upgrades/evm-rpc-fix" feeabs "github.com/pushchain/push-chain-node/app/upgrades/fee-abs" @@ -71,6 +72,7 @@ var Upgrades = []upgrades.Upgrade{ contractauditchanges.NewUpgrade(), evmparamsmigration.NewUpgrade(), evmchainidffix.NewUpgrade(), + evmpreinstalls.NewUpgrade(), } // RegisterUpgradeHandlers registers the chain upgrade handlers diff --git a/app/upgrades/evm-preinstalls/upgrade.go b/app/upgrades/evm-preinstalls/upgrade.go new file mode 100644 index 000000000..09bcd9bfb --- /dev/null +++ b/app/upgrades/evm-preinstalls/upgrade.go @@ -0,0 +1,82 @@ +package evmpreinstalls + +import ( + "context" + "fmt" + + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/evm/x/vm/statedb" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" + + "github.com/pushchain/push-chain-node/app/upgrades" +) + +const UpgradeName = "evm-preinstalls" + +func NewUpgrade() upgrades.Upgrade { + return upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, + StoreUpgrades: storetypes.StoreUpgrades{ + Added: []string{}, + Deleted: []string{}, + }, + } +} + +func CreateUpgradeHandler( + mm upgrades.ModuleManager, + configurator module.Configurator, + keepers *upgrades.AppKeepers, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + logger := sdkCtx.Logger().With("upgrade", UpgradeName) + logger.Info("Starting upgrade handler") + + versionMap, err := mm.RunMigrations(ctx, configurator, fromVM) + if err != nil { + return nil, fmt.Errorf("RunMigrations: %w", err) + } + + if err := deployCreate2Factory(sdkCtx, keepers); err != nil { + return nil, fmt.Errorf("deployCreate2Factory: %w", err) + } + + logger.Info("Upgrade complete", "upgrade", UpgradeName) + return versionMap, nil + } +} + +func deployCreate2Factory(ctx sdk.Context, keepers *upgrades.AppKeepers) error { + logger := ctx.Logger().With("migration", "evm-preinstalls") + + address := common.HexToAddress("0x4e59b44847b379578588920ca78fbf26c0b4956c") + code := common.FromHex("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3") + codeHash := crypto.Keccak256Hash(code).Bytes() + + if evmtypes.IsEmptyCodeHash(codeHash) { + return fmt.Errorf("create2 factory has empty code hash") + } + + if err := keepers.EVMKeeper.SetAccount(ctx, address, statedb.Account{ + Nonce: 0, + Balance: new(uint256.Int), + CodeHash: codeHash, + }); err != nil { + return fmt.Errorf("SetAccount: %w", err) + } + + keepers.EVMKeeper.SetCodeHash(ctx, address.Bytes(), codeHash) + keepers.EVMKeeper.SetCode(ctx, codeHash, code) + + logger.Info("Create2 factory deployed", "address", address.Hex()) + return nil +} diff --git a/proto/uexecutor/v1/query.proto b/proto/uexecutor/v1/query.proto index e5e9eef50..8ff266276 100755 --- a/proto/uexecutor/v1/query.proto +++ b/proto/uexecutor/v1/query.proto @@ -146,6 +146,7 @@ message PendingOutboundEntry { string outbound_id = 1; string universal_tx_id = 2; int64 created_at = 3; + int64 signing_deadline = 4; // unix timestamp after which the TSS signature expires on the destination chain (0 = no expiry) } message QueryGetPendingOutboundRequest { diff --git a/proto/uregistry/v1/types.proto b/proto/uregistry/v1/types.proto index d0a7dcac6..9155f993b 100644 --- a/proto/uregistry/v1/types.proto +++ b/proto/uregistry/v1/types.proto @@ -114,6 +114,8 @@ message ChainConfig { google.protobuf.Duration gas_oracle_fetch_interval = 8 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; // how often relayers should fetch gas prices repeated VaultMethods vault_methods = 9; // List of methods exposed by the vault contract (optional) + + google.protobuf.Duration tss_signing_deadline = 10 [(gogoproto.stdduration) = true]; // duration added to block time to compute the signature expiry deadline on the destination chain (zero = no expiry) } message NativeRepresentation { diff --git a/universalClient/chains/common/types.go b/universalClient/chains/common/types.go index d0526d59d..ffb3fd493 100644 --- a/universalClient/chains/common/types.go +++ b/universalClient/chains/common/types.go @@ -67,9 +67,13 @@ type TxBuilder interface { // IsAlreadyExecuted checks whether a transaction with the given txID has already been // executed on the destination chain (e.g., by another relayer). - // For SVM: checks if the ExecutedTx PDA exists on-chain. - // For EVM: returns false (EVM uses nonce-based replay protection). - IsAlreadyExecuted(ctx context.Context, txID string) (bool, error) + // For SVM: checks if the ExecutedTx PDA exists on-chain, AND returns the + // unix timestamp of the latest finalized block. Callers use this as the + // cluster's "now" to gate deadline-based give-up/REVERT decisions and to + // detect cluster halt or finalization stall (queryBlockTime far behind + // wall-clock). 0 means freshness couldn't be determined. + // For EVM: returns (false, 0, nil). EVM uses nonce-based replay protection. + IsAlreadyExecuted(ctx context.Context, txID string) (executed bool, queryBlockTime int64, err error) // GetGasFeeUsed returns the gas fee used by a transaction on the destination chain. // EVM: fetches receipt and returns gasUsed * effectiveGasPrice as decimal string. diff --git a/universalClient/chains/evm/tx_builder.go b/universalClient/chains/evm/tx_builder.go index d03ec8444..72f1f16d2 100644 --- a/universalClient/chains/evm/tx_builder.go +++ b/universalClient/chains/evm/tx_builder.go @@ -77,7 +77,9 @@ func NewTxBuilder( return tb, nil } -// GetOutboundSigningRequest creates a signing request from outbound event data +// GetOutboundSigningRequest creates a signing request from outbound event data. +// EVM doesn't consume data.SigningDeadline — deadlines are SVM-only; EVM relies +// on nonce-based finality. func (tb *TxBuilder) GetOutboundSigningRequest( ctx context.Context, data *uetypes.OutboundCreatedEvent, @@ -439,10 +441,11 @@ func parseGasLimit(gasLimitStr string) (*big.Int, error) { return gasLimit, nil } -// IsAlreadyExecuted returns false for EVM. EVM uses nonce-based replay protection, -// checked via GetNextNonce in the broadcaster. -func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, error) { - return false, nil +// IsAlreadyExecuted returns (false, 0, nil) for EVM. EVM uses nonce-based +// replay protection (checked via GetNextNonce in the broadcaster); the +// cluster-time signal is SVM-only. +func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, int64, error) { + return false, 0, nil } // GetGasFeeUsed returns the gas fee used by a transaction on the EVM chain. diff --git a/universalClient/chains/evm/tx_builder_test.go b/universalClient/chains/evm/tx_builder_test.go index 8ae921fe0..4ec721a86 100644 --- a/universalClient/chains/evm/tx_builder_test.go +++ b/universalClient/chains/evm/tx_builder_test.go @@ -549,27 +549,30 @@ func TestFinalizeUniversalTxUnifiedEncoding(t *testing.T) { } } -// TestIsAlreadyExecuted tests the stub that always returns false +// TestIsAlreadyExecuted tests the stub that always returns (false, 0, nil) func TestIsAlreadyExecuted(t *testing.T) { builder := newTestTxBuilder(t) ctx := context.Background() t.Run("always returns false", func(t *testing.T) { - executed, err := builder.IsAlreadyExecuted(ctx, "0x1234567890abcdef") + executed, queryBlockTime, err := builder.IsAlreadyExecuted(ctx, "0x1234567890abcdef") assert.NoError(t, err) assert.False(t, executed) + assert.Equal(t, int64(0), queryBlockTime) }) t.Run("returns false for empty txID", func(t *testing.T) { - executed, err := builder.IsAlreadyExecuted(ctx, "") + executed, queryBlockTime, err := builder.IsAlreadyExecuted(ctx, "") assert.NoError(t, err) assert.False(t, executed) + assert.Equal(t, int64(0), queryBlockTime) }) t.Run("returns false for arbitrary txID", func(t *testing.T) { - executed, err := builder.IsAlreadyExecuted(ctx, "any-string-at-all") + executed, queryBlockTime, err := builder.IsAlreadyExecuted(ctx, "any-string-at-all") assert.NoError(t, err) assert.False(t, executed) + assert.Equal(t, int64(0), queryBlockTime) }) } diff --git a/universalClient/chains/push/event_parser.go b/universalClient/chains/push/event_parser.go index d82d60bba..901d02396 100644 --- a/universalClient/chains/push/event_parser.go +++ b/universalClient/chains/push/event_parser.go @@ -64,7 +64,6 @@ func convertTssEvent(tssEvent *utsstypes.TssEvent) (*store.Event, error) { }, nil } - // convertFundMigrationEvent converts a FundMigration to a store.Event. func convertFundMigrationEvent(migration *utsstypes.FundMigration) (*store.Event, error) { if migration == nil { @@ -138,6 +137,7 @@ func convertOutboundToEvent(entry *uexecutortypes.PendingOutboundEntry, outbound PcTxHash: pcTxHash, LogIndex: logIndex, RevertMsg: revertMsg, + SigningDeadline: entry.SigningDeadline, } eventData, err := json.Marshal(outboundData) diff --git a/universalClient/chains/push/event_parser_test.go b/universalClient/chains/push/event_parser_test.go index 31168157b..b24ba7c1e 100644 --- a/universalClient/chains/push/event_parser_test.go +++ b/universalClient/chains/push/event_parser_test.go @@ -245,42 +245,54 @@ func TestConvertOutboundToEvent(t *testing.T) { assert.Equal(t, "3", data.LogIndex) assert.Empty(t, data.RevertMsg) }) -} - -func TestDefaultExpiryOffset(t *testing.T) { - assert.Equal(t, uint64(600), uint64(DefaultExpiryOffset)) -} -func TestHashEventID(t *testing.T) { - t.Run("deterministic output", func(t *testing.T) { - id1 := hashEventID("keygen", "123") - id2 := hashEventID("keygen", "123") - assert.Equal(t, id1, id2) + t.Run("both nil returns error", func(t *testing.T) { + result, err := convertOutboundToEvent(nil, nil) + require.Error(t, err) + assert.Nil(t, result) + assert.Contains(t, err.Error(), "entry or outbound is nil") }) - t.Run("different types produce different IDs", func(t *testing.T) { - id1 := hashEventID("keygen", "123") - id2 := hashEventID("refresh", "123") - assert.NotEqual(t, id1, id2) - }) + t.Run("chain-supplied signing deadline flows through", func(t *testing.T) { + entry := &uexecutortypes.PendingOutboundEntry{ + OutboundId: "0xabc", + UniversalTxId: "utx-deadline", + CreatedAt: 1000, + SigningDeadline: 1735689600, + } + outbound := &uexecutortypes.OutboundTx{ + Id: "0xabc", + DestinationChain: "solana:devnet", + Amount: "1", + } - t.Run("different raw IDs produce different IDs", func(t *testing.T) { - id1 := hashEventID("keygen", "1") - id2 := hashEventID("keygen", "2") - assert.NotEqual(t, id1, id2) - }) + result, err := convertOutboundToEvent(entry, outbound) + require.NoError(t, err) - t.Run("output is hex string of sha256 length", func(t *testing.T) { - id := hashEventID("type", "id") - assert.Len(t, id, 64) // sha256 = 32 bytes = 64 hex chars + var data uexecutortypes.OutboundCreatedEvent + require.NoError(t, json.Unmarshal(result.EventData, &data)) + assert.Equal(t, int64(1735689600), data.SigningDeadline) }) -} -func TestConvertOutboundToEvent_BothNil(t *testing.T) { - result, err := convertOutboundToEvent(nil, nil) - require.Error(t, err) - assert.Nil(t, result) - assert.Contains(t, err.Error(), "entry or outbound is nil") + t.Run("zero signing deadline stays zero", func(t *testing.T) { + entry := &uexecutortypes.PendingOutboundEntry{ + OutboundId: "0xnone", + UniversalTxId: "utx-no-deadline", + CreatedAt: 1000, + } + outbound := &uexecutortypes.OutboundTx{ + Id: "0xnone", + DestinationChain: "eip155:1", + Amount: "1", + } + + result, err := convertOutboundToEvent(entry, outbound) + require.NoError(t, err) + + var data uexecutortypes.OutboundCreatedEvent + require.NoError(t, json.Unmarshal(result.EventData, &data)) + assert.Equal(t, int64(0), data.SigningDeadline) + }) } func TestConvertFundMigrationEvent(t *testing.T) { @@ -341,3 +353,32 @@ func TestConvertFundMigrationEvent(t *testing.T) { assert.Equal(t, hashEventID(store.EventTypeSignFundMigrate, "42"), result.EventID) }) } + +func TestHashEventID(t *testing.T) { + t.Run("deterministic output", func(t *testing.T) { + id1 := hashEventID("keygen", "123") + id2 := hashEventID("keygen", "123") + assert.Equal(t, id1, id2) + }) + + t.Run("different types produce different IDs", func(t *testing.T) { + id1 := hashEventID("keygen", "123") + id2 := hashEventID("refresh", "123") + assert.NotEqual(t, id1, id2) + }) + + t.Run("different raw IDs produce different IDs", func(t *testing.T) { + id1 := hashEventID("keygen", "1") + id2 := hashEventID("keygen", "2") + assert.NotEqual(t, id1, id2) + }) + + t.Run("output is hex string of sha256 length", func(t *testing.T) { + id := hashEventID("type", "id") + assert.Len(t, id, 64) // sha256 = 32 bytes = 64 hex chars + }) +} + +func TestDefaultExpiryOffset(t *testing.T) { + assert.Equal(t, uint64(600), uint64(DefaultExpiryOffset)) +} diff --git a/universalClient/chains/svm/rpc_client.go b/universalClient/chains/svm/rpc_client.go index 4fe0e92f0..5a26aa2fb 100644 --- a/universalClient/chains/svm/rpc_client.go +++ b/universalClient/chains/svm/rpc_client.go @@ -166,6 +166,40 @@ func (rc *RPCClient) GetLatestSlot(ctx context.Context) (uint64, error) { return slot, err } +// LatestFinalizedBlockTime returns the unix timestamp of the latest finalized +// block — the cluster's view of "now" against which on-chain deadline checks +// fire. Used by broadcaster and resolver to gate deadline-based decisions: +// comparing local wall-clock to this value catches host-clock skew, full +// cluster halts (block time stops advancing), and finalization stalls (the +// latest *finalized* block ages even while production continues). +// +// Returns 0 + nil error if block time is unavailable for the latest slot +// (e.g., the slot is too new for the RPC to have indexed). Returns 0 + err +// only when the slot lookup itself fails. +func (rc *RPCClient) LatestFinalizedBlockTime(ctx context.Context) (int64, error) { + slot, err := rc.GetLatestSlot(ctx) + if err != nil { + return 0, err + } + + var blockTime int64 + if err := rc.executeWithFailover(ctx, "get_block_time", func(client *rpc.Client) error { + t, innerErr := client.GetBlockTime(ctx, slot) + if innerErr != nil { + return innerErr + } + if t != nil { + blockTime = int64(*t) + } + return nil + }); err != nil { + // Block-time lookup failed (e.g., slot too recent). Surface 0 so caller + // treats it as "unknown freshness" and defers irreversible decisions. + return 0, nil + } + return blockTime, nil +} + // GetRecentBlockhash gets a recent blockhash for transaction building func (rc *RPCClient) GetRecentBlockhash(ctx context.Context) (solana.Hash, error) { var blockhash solana.Hash @@ -320,7 +354,9 @@ func (rc *RPCClient) SimulateTransaction(ctx context.Context, tx *solana.Transac return result.Value, nil } -// GetAccountData fetches account data for a given public key +// GetAccountData fetches account data for a given public key. Uses Solana +// RPC's default commitment (`finalized`, per the JSON-RPC spec) — reorg-safe +// for both terminal decisions and race-recovery probes. func (rc *RPCClient) GetAccountData(ctx context.Context, pubkey solana.PublicKey) ([]byte, error) { var accountData []byte err := rc.executeWithFailover(ctx, "get_account_data", func(client *rpc.Client) error { diff --git a/universalClient/chains/svm/tx_builder.go b/universalClient/chains/svm/tx_builder.go index 52a56b532..784ac1d3a 100644 --- a/universalClient/chains/svm/tx_builder.go +++ b/universalClient/chains/svm/tx_builder.go @@ -1,60 +1,27 @@ -// Package svm implements the Solana (SVM) transaction builder for Push Chain's -// cross-chain outbound transaction system. +// Package svm implements the Solana transaction builder for Push Chain +// cross-chain outbounds. // -// # How Cross-Chain Outbound Works (High-Level) +// # Two-signature model // -// When a user on Push Chain wants to send funds/execute something on Solana: +// Every gateway tx carries two signatures: +// - TSS (secp256k1/ECDSA): authorizes the cross-chain op. Signs the keccak256 +// of the canonical message; gateway recovers via secp256k1_recover and +// checks against the TSS PDA's stored ETH address. +// - Relayer (Ed25519): standard Solana tx signature. Relayer pays the SOL fee. // -// 1. Push Chain emits an OutboundCreatedEvent with details (amount, recipient, etc.) -// 2. A coordinator node picks up the event -// 3. This TxBuilder constructs the message that needs to be signed (GetOutboundSigningRequest) -// 4. Push Chain validators collectively sign the message using TSS (Threshold Signature Scheme) -// - TSS uses secp256k1 (same curve as Ethereum) — the TSS group has an ETH-style address -// 5. This TxBuilder assembles the full Solana transaction with the TSS signature and broadcasts it -// (BroadcastOutboundSigningRequest) -// 6. The Solana gateway contract verifies the TSS signature on-chain using secp256k1_recover +// # Gateway entry points // -// # Two-Signature Architecture +// - finalize_universal_tx — id=1 withdraw, id=2 execute (CPI). +// - finalize_universal_tx_with_ix_data_ref — same flow but ix_data is loaded +// from a stored PDA (large-payload path). See the Ref-Finalize Route section. +// - revert_universal_tx — id=3, refund-on-failure for SOL and SPL. +// - rescue_funds — id=4, emergency drain of locked vault funds. // -// Every Solana transaction requires TWO different signatures: -// -// - TSS Signature (secp256k1/ECDSA): Signs the message hash. Verified by the gateway contract -// on-chain via secp256k1_recover. This proves the Push Chain validators approved the operation. -// The TSS group's ETH address is stored in the TSS PDA on Solana. -// -// - Relayer Signature (Ed25519): Signs the Solana transaction itself. This is a standard -// Solana transaction signature from the relayer's keypair. The relayer pays for gas (SOL). -// -// # Gateway Contract (Anchor/Rust on Solana) -// -// The gateway is an Anchor program deployed on Solana with these main entry points: -// -// - finalize_universal_tx (instruction_id=1 for withdraw, 2 for execute): -// Unified function that handles both simple fund transfers and arbitrary program execution. -// For withdraw: transfers SOL/SPL from the vault to a recipient. -// For execute: calls an arbitrary Solana program via CPI with provided accounts and data. -// -// - revert_universal_tx (instruction_id=3): Reverts a failed cross-chain tx, returns native SOL. -// -// - revert_universal_tx_token (instruction_id=4): Same but for SPL tokens. -// -// # Key Concepts -// -// - PDA (Program Derived Address): Deterministic addresses derived from seeds + program ID. -// Like CREATE2 in EVM. The gateway uses PDAs for config, vault, TSS state, etc. -// -// - Anchor Discriminator: First 8 bytes of sha256("global:"). Tells the -// Anchor framework which function to call. Similar to EVM function selectors (4 bytes of keccak256). -// -// - Borsh Serialization: Solana's standard binary format. Little-endian integers, -// Vec = 4-byte LE length prefix + elements. Used for instruction data. -// -// - TSS PDA: Stores the TSS group's 20-byte ETH address and chain ID. Replay protection uses per-tx ExecutedTx PDAs. -// -// - CEA (Cross-chain Execution Account): Per-sender identity PDA derived from the EVM sender address. -// -// - ATA (Associated Token Account): Deterministic token account for a wallet + mint pair. -// Like mapping(address => mapping(token => balance)) in EVM, but accounts are explicit on Solana. +// Gateway-internals shorthand used throughout this file: +// - PDA: deterministic address from seeds + program ID (Solana CREATE2 analog). +// - Anchor discriminator: sha256("global:")[:8] — 8-byte function selector. +// - Borsh: Solana's binary encoding — LE integers, Vec = 4-byte LE len + bytes. +// - CEA: per-sender identity PDA used as the CPI signer for execute mode. package svm import ( @@ -81,31 +48,62 @@ import ( uetypes "github.com/pushchain/push-chain-node/x/uexecutor/types" ) -// GatewayAccountMeta represents a single account that a target program needs when executing -// an arbitrary cross-chain call (instruction_id=2). The payload from Push Chain includes a list -// of these — each with the account's public key and whether it needs write access. -// This mirrors the Rust struct in the gateway contract (state.rs). +// ============================================================================= +// Gateway Program Constants +// ============================================================================= + +// Gateway-protocol values — must match the on-chain Rust program. Changing any +// of these requires a coordinated gateway program upgrade. +var ( + // PDA seed prefixes. + configSeed = []byte("config") + vaultSeed = []byte("vault") + feeVaultSeed = []byte("fee_vault") + tssSeed = []byte("final_tss_pda") + executedSubTxSeed = []byte("executed_sub_tx") + ceaAuthoritySeed = []byte("push_identity") + rateLimitConfigSeed = []byte("rate_limit_config") + tokenRateLimitSeed = []byte("rate_limit") + storedIxDataSeed = []byte("stored_ix_data") + + // TSS message envelope — cross-protocol replay guard. + tssMessagePrefix = []byte("PUSH_CHAIN_SVM") + + // Anchor discriminators for ref-finalize. Copied verbatim from the IDL — + // anchorDiscriminator() typos would only fail at runtime as a decode error. + discStoreExecuteIxData = [8]byte{177, 199, 114, 191, 66, 93, 93, 110} + discFinalizeUniversalTxRef = [8]byte{143, 158, 113, 225, 174, 35, 57, 141} + discCloseStoredIxData = [8]byte{58, 81, 153, 208, 99, 218, 247, 14} +) + +// Local policy — universalClient-side routing thresholds and compute budget. +// Safe to tune in a universalClient release without coordinating with the gateway. +const ( + solanaTxMaxBytes = 1232 // Solana hard tx-size limit (legacy and v0) + maxDirectTxSize = 1180 // fall back to ref-route above this; margin absorbs blockhash-encoding variance + maxRefRouteIxData = 921 // ix_data ceiling — store tx itself must fit under solanaTxMaxBytes + defaultComputeUnitLimit = uint32(400_000) // CU budget per gateway tx; covers all flows including CEA execute +) + +// ============================================================================= +// Types +// ============================================================================= + +// GatewayAccountMeta describes one CPI account the target program needs for +// execute-mode (instruction_id=2) outbounds. Mirrors the Rust struct in state.rs. type GatewayAccountMeta struct { - Pubkey [32]byte // Solana public key (32 bytes, not base58-encoded) - IsWritable bool // Whether the target program needs to write to this account + Pubkey [32]byte // raw 32-byte pubkey, not base58 + IsWritable bool } -// TxBuilder constructs and broadcasts Solana transactions for cross-chain operations. -// It implements the common.TxBuilder interface shared with the EVM tx builder. -// -// The builder needs: -// - rpcClient: to talk to a Solana RPC node (fetch account data, send transactions) -// - chainID: identifies the Solana cluster (e.g., "solana:EtWTRABZ..." for devnet) -// - gatewayAddress: the deployed gateway program's public key on Solana -// - nodeHome: filesystem path where the relayer's Solana keypair is stored type TxBuilder struct { rpcClient *RPCClient chainID string gatewayAddress solana.PublicKey nodeHome string logger zerolog.Logger - protocolALT solana.PublicKey // Protocol ALT pubkey (zero if not configured) - tokenALTs map[solana.PublicKey]solana.PublicKey // mint pubkey → token ALT pubkey + protocolALT solana.PublicKey // zero if not configured + tokenALTs map[solana.PublicKey]solana.PublicKey // mint → token ALT } // NewTxBuilder creates a new Solana transaction builder. @@ -326,7 +324,7 @@ func (tb *TxBuilder) GetOutboundSigningRequest( if txType == uetypes.TxType_INBOUND_REVERT || txType == uetypes.TxType_RESCUE_FUNDS { // Revert (id=3) and rescue (id=4): instruction_id determined by TxType, no payload decode - instructionID, err = tb.determineInstructionID(txType, isNative) + instructionID, err = tb.determineInstructionID(txType) if err != nil { return nil, fmt.Errorf("failed to determine instruction ID: %w", err) } @@ -369,24 +367,17 @@ func (tb *TxBuilder) GetOutboundSigningRequest( // If payload was empty/missing, fall back to TxType-derived instruction_id if instructionID == 0 { - fallbackID, fbErr := tb.determineInstructionID(txType, isNative) + fallbackID, fbErr := tb.determineInstructionID(txType) if fbErr != nil { return nil, fmt.Errorf("failed to determine instruction ID: %w", fbErr) } instructionID = fallbackID } - // Validate instruction_id - if instructionID != 1 && instructionID != 2 { - return nil, fmt.Errorf("invalid instruction_id: %d (expected 1=withdraw or 2=execute)", instructionID) - } - - // Validate mode-specific constraints per integration guide + // Mode-specific semantic checks. Shape (instruction_id ∈ {1,2}, withdraw ⇒ + // no accounts/ix_data) is already guaranteed by decodePayload + determineInstructionID. switch instructionID { case 1: // Withdraw mode - if len(accounts) > 0 || len(ixData) > 0 { - return nil, fmt.Errorf("withdraw mode: accounts and ixData must be empty") - } if amount.Uint64() == 0 { return nil, fmt.Errorf("withdraw mode: amount must be > 0") } @@ -405,7 +396,7 @@ func (tb *TxBuilder) GetOutboundSigningRequest( // This message is what TSS validators sign. The gateway contract reconstructs // the same message on-chain and verifies the signature matches. messageHash, err := tb.constructTSSMessage( - instructionID, chainID, amount.Uint64(), + instructionID, chainID, data.SigningDeadline, amount.Uint64(), txID, universalTxID, sender, token, gasFee, targetProgram, accounts, ixData, revertRecipient, revertMint, revertMsg, @@ -420,6 +411,14 @@ func (tb *TxBuilder) GetOutboundSigningRequest( }, nil } +// ============================================================================= +// Transaction Status & Lifecycle Queries +// +// Helpers that report the on-chain progress of an outbound. Used by the +// coordinator (nonce seeding), the broadcaster (replay-check), the resolver +// (terminal-state detection), and the event listener (status confirmation). +// ============================================================================= + // GetNextNonce returns 0 for SVM. The contract no longer uses a global nonce; // replay protection is handled by per-tx ExecutedTx PDAs. func (tb *TxBuilder) GetNextNonce(ctx context.Context, signerAddress string, useFinalized bool) (uint64, error) { @@ -427,32 +426,36 @@ func (tb *TxBuilder) GetNextNonce(ctx context.Context, signerAddress string, use } // IsAlreadyExecuted checks if the ExecutedTx PDA for the given txID exists on-chain, -// indicating another relayer has already processed this transaction. -func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, error) { +// indicating another relayer has already processed this transaction. Also +// returns the latest finalized block's unix timestamp — the cluster's view of +// "now" — so callers can gate deadline-based decisions against cluster time +// rather than the host's local clock. queryBlockTime is best-effort: 0 means +// the RPC couldn't supply it and the caller should treat cluster freshness as +// unknown (defer irreversible decisions). +func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, int64, error) { txIDBytes, err := hex.DecodeString(removeHexPrefix(txID)) if err != nil { - return false, fmt.Errorf("invalid txID: %s", txID) + return false, 0, fmt.Errorf("invalid txID: %s", txID) } if len(txIDBytes) != 32 { - return false, fmt.Errorf("txID must be 32 bytes, got %d", len(txIDBytes)) + return false, 0, fmt.Errorf("txID must be 32 bytes, got %d", len(txIDBytes)) } var txIDArr [32]byte copy(txIDArr[:], txIDBytes) - executedTxPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("executed_sub_tx"), txIDArr[:]}, tb.gatewayAddress) + executedTxPDA, _, err := solana.FindProgramAddress([][]byte{executedSubTxSeed, txIDArr[:]}, tb.gatewayAddress) if err != nil { - return false, fmt.Errorf("failed to derive executed_tx PDA: %w", err) + return false, 0, fmt.Errorf("failed to derive executed_tx PDA: %w", err) } - data, err := tb.rpcClient.GetAccountData(ctx, executedTxPDA) - if err != nil { - // Account doesn't exist or RPC error — treat as not executed - return false, nil - } + data, _ := tb.rpcClient.GetAccountData(ctx, executedTxPDA) + executed := len(data) > 0 + + // Cluster freshness signal — best-effort; 0 on RPC failure. + blockTime, _ := tb.rpcClient.LatestFinalizedBlockTime(ctx) - // If we got non-empty data, the PDA exists → tx was already executed - return len(data) > 0, nil + return executed, blockTime, nil } // GetGasFeeUsed returns "0" for SVM. SVM gas accounting is handled via vault @@ -462,6 +465,44 @@ func (tb *TxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, return "0", nil } +// VerifyBroadcastedTx checks the status of a broadcasted transaction on Solana. +// Returns (found, blockHeight, confirmations, status, error): +// - found=false: tx not found or not yet confirmed +// - found=true: tx exists on-chain +// - confirmations: number of slots since the tx was included (0 = just confirmed) +// - status: 0 = failed, 1 = success +func (tb *TxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) (found bool, blockHeight uint64, confirmations uint64, status uint8, err error) { + sig, sigErr := solana.SignatureFromBase58(txHash) + if sigErr != nil { + return false, 0, 0, 0, nil + } + + tx, txErr := tb.rpcClient.GetTransaction(ctx, sig) + if txErr != nil { + return false, 0, 0, 0, nil + } + + if tx == nil { + return false, 0, 0, 0, nil + } + + // Calculate confirmations from current slot + var confs uint64 + if tx.Slot > 0 { + latestSlot, slotErr := tb.rpcClient.GetLatestSlot(ctx) + if slotErr == nil && latestSlot >= tx.Slot { + confs = latestSlot - tx.Slot + 1 + } + } + + // Check if transaction had an error + if tx.Meta != nil && tx.Meta.Err != nil { + return true, tx.Slot, confs, 0, nil + } + + return true, tx.Slot, confs, 1, nil +} + // ============================================================================= // STEP 2: BroadcastOutboundSigningRequest // @@ -481,7 +522,13 @@ func (tb *TxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, // ============================================================================= // BroadcastOutboundSigningRequest assembles a complete Solana transaction with the -// TSS signature and broadcasts it to the Solana network. +// TSS signature and broadcasts it to the Solana network. For execute-mode +// outbounds (instruction_id=2) whose direct tx exceeds maxDirectTxSize, falls +// back to the 2-tx ref-finalize route automatically. +// +// Returned tx hash is always the FINALIZE tx (direct or ref-finalize). The +// resolver / event listener only need to track this — the store tx is an +// implementation detail invisible to downstream consumers. func (tb *TxBuilder) BroadcastOutboundSigningRequest( ctx context.Context, req *common.UnsignedSigningReq, @@ -493,6 +540,21 @@ func (tb *TxBuilder) BroadcastOutboundSigningRequest( return "", err } + // Ref route is only viable for execute (id=2): + // - id=1 (withdraw) carries empty ix_data → can't overflow, and the + // store instruction would reject it with EmptyIxData anyway. + // - id=3/4 (revert/rescue) go through separate gateway entrypoints + // (revert_universal_tx / rescue_funds) with no ref-route counterpart. + if instructionID == 2 { + if txBytes, mErr := tx.MarshalBinary(); mErr == nil && len(txBytes) > maxDirectTxSize { + tb.logger.Info(). + Int("direct_tx_bytes", len(txBytes)). + Int("threshold", maxDirectTxSize). + Msg("direct finalize exceeds tx size threshold, switching to ref-finalize route") + return tb.broadcastRefRoute(ctx, req, data, signature) + } + } + txHash, err := tb.rpcClient.BroadcastTransaction(ctx, tx) if err != nil { return "", fmt.Errorf("failed to broadcast transaction: %w", err) @@ -506,6 +568,59 @@ func (tb *TxBuilder) BroadcastOutboundSigningRequest( return txHash, nil } +// storedPDAExists is the race-recovery probe — if the PDA is on-chain we can +// proceed to finalize regardless of whose store_execute_ix_data put it there. +func (tb *TxBuilder) storedPDAExists(ctx context.Context, storedPDA solana.PublicKey) bool { + data, _ := tb.rpcClient.GetAccountData(ctx, storedPDA) + return len(data) > 0 +} + +// broadcastRefRoute drives the 2-tx ref-finalize flow as a tick-based state +// machine — at most ONE action per broadcaster tick: +// +// - PDA exists on-chain → broadcast finalize, return tx hash. +// - PDA absent → broadcast store, return non-nil error so the +// broadcaster counts it as a failed attempt and retries next tick. The +// happy path: tick N broadcasts store, tick N+1 (15s later, after ~13s +// Finalized) sees the PDA and broadcasts finalize. +// +// PDA is content-addressed by (sub_tx_id, keccak256(ix_data)); every validator +// derives the same address. Only one store wins on-chain (Anchor `init` dedups); +// losers see AccountAlreadyInUse — the broadcaster's retry handles it. +func (tb *TxBuilder) broadcastRefRoute( + ctx context.Context, + req *common.UnsignedSigningReq, + data *uetypes.OutboundCreatedEvent, + signature []byte, +) (string, error) { + storeTx, refTx, storedPDA, err := tb.BuildRefRouteTransactions(ctx, req, data, signature) + if err != nil { + return "", fmt.Errorf("failed to build ref-route transactions: %w", err) + } + + if tb.storedPDAExists(ctx, storedPDA) { + refHash, err := tb.rpcClient.BroadcastTransaction(ctx, refTx) + if err != nil { + return "", fmt.Errorf("failed to broadcast finalize_universal_tx_with_ix_data_ref: %w", err) + } + tb.logger.Info(). + Str("tx_hash", refHash). + Str("stored_pda", storedPDA.String()). + Msg("ref-finalize broadcast successfully") + return refHash, nil + } + + storeHash, broadcastErr := tb.rpcClient.BroadcastTransaction(ctx, storeTx) + if broadcastErr != nil { + return "", fmt.Errorf("failed to broadcast store_execute_ix_data: %w", broadcastErr) + } + tb.logger.Info(). + Str("store_tx_hash", storeHash). + Str("stored_pda", storedPDA.String()). + Msg("store_execute_ix_data broadcast; finalize deferred to next tick") + return "", fmt.Errorf("store_execute_ix_data broadcast; finalize will be attempted on next broadcaster tick") +} + // fetchAddressTables fetches Address Lookup Table state for V0 transactions. // Always includes the protocol ALT (if configured). For SPL tokens, also includes // the token-specific ALT for the given mint (if configured). @@ -674,7 +789,7 @@ func (tb *TxBuilder) BuildOutboundTransaction( if txType == uetypes.TxType_INBOUND_REVERT || txType == uetypes.TxType_RESCUE_FUNDS { // Revert (id=3) and rescue (id=4): instruction_id determined by TxType, no payload decode var idErr error - instructionID, idErr = tb.determineInstructionID(txType, isNative) + instructionID, idErr = tb.determineInstructionID(txType) if idErr != nil { return nil, 0, fmt.Errorf("failed to determine instruction ID: %w", idErr) } @@ -696,41 +811,37 @@ func (tb *TxBuilder) BuildOutboundTransaction( // Fall back to TxType if payload was empty if instructionID == 0 { - fallbackID, fbErr := tb.determineInstructionID(txType, isNative) + fallbackID, fbErr := tb.determineInstructionID(txType) if fbErr != nil { return nil, 0, fmt.Errorf("failed to determine instruction ID: %w", fbErr) } instructionID = fallbackID } - - if instructionID != 1 && instructionID != 2 { - return nil, 0, fmt.Errorf("invalid instruction_id: %d", instructionID) - } } // --- Derive PDAs --- - configPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("config")}, tb.gatewayAddress) + configPDA, _, err := solana.FindProgramAddress([][]byte{configSeed}, tb.gatewayAddress) if err != nil { return nil, 0, fmt.Errorf("failed to derive config PDA: %w", err) } - vaultPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("vault")}, tb.gatewayAddress) + vaultPDA, _, err := solana.FindProgramAddress([][]byte{vaultSeed}, tb.gatewayAddress) if err != nil { return nil, 0, fmt.Errorf("failed to derive vault PDA: %w", err) } - tssPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("final_tss_pda")}, tb.gatewayAddress) + tssPDA, _, err := solana.FindProgramAddress([][]byte{tssSeed}, tb.gatewayAddress) if err != nil { return nil, 0, fmt.Errorf("failed to derive TSS PDA: %w", err) } - executedTxPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("executed_sub_tx"), txID[:]}, tb.gatewayAddress) + executedTxPDA, _, err := solana.FindProgramAddress([][]byte{executedSubTxSeed, txID[:]}, tb.gatewayAddress) if err != nil { return nil, 0, fmt.Errorf("failed to derive executed_tx PDA: %w", err) } // --- Derive fee_vault PDA (needed for revert and rescue) --- - feeVaultPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("fee_vault")}, tb.gatewayAddress) + feeVaultPDA, _, err := solana.FindProgramAddress([][]byte{feeVaultSeed}, tb.gatewayAddress) if err != nil { return nil, 0, fmt.Errorf("failed to derive fee_vault PDA: %w", err) } @@ -756,14 +867,14 @@ func (tb *TxBuilder) BuildOutboundTransaction( targetProgram = solana.SystemProgramID } - ceaAuthorityPDA, _, ceaErr := solana.FindProgramAddress([][]byte{[]byte("push_identity"), sender[:]}, tb.gatewayAddress) + ceaAuthorityPDA, _, ceaErr := solana.FindProgramAddress([][]byte{ceaAuthoritySeed, sender[:]}, tb.gatewayAddress) if ceaErr != nil { return nil, 0, fmt.Errorf("failed to derive cea_authority PDA: %w", ceaErr) } instructionData = tb.buildWithdrawAndExecuteData( instructionID, txID, universalTxID, amount.Uint64(), sender, - writableFlags, ixData, gasFee, + writableFlags, ixData, gasFee, data.SigningDeadline, signature, recoveryID, req.SigningHash, ) @@ -774,13 +885,14 @@ func (tb *TxBuilder) BuildOutboundTransaction( isNative, instructionID, recipientPubkey, mintPubkey, execAccounts, + solana.PublicKey{}, solana.PublicKey{}, // direct route: None sentinels for stored_ix_data + store_refund_recipient ) case instructionID == 3: // ---- revert_universal_tx (unified for SOL and SPL) ---- instructionData = tb.buildRevertData( txID, universalTxID, amount.Uint64(), - recipientPubkey, revertMsgBytes, gasFee, + recipientPubkey, revertMsgBytes, gasFee, data.SigningDeadline, signature, recoveryID, req.SigningHash, ) accounts = tb.buildRevertAccounts( @@ -792,7 +904,7 @@ func (tb *TxBuilder) BuildOutboundTransaction( case instructionID == 4: // ---- rescue_funds ---- instructionData = tb.buildRescueData( - txID, universalTxID, amount.Uint64(), gasFee, + txID, universalTxID, amount.Uint64(), gasFee, data.SigningDeadline, signature, recoveryID, req.SigningHash, ) accounts = tb.buildRescueAccounts( @@ -814,11 +926,9 @@ func (tb *TxBuilder) BuildOutboundTransaction( instructionData, ) - // Hardcoded compute budget for Solana transactions. The event's gasLimit is a fee - // parameter (used by core for gasFee = gasPrice × gasLimit), not actual compute units. - // 400,000 CU is sufficient for all gateway operations including CEA execute flows. - const svmComputeUnitLimit = uint32(400_000) - computeLimitIx := tb.buildSetComputeUnitLimitInstruction(svmComputeUnitLimit) + // Event's gasLimit is a fee parameter (gasFee = gasPrice × gasLimit), not + // actual compute units; we always allocate defaultComputeUnitLimit instead. + computeLimitIx := tb.buildSetComputeUnitLimitInstruction(defaultComputeUnitLimit) // Build the instruction list. instructions := []solana.Instruction{computeLimitIx} @@ -867,20 +977,299 @@ func (tb *TxBuilder) BuildOutboundTransaction( return nil, 0, fmt.Errorf("failed to sign transaction: %w", err) } - // Warn if transaction exceeds Solana's 1232-byte raw limit. + // Warn if transaction exceeds Solana's raw tx limit. if txBytes, marshalErr := tx.MarshalBinary(); marshalErr == nil { - if len(txBytes) > 1232 { + if len(txBytes) > solanaTxMaxBytes { tb.logger.Warn(). Int("raw_bytes", len(txBytes)). + Int("limit", solanaTxMaxBytes). Int("ix_data_bytes", len(ixData)). Uint8("instruction_id", instructionID). - Msg("transaction exceeds 1232-byte Solana limit") + Msg("transaction exceeds Solana raw tx limit") } } return tx, instructionID, nil } +// ============================================================================= +// STEP 2b: BuildRefRouteTransactions +// +// For execute-mode outbounds whose direct finalize_universal_tx exceeds +// Solana's 1232-byte limit, the universal validator splits the work into +// two transactions: +// +// 1. store_execute_ix_data — relayer-signed only (no TSS involvement); +// uploads raw ix_data into a content-addressed PDA. +// 2. finalize_universal_tx_with_ix_data_ref — uses the SAME TSS signature +// as the direct route; gateway reconstructs the message from stored bytes. +// +// NOTE: parsing duplicates BuildOutboundTransaction. Future refactor should +// hoist the parse into a shared helper. For now the duplication is bounded +// to execute mode (id=2); revert/rescue (3/4) never use this path. +// ============================================================================= + +// BuildRefRouteTransactions builds the (storeTx, refFinalizeTx) pair for a +// large-payload execute outbound. Only valid for instructionID=2 with non-empty +// ix_data; callers should size-check the direct tx first and only invoke this +// when the direct route doesn't fit. +// +// Returns the storedIxData PDA alongside the txs so the broadcaster can probe +// for pre-existing PDAs (retry idempotency) before re-broadcasting the store tx. +func (tb *TxBuilder) BuildRefRouteTransactions( + ctx context.Context, + req *common.UnsignedSigningReq, + data *uetypes.OutboundCreatedEvent, + signature []byte, +) (*solana.Transaction, *solana.Transaction, solana.PublicKey, error) { + if req == nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("signing request is nil") + } + if data == nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("outbound event data is nil") + } + if len(signature) != 65 { + return nil, nil, solana.PublicKey{}, fmt.Errorf("signature must be 65 bytes, got %d", len(signature)) + } + + recoveryID := signature[64] + signature = signature[:64] + + relayerKeypair, err := tb.loadRelayerKeypair() + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to load relayer keypair: %w", err) + } + + // --- Parse event (mirrors BuildOutboundTransaction; execute path only) --- + + amount := new(big.Int) + amount, ok := amount.SetString(data.Amount, 10) + if !ok { + return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid amount: %s", data.Amount) + } + if !amount.IsUint64() { + return nil, nil, solana.PublicKey{}, fmt.Errorf("amount exceeds u64 max: %s", data.Amount) + } + + assetAddr := data.AssetAddr + isNative := assetAddr == "" || assetAddr == "0x0" || assetAddr == "0x0000000000000000000000000000000000000000" + + var txID [32]byte + txIDBytes, err := hex.DecodeString(removeHexPrefix(data.TxID)) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid txID: %s", data.TxID) + } + if len(txIDBytes) == 32 { + copy(txID[:], txIDBytes) + } else if len(txIDBytes) > 0 { + copy(txID[32-len(txIDBytes):], txIDBytes) + } + + var universalTxID [32]byte + utxIDBytes, err := hex.DecodeString(removeHexPrefix(data.UniversalTxId)) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid universalTxID: %s", data.UniversalTxId) + } + if len(utxIDBytes) == 32 { + copy(universalTxID[:], utxIDBytes) + } else if len(utxIDBytes) > 0 { + copy(universalTxID[32-len(utxIDBytes):], utxIDBytes) + } + + var sender [20]byte + senderBytes, err := hex.DecodeString(removeHexPrefix(data.Sender)) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid sender: %s", data.Sender) + } + if len(senderBytes) == 20 { + copy(sender[:], senderBytes) + } else { + return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid sender length: expected 20 bytes, got %d", len(senderBytes)) + } + + var mintPubkey solana.PublicKey + if !isNative { + mintPubkey, err = solana.PublicKeyFromBase58(assetAddr) + if err != nil { + hexBytes, hexErr := hex.DecodeString(removeHexPrefix(assetAddr)) + if hexErr != nil || len(hexBytes) != 32 { + return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid asset address format: %s", assetAddr) + } + mintPubkey = solana.PublicKeyFromBytes(hexBytes) + } + } + + var gasFee uint64 + if data.GasFee != "" { + gasFee, _ = strconv.ParseUint(data.GasFee, 10, 64) + } + + recipientPubkey, err := solana.PublicKeyFromBase58(data.Recipient) + if err != nil { + hexBytes, hexErr := hex.DecodeString(removeHexPrefix(data.Recipient)) + if hexErr != nil || len(hexBytes) != 32 { + return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid recipient address format: %s", data.Recipient) + } + recipientPubkey = solana.PublicKeyFromBytes(hexBytes) + } + + // Decode payload — ref route is execute-only, so we require an instruction_id of 2. + var execAccounts []GatewayAccountMeta + var ixData []byte + var instructionID uint8 + payloadHex := removeHexPrefix(data.Payload) + if payloadHex != "" { + payloadBytes, decErr := hex.DecodeString(payloadHex) + if decErr != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to decode payload hex: %w", decErr) + } + if len(payloadBytes) > 0 { + execAccounts, ixData, instructionID, _, err = decodePayload(payloadBytes) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to decode payload: %w", err) + } + } + } + if instructionID != 2 { + return nil, nil, solana.PublicKey{}, fmt.Errorf("ref route only valid for execute mode (instruction_id=2), got %d", instructionID) + } + if len(ixData) == 0 { + return nil, nil, solana.PublicKey{}, fmt.Errorf("ref route requires non-empty ix_data") + } + if len(ixData) > maxRefRouteIxData { + return nil, nil, solana.PublicKey{}, fmt.Errorf("ix_data size %d exceeds ref-route max %d (store tx would itself exceed %d-byte limit)", len(ixData), maxRefRouteIxData, solanaTxMaxBytes) + } + + // --- Derive PDAs --- + + configPDA, _, err := solana.FindProgramAddress([][]byte{configSeed}, tb.gatewayAddress) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to derive config PDA: %w", err) + } + vaultPDA, _, err := solana.FindProgramAddress([][]byte{vaultSeed}, tb.gatewayAddress) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to derive vault PDA: %w", err) + } + tssPDA, _, err := solana.FindProgramAddress([][]byte{tssSeed}, tb.gatewayAddress) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to derive TSS PDA: %w", err) + } + executedTxPDA, _, err := solana.FindProgramAddress([][]byte{executedSubTxSeed, txID[:]}, tb.gatewayAddress) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to derive executed_tx PDA: %w", err) + } + ceaAuthorityPDA, _, err := solana.FindProgramAddress([][]byte{ceaAuthoritySeed, sender[:]}, tb.gatewayAddress) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to derive cea_authority PDA: %w", err) + } + + // Content-addressed stored_ix_data PDA: ["stored_ix_data", sub_tx_id, keccak256(ix_data)] + ixDataHashSlice := crypto.Keccak256(ixData) + var ixDataHash [32]byte + copy(ixDataHash[:], ixDataHashSlice) + storedIxDataPDA, err := tb.deriveStoredIxDataPDA(txID, ixDataHash) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to derive stored_ix_data PDA: %w", err) + } + + // Resolve store_refund_recipient: + // - If the PDA already exists on-chain (another validator won the store + // race), the contract enforces store_refund_recipient.key() == stored + // value, so we must echo whatever's already stored — not our own key. + // - Otherwise we'll be the one creating the PDA, so our relayer is right. + storeRefundRecipient := relayerKeypair.PublicKey() + if existing, _ := tb.rpcClient.GetAccountData(ctx, storedIxDataPDA); len(existing) >= storedIxDataRefundRecipientOffset+32 { + copy(storeRefundRecipient[:], existing[storedIxDataRefundRecipientOffset:storedIxDataRefundRecipientOffset+32]) + } + + // --- Build store_execute_ix_data tx (relayer-signed only, no TSS) --- + + recentBlockhash, err := tb.rpcClient.GetRecentBlockhash(ctx) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to get recent blockhash: %w", err) + } + + storeData := tb.buildStoreIxDataData(txID, ixDataHash, ixData) + storeAccounts := tb.buildStoreIxDataAccounts(relayerKeypair.PublicKey(), storedIxDataPDA) + storeInstruction := solana.NewInstruction(tb.gatewayAddress, storeAccounts, storeData) + + storeTx, err := solana.NewTransaction( + []solana.Instruction{storeInstruction}, + recentBlockhash, + solana.TransactionPayer(relayerKeypair.PublicKey()), + ) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to create store tx: %w", err) + } + if _, err := storeTx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if key.Equals(relayerKeypair.PublicKey()) { + priv := relayerKeypair + return &priv + } + return nil + }); err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to sign store tx: %w", err) + } + + // --- Build finalize_universal_tx_with_ix_data_ref tx (TSS-signed) --- + + writableFlags := accountsToWritableFlags(execAccounts) + refInstructionData := tb.buildWithdrawAndExecuteRefData( + 2, // execute + txID, universalTxID, amount.Uint64(), sender, + ixDataHash, + writableFlags, + gasFee, + data.SigningDeadline, + signature, recoveryID, req.SigningHash, + ) + + refAccounts := tb.buildWithdrawAndExecuteAccounts( + relayerKeypair.PublicKey(), + configPDA, vaultPDA, ceaAuthorityPDA, tssPDA, executedTxPDA, + recipientPubkey, // destination_program (target of CPI) + isNative, 2, // execute + recipientPubkey, mintPubkey, + execAccounts, + storedIxDataPDA, storeRefundRecipient, // ref route: real values + ) + + refInstruction := solana.NewInstruction(tb.gatewayAddress, refAccounts, refInstructionData) + computeLimitIx := tb.buildSetComputeUnitLimitInstruction(defaultComputeUnitLimit) + + instructions := []solana.Instruction{computeLimitIx} + needsRecipientATA := !isNative && false // execute mode (id=2) doesn't create recipient ATA; gateway handles cea_ata internally + if needsRecipientATA { + instructions = append(instructions, tb.buildCreateATAIdempotentInstruction( + relayerKeypair.PublicKey(), recipientPubkey, mintPubkey, + )) + } + instructions = append(instructions, refInstruction) + + refOpts := []solana.TransactionOption{solana.TransactionPayer(relayerKeypair.PublicKey())} + addressTables, altErr := tb.fetchAddressTables(ctx, mintPubkey, isNative) + if altErr != nil { + tb.logger.Warn().Err(altErr).Msg("failed to fetch ALTs for ref-finalize, falling back to legacy tx") + } else if len(addressTables) > 0 { + refOpts = append(refOpts, solana.TransactionAddressTables(addressTables)) + } + refTx, err := solana.NewTransaction(instructions, recentBlockhash, refOpts...) + if err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to create ref-finalize tx: %w", err) + } + if _, err := refTx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if key.Equals(relayerKeypair.PublicKey()) { + priv := relayerKeypair + return &priv + } + return nil + }); err != nil { + return nil, nil, solana.PublicKey{}, fmt.Errorf("failed to sign ref-finalize tx: %w", err) + } + + return storeTx, refTx, storedIxDataPDA, nil +} + // ============================================================================= // Helper Functions // ============================================================================= @@ -904,7 +1293,7 @@ func removeHexPrefix(s string) string { // // Seed: ["final_tss_pda"] — must match the Rust constant TSS_SEED in state.rs func (tb *TxBuilder) deriveTSSPDA() (solana.PublicKey, error) { - seeds := [][]byte{[]byte("final_tss_pda")} + seeds := [][]byte{tssSeed} address, _, err := solana.FindProgramAddress(seeds, tb.gatewayAddress) return address, err } @@ -948,16 +1337,14 @@ func (tb *TxBuilder) fetchTSSChainID(ctx context.Context, tssPDA solana.PublicKe // Instruction ID Mapping // ============================================================================= -// determineInstructionID maps the Push Chain TxType + asset type to the gateway's instruction ID. -// -// The gateway contract uses these IDs in the TSS message and the instruction data: +// determineInstructionID maps the Push Chain TxType to the gateway's instruction ID. // // ID Function When -// 1 finalize_universal_tx FUNDS (withdraw mode): send SOL or SPL tokens to a recipient -// 2 finalize_universal_tx FUNDS_AND_PAYLOAD or GAS_AND_PAYLOAD (execute mode): call a program -// 3 revert_universal_tx INBOUND_REVERT: unified revert for both SOL and SPL -// 4 rescue_funds RESCUE_FUNDS: emergency rescue of locked funds -func (tb *TxBuilder) determineInstructionID(txType uetypes.TxType, isNative bool) (uint8, error) { +// 1 finalize_universal_tx FUNDS (withdraw mode) +// 2 finalize_universal_tx FUNDS_AND_PAYLOAD or GAS_AND_PAYLOAD (execute mode) +// 3 revert_universal_tx INBOUND_REVERT (unified SOL + SPL) +// 4 rescue_funds RESCUE_FUNDS +func (tb *TxBuilder) determineInstructionID(txType uetypes.TxType) (uint8, error) { switch txType { case uetypes.TxType_FUNDS: return 1, nil @@ -1009,6 +1396,7 @@ func (tb *TxBuilder) determineInstructionID(txType uetypes.TxType, isNative bool func (tb *TxBuilder) constructTSSMessage( instructionID uint8, chainID string, + deadlineUnix int64, amount uint64, txID [32]byte, universalTxID [32]byte, @@ -1022,10 +1410,16 @@ func (tb *TxBuilder) constructTSSMessage( revertMint [32]byte, revertMsg []byte, ) ([]byte, error) { - message := []byte("PUSH_CHAIN_SVM") + // Wire format expected by the SVM gateway program's validate_message: + // PREFIX || instruction_id || chain_id || deadline(i64 BE) || amount(u64 BE) || additional_data + message := append([]byte(nil), tssMessagePrefix...) message = append(message, instructionID) message = append(message, []byte(chainID)...) + deadlineBytes := make([]byte, 8) + binary.BigEndian.PutUint64(deadlineBytes, uint64(deadlineUnix)) + message = append(message, deadlineBytes...) + amountBytes := make([]byte, 8) binary.BigEndian.PutUint64(amountBytes, amount) message = append(message, amountBytes...) @@ -1174,63 +1568,77 @@ func (tb *TxBuilder) loadRelayerKeypair() (solana.PrivateKey, error) { // The payload is built off-chain and encodes the operation type plus any // target program data needed for execution: // -// [u32 BE] accounts_count — how many accounts the target program needs -// [33 bytes] × N accounts — each is [pubkey(32) + is_writable(1)] -// [u32 BE] ix_data_len — length of the instruction data for the target program -// [N bytes] ix_data — the raw instruction data to pass to the target program -// [u8] instruction_id — 1=withdraw, 2=execute -// [32 bytes] target_program — the Solana program to invoke +// bytes [0 .. 4) accountsCount u32 — number of CPI accounts (N) +// bytes [4 .. 4+33N) accounts N × {pubkey[32], isWritable[1]} +// bytes [4+33N .. 8+33N) ixDataLen u32 — length of ix_data in bytes (M) +// bytes [8+33N .. 8+33N+M) ixData M raw bytes for the target program +// byte [8+33N+M] instructionID u8 — 1=withdraw, 2=execute +// bytes [9+33N+M .. 41+33N+M) targetProgram 32 bytes — the Solana program to invoke // // For withdraw (instruction_id=1): accounts_count=0, ix_data_len=0 -// For execute (instruction_id=2): accounts and ix_data contain CPI data +// For execute (instruction_id=2): accounts and ix_data contain CPI data func decodePayload(payload []byte) ([]GatewayAccountMeta, []byte, uint8, [32]byte, error) { + const ( + sizeAccountsCount = 4 + sizeAccount = 33 // 32-byte pubkey + 1-byte is_writable + sizeIxDataLen = 4 + sizeInstructionID = 1 + sizeTargetProgram = 32 + + minPayloadLen = sizeAccountsCount + sizeIxDataLen + sizeInstructionID + sizeTargetProgram + ) + var targetProgram [32]byte - // Minimum payload: accounts_count(4) + ix_data_len(4) + instruction_id(1) + target_program(32) = 41 - if len(payload) < 41 { - return nil, nil, 0, targetProgram, fmt.Errorf("payload too short: %d bytes (minimum 41)", len(payload)) - } + // --- Validate structure and bounds --- - offset := 0 + if len(payload) < minPayloadLen { + return nil, nil, 0, targetProgram, fmt.Errorf("payload too short: %d bytes (minimum %d)", len(payload), minPayloadLen) + } - accountsCount := binary.BigEndian.Uint32(payload[offset : offset+4]) - offset += 4 + accountsCount := binary.BigEndian.Uint32(payload[0:sizeAccountsCount]) - accounts := make([]GatewayAccountMeta, accountsCount) - for i := uint32(0); i < accountsCount; i++ { - if offset+33 > len(payload) { - return nil, nil, 0, targetProgram, fmt.Errorf("payload too short for account %d", i) - } - var pubkey [32]byte - copy(pubkey[:], payload[offset:offset+32]) - isWritable := payload[offset+32] == 1 - accounts[i] = GatewayAccountMeta{Pubkey: pubkey, IsWritable: isWritable} - offset += 33 + ixDataLenOff := uint64(sizeAccountsCount) + uint64(accountsCount)*sizeAccount + if ixDataLenOff+sizeIxDataLen > uint64(len(payload)) { + return nil, nil, 0, targetProgram, fmt.Errorf("payload too short for %d accounts: need %d bytes through ix_data length, have %d", accountsCount, ixDataLenOff+sizeIxDataLen, len(payload)) } + // Past this check ixDataLenOff is bounded by len(payload), so int conversion is safe. + ixDataLen := binary.BigEndian.Uint32(payload[ixDataLenOff : ixDataLenOff+sizeIxDataLen]) - if offset+4 > len(payload) { - return nil, nil, 0, targetProgram, fmt.Errorf("payload too short for ix_data length") + // Payload must be consumed exactly — no truncation, no trailing bytes. + expectedLen := ixDataLenOff + sizeIxDataLen + uint64(ixDataLen) + sizeInstructionID + sizeTargetProgram + switch { + case uint64(len(payload)) < expectedLen: + return nil, nil, 0, targetProgram, fmt.Errorf("payload too short: expected %d bytes, got %d (accountsCount=%d, ixDataLen=%d)", expectedLen, len(payload), accountsCount, ixDataLen) + case uint64(len(payload)) > expectedLen: + return nil, nil, 0, targetProgram, fmt.Errorf("payload has %d trailing bytes (expected %d, got %d)", uint64(len(payload))-expectedLen, expectedLen, len(payload)) } - ixDataLen := binary.BigEndian.Uint32(payload[offset : offset+4]) - offset += 4 - if offset+int(ixDataLen) > len(payload) { - return nil, nil, 0, targetProgram, fmt.Errorf("payload too short for ix_data") - } - ixData := make([]byte, ixDataLen) - copy(ixData, payload[offset:offset+int(ixDataLen)]) - offset += int(ixDataLen) + ixDataOff := int(ixDataLenOff) + sizeIxDataLen + instrIDOff := ixDataOff + int(ixDataLen) + targetOff := instrIDOff + sizeInstructionID - if offset >= len(payload) { - return nil, nil, 0, targetProgram, fmt.Errorf("payload too short for instruction_id") + instructionID := payload[instrIDOff] + if instructionID != 1 && instructionID != 2 { + return nil, nil, 0, targetProgram, fmt.Errorf("invalid instruction_id %d (expected 1=withdraw or 2=execute)", instructionID) + } + if instructionID == 1 && (accountsCount != 0 || ixDataLen != 0) { + return nil, nil, 0, targetProgram, fmt.Errorf("withdraw payload must have accountsCount=0 and ixDataLen=0, got %d/%d", accountsCount, ixDataLen) } - instructionID := payload[offset] - offset++ - if offset+32 > len(payload) { - return nil, nil, 0, targetProgram, fmt.Errorf("payload too short for target_program") + // --- Parse validated payload --- + + accounts := make([]GatewayAccountMeta, accountsCount) + for i := range accountsCount { + base := sizeAccountsCount + int(i)*sizeAccount + copy(accounts[i].Pubkey[:], payload[base:base+32]) + accounts[i].IsWritable = payload[base+32] == 1 } - copy(targetProgram[:], payload[offset:offset+32]) + + ixData := make([]byte, ixDataLen) + copy(ixData, payload[ixDataOff:instrIDOff]) + + copy(targetProgram[:], payload[targetOff:targetOff+sizeTargetProgram]) return accounts, ixData, instructionID, targetProgram, nil } @@ -1310,6 +1718,7 @@ func (tb *TxBuilder) buildWithdrawAndExecuteData( writableFlags []byte, ixData []byte, gasFee uint64, + deadlineUnix int64, signature []byte, recoveryID byte, messageHash []byte, @@ -1343,6 +1752,10 @@ func (tb *TxBuilder) buildWithdrawAndExecuteData( binary.LittleEndian.PutUint64(gasFeeBytes, gasFee) data = append(data, gasFeeBytes...) + deadlineBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(deadlineBytes, uint64(deadlineUnix)) + data = append(data, deadlineBytes...) + data = append(data, signature...) data = append(data, recoveryID) data = append(data, messageHash...) @@ -1371,6 +1784,7 @@ func (tb *TxBuilder) buildRevertData( revertRecipient solana.PublicKey, revertMsg []byte, gasFee uint64, + deadlineUnix int64, signature []byte, recoveryID byte, messageHash []byte, @@ -1397,6 +1811,10 @@ func (tb *TxBuilder) buildRevertData( binary.LittleEndian.PutUint64(gasFeeBytes, gasFee) data = append(data, gasFeeBytes...) + deadlineBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(deadlineBytes, uint64(deadlineUnix)) + data = append(data, deadlineBytes...) + data = append(data, signature...) data = append(data, recoveryID) data = append(data, messageHash...) @@ -1421,6 +1839,7 @@ func (tb *TxBuilder) buildRescueData( universalTxID [32]byte, amount uint64, gasFee uint64, + deadlineUnix int64, signature []byte, recoveryID byte, messageHash []byte, @@ -1440,6 +1859,10 @@ func (tb *TxBuilder) buildRescueData( binary.LittleEndian.PutUint64(gasFeeBytes, gasFee) data = append(data, gasFeeBytes...) + deadlineBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(deadlineBytes, uint64(deadlineUnix)) + data = append(data, deadlineBytes...) + data = append(data, signature...) data = append(data, recoveryID) data = append(data, messageHash...) @@ -1483,11 +1906,19 @@ func (tb *TxBuilder) buildRescueData( // --- Optional rate limit accounts (17-18) --- // 17 rate_limit_config read/None Rate limit config PDA ["rate_limit_config"] (required when destination=gateway ie CEA path only)) // 18 token_rate_limit mut/None Token rate limit PDA ["rate_limit", mint] (required when destination=gateway ie CEA path only) +// --- Optional ref-finalize accounts (19-20) --- +// 19 stored_ix_data read/None StoredIxData PDA (only used by ref-finalize route) +// 20 store_refund_recipient mut/None Receives store-tx fee reimbursement (ref route only) // --- Execute-only remaining accounts --- -// 19+ remaining_accounts varies Accounts that the target program needs +// 21+ remaining_accounts varies Accounts that the target program needs // // For Anchor Option fields: passing the gateway program's own ID = None. // This is Anchor's convention for encoding "this optional account is not provided". +// +// storedIxDataPDA and storeRefundRecipient are zero-valued for the direct route +// (None sentinels emitted) and set to real values for the ref-finalize route. +// They must always occupy positions 19-20, otherwise remaining_accounts (CPI +// accounts for execute mode) shift up and Anchor misinterprets them. func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( caller solana.PublicKey, configPDA solana.PublicKey, @@ -1501,6 +1932,8 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( recipientPubkey solana.PublicKey, mintPubkey solana.PublicKey, execAccounts []GatewayAccountMeta, + storedIxDataPDA solana.PublicKey, + storeRefundRecipient solana.PublicKey, ) []*solana.AccountMeta { // First 8 required accounts (always present) accounts := []*solana.AccountMeta{ @@ -1530,16 +1963,13 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( } } else { // SPL token flow: derive and pass real ATAs - ataProgramID := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") - rentSysvar := solana.MustPublicKeyFromBase58("SysvarRent111111111111111111111111111111111") - vaultATA, _, _ := solana.FindProgramAddress( [][]byte{accounts[2].PublicKey.Bytes(), solana.TokenProgramID.Bytes(), mintPubkey.Bytes()}, - ataProgramID, + solana.SPLAssociatedTokenAccountProgramID, ) ceaATA, _, _ := solana.FindProgramAddress( [][]byte{ceaAuthorityPDA.Bytes(), solana.TokenProgramID.Bytes(), mintPubkey.Bytes()}, - ataProgramID, + solana.SPLAssociatedTokenAccountProgramID, ) if instructionID == 1 { @@ -1551,13 +1981,13 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( accounts = append(accounts, &solana.AccountMeta{PublicKey: ceaATA, IsWritable: true, IsSigner: false}) accounts = append(accounts, &solana.AccountMeta{PublicKey: mintPubkey, IsWritable: false, IsSigner: false}) accounts = append(accounts, &solana.AccountMeta{PublicKey: solana.TokenProgramID, IsWritable: false, IsSigner: false}) - accounts = append(accounts, &solana.AccountMeta{PublicKey: rentSysvar, IsWritable: false, IsSigner: false}) - accounts = append(accounts, &solana.AccountMeta{PublicKey: ataProgramID, IsWritable: false, IsSigner: false}) + accounts = append(accounts, &solana.AccountMeta{PublicKey: solana.SysVarRentPubkey, IsWritable: false, IsSigner: false}) + accounts = append(accounts, &solana.AccountMeta{PublicKey: solana.SPLAssociatedTokenAccountProgramID, IsWritable: false, IsSigner: false}) if instructionID == 1 { recipientATA, _, _ := solana.FindProgramAddress( [][]byte{recipientPubkey.Bytes(), solana.TokenProgramID.Bytes(), mintPubkey.Bytes()}, - ataProgramID, + solana.SPLAssociatedTokenAccountProgramID, ) accounts = append(accounts, &solana.AccountMeta{PublicKey: recipientATA, IsWritable: true, IsSigner: false}) } else { @@ -1569,7 +1999,7 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( // When destination is the gateway itself (CEA→UEA), pass real rate limit PDAs. // Otherwise, pass None (gateway program ID sentinel). if destinationProgram.Equals(tb.gatewayAddress) { - rateLimitConfigPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("rate_limit_config")}, tb.gatewayAddress) + rateLimitConfigPDA, _, _ := solana.FindProgramAddress([][]byte{rateLimitConfigSeed}, tb.gatewayAddress) accounts = append(accounts, &solana.AccountMeta{PublicKey: rateLimitConfigPDA, IsWritable: false, IsSigner: false}) // token_rate_limit PDA: seeds = ["rate_limit", token_mint] @@ -1579,7 +2009,7 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( rateLimitMint = mintPubkey } // rateLimitMint is zero-value (Pubkey::default()) for native SOL - tokenRateLimitPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("rate_limit"), rateLimitMint.Bytes()}, tb.gatewayAddress) + tokenRateLimitPDA, _, _ := solana.FindProgramAddress([][]byte{tokenRateLimitSeed, rateLimitMint.Bytes()}, tb.gatewayAddress) accounts = append(accounts, &solana.AccountMeta{PublicKey: tokenRateLimitPDA, IsWritable: true, IsSigner: false}) } else { // Not a CEA→UEA flow: rate limit accounts are None @@ -1587,6 +2017,22 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) } + // Ref-finalize optional accounts (#19-20): + // Always emit these slots so remaining_accounts (execute-mode CPI accounts) + // land at the correct position. Direct route passes zero pubkeys → None sentinel. + if storedIxDataPDA.IsZero() { + accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) + } else { + // Must be writable — finalize_universal_tx_with_ix_data_ref auto-closes + // the PDA on success (the contract declares it `#[account(mut)]`). + accounts = append(accounts, &solana.AccountMeta{PublicKey: storedIxDataPDA, IsWritable: true, IsSigner: false}) + } + if storeRefundRecipient.IsZero() { + accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) + } else { + accounts = append(accounts, &solana.AccountMeta{PublicKey: storeRefundRecipient, IsWritable: true, IsSigner: false}) + } + // For execute mode: append the target program's accounts as "remaining_accounts". // These are the accounts that the gateway will pass through via CPI to the target program. if instructionID == 2 { @@ -1603,46 +2049,6 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( return accounts } -// VerifyBroadcastedTx checks the status of a broadcasted transaction on Solana. -// Returns (found, confirmations, status, error): -// - found=false: tx not found or not yet confirmed -// - found=true: tx exists on-chain -// - confirmations: number of slots since the tx was included (0 = just confirmed) -// - status: 0 = failed, 1 = success -func (tb *TxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) (found bool, blockHeight uint64, confirmations uint64, status uint8, err error) { - sig, sigErr := solana.SignatureFromBase58(txHash) - if sigErr != nil { - return false, 0, 0, 0, nil - } - - tx, txErr := tb.rpcClient.GetTransaction(ctx, sig) - if txErr != nil { - return false, 0, 0, 0, nil - } - - if tx == nil { - return false, 0, 0, 0, nil - } - - // Calculate confirmations from current slot - var confs uint64 - if tx.Slot > 0 { - latestSlot, slotErr := tb.rpcClient.GetLatestSlot(ctx) - if slotErr == nil && latestSlot >= tx.Slot { - confs = latestSlot - tx.Slot + 1 - } - } - - // Check if transaction had an error - if tx.Meta != nil && tx.Meta.Err != nil { - return true, tx.Slot, confs, 0, nil - } - - return true, tx.Slot, confs, 1, nil -} - -// buildSetComputeUnitLimitInstruction creates a SetComputeUnitLimit instruction for the Compute Budget program -// Instruction format: [1-byte instruction type (2 = SetComputeUnitLimit)] + [4-byte u32 units] // buildRevertAccounts builds the unified accounts list for revert_universal_tx // (handles both SOL and SPL). // @@ -1691,14 +2097,13 @@ func (tb *TxBuilder) buildRevertAccounts( } } else { // SPL: derive and pass real ATAs - ataProgramID := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") tokenVaultATA, _, _ := solana.FindProgramAddress( [][]byte{vaultPDA.Bytes(), solana.TokenProgramID.Bytes(), mintPubkey.Bytes()}, - ataProgramID, + solana.SPLAssociatedTokenAccountProgramID, ) recipientATA, _, _ := solana.FindProgramAddress( [][]byte{recipient.Bytes(), solana.TokenProgramID.Bytes(), mintPubkey.Bytes()}, - ataProgramID, + solana.SPLAssociatedTokenAccountProgramID, ) accounts = append(accounts, &solana.AccountMeta{PublicKey: tokenVaultATA, IsWritable: true, IsSigner: false}, @@ -1746,46 +2151,175 @@ func (tb *TxBuilder) buildRescueAccounts( // Byte 0: instruction type (2 = SetComputeUnitLimit) // Bytes 1-4: units (u32, little-endian) func (tb *TxBuilder) buildSetComputeUnitLimitInstruction(units uint32) solana.Instruction { - computeBudgetProgramID := solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111") - data := make([]byte, 5) data[0] = 2 // SetComputeUnitLimit binary.LittleEndian.PutUint32(data[1:], units) return solana.NewInstruction( - computeBudgetProgramID, + solana.ComputeBudget, []*solana.AccountMeta{}, data, ) } // ============================================================================= -// ATA Creation +// Ref-Finalize Route (Large-Payload 2-Tx Path) +// +// When the direct finalize_universal_tx exceeds Solana's 1232-byte raw tx +// limit (typically multi-hop CPI flows with a fat ix_data), we split the +// flow into two transactions: +// +// 1. store_execute_ix_data — relayer uploads raw ix_data into a content- +// addressed PDA. Permissionless, no TSS involvement. +// 2. finalize_universal_tx_with_ix_data_ref — same logical finalize as the +// direct route, but the program loads ix_data from the PDA instead of +// taking it inline. Uses the SAME TSS message envelope (raw ix_data), +// so constructTSSMessage does NOT branch on route. +// +// On success, finalize_universal_tx_with_ix_data_ref auto-closes the +// StoredIxData PDA and returns rent to store_refund_recipient in the same +// tx. close_stored_ix_data is only used for the failure / abort tail +// (store succeeded but finalize never did). // ============================================================================= -// buildCreateATAIdempotentInstruction builds a CreateIdempotent instruction for the -// Associated Token Account (ATA) program. This creates the recipient's ATA if it -// doesn't exist, or succeeds as a no-op if it already exists. +// deriveStoredIxDataPDA returns the canonical StoredIxData PDA for a given +// (sub_tx_id, ix_data_hash). The PDA is content-addressed: any holder of the +// raw ix_data can compute its hash and derive the same address. +func (tb *TxBuilder) deriveStoredIxDataPDA(subTxID, ixDataHash [32]byte) (solana.PublicKey, error) { + addr, _, err := solana.FindProgramAddress( + [][]byte{storedIxDataSeed, subTxID[:], ixDataHash[:]}, + tb.gatewayAddress, + ) + return addr, err +} + +// buildStoreIxDataData constructs the Borsh-serialized instruction data for +// store_execute_ix_data. // -// This is needed for SPL withdraw and SPL revert flows because the gateway contract -// validates that the recipient ATA exists but does NOT create it. The relayer pays -// the ATA rent (~0.002 SOL) which is reimbursed via the gas_fee. +// Offset Size Field +// 0 8 discriminator +// 8 32 sub_tx_id [u8; 32] +// 40 32 ix_data_hash [u8; 32] +// 72 4+N ix_data Vec (4-byte LE length + bytes) +func (tb *TxBuilder) buildStoreIxDataData(subTxID, ixDataHash [32]byte, ixData []byte) []byte { + data := make([]byte, 0, 8+32+32+4+len(ixData)) + data = append(data, discStoreExecuteIxData[:]...) + data = append(data, subTxID[:]...) + data = append(data, ixDataHash[:]...) + + lenBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(lenBytes, uint32(len(ixData))) + data = append(data, lenBytes...) + data = append(data, ixData...) + return data +} + +// buildWithdrawAndExecuteRefData constructs the Borsh-serialized instruction +// data for finalize_universal_tx_with_ix_data_ref. +// +// Field order DIFFERS from the direct route: ix_data_hash (fixed [u8; 32]) +// comes BEFORE writable_flags (Vec). This is intentional in the on-chain +// program — do not swap, or Anchor will fail to decode. +// +// Offset Size Field +// 0 8 discriminator +// 8 1 instruction_id u8 +// 9 32 sub_tx_id [u8; 32] +// 41 32 universal_tx_id [u8; 32] +// 73 8 amount u64 (LE) +// 81 20 push_account [u8; 20] +// 101 32 ix_data_hash [u8; 32] ← swapped vs direct +// 133 4+N writable_flags Vec ← swapped vs direct +// ... 8 gas_fee u64 (LE) +// ... 64 signature [u8; 64] +// ... 1 recovery_id u8 +// ... 32 message_hash [u8; 32] +func (tb *TxBuilder) buildWithdrawAndExecuteRefData( + instructionID uint8, + subTxID [32]byte, + universalTxID [32]byte, + amount uint64, + pushAccount [20]byte, + ixDataHash [32]byte, + writableFlags []byte, + gasFee uint64, + deadlineUnix int64, + signature []byte, + recoveryID byte, + messageHash []byte, +) []byte { + data := make([]byte, 0, 256) + data = append(data, discFinalizeUniversalTxRef[:]...) + data = append(data, instructionID) + data = append(data, subTxID[:]...) + data = append(data, universalTxID[:]...) + + amountBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(amountBytes, amount) + data = append(data, amountBytes...) + + data = append(data, pushAccount[:]...) + data = append(data, ixDataHash[:]...) + + wfLen := make([]byte, 4) + binary.LittleEndian.PutUint32(wfLen, uint32(len(writableFlags))) + data = append(data, wfLen...) + data = append(data, writableFlags...) + + gasFeeBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(gasFeeBytes, gasFee) + data = append(data, gasFeeBytes...) + + deadlineBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(deadlineBytes, uint64(deadlineUnix)) + data = append(data, deadlineBytes...) + + data = append(data, signature...) + data = append(data, recoveryID) + data = append(data, messageHash...) + return data +} + +// buildStoreIxDataAccounts builds the accounts list for store_execute_ix_data. // -// ATA program instruction indices: +// # Account Flags +// 1 caller signer, mut Relayer paying for storage +// 2 stored_ix_data mut Canonical StoredIxData PDA (gets init'd) +// 3 system_program read-only +func (tb *TxBuilder) buildStoreIxDataAccounts(caller, storedIxDataPDA solana.PublicKey) []*solana.AccountMeta { + return []*solana.AccountMeta{ + {PublicKey: caller, IsWritable: true, IsSigner: true}, + {PublicKey: storedIxDataPDA, IsWritable: true, IsSigner: false}, + {PublicKey: solana.SystemProgramID, IsWritable: false, IsSigner: false}, + } +} + +// buildCloseStoredIxDataAccounts builds the accounts list for close_stored_ix_data. +// All four metas required — Anchor's Option still demands a slot. // -// 0 = Create (fails if ATA exists) -// 1 = CreateIdempotent (no-op if ATA exists) ← we use this +// 1 caller (signer, mut) 2 stored_ix_data (mut) +// 3 store_refund_recipient (mut) 4 executed_sub_tx (canonical PDA) +func (tb *TxBuilder) buildCloseStoredIxDataAccounts(caller, storedIxDataPDA, executedSubTxPDA solana.PublicKey) []*solana.AccountMeta { + return []*solana.AccountMeta{ + {PublicKey: caller, IsWritable: true, IsSigner: true}, + {PublicKey: storedIxDataPDA, IsWritable: true, IsSigner: false}, + {PublicKey: caller, IsWritable: true, IsSigner: false}, + {PublicKey: executedSubTxPDA, IsWritable: false, IsSigner: false}, + } +} + +// buildCreateATAIdempotentInstruction creates the recipient's ATA if absent +// (no-op if present). Required for SPL withdraw/revert flows because the +// gateway validates the recipient ATA exists but does NOT create it. Relayer +// pays the ~0.002 SOL rent, reimbursed via gas_fee. func (tb *TxBuilder) buildCreateATAIdempotentInstruction( payer solana.PublicKey, owner solana.PublicKey, mint solana.PublicKey, ) solana.Instruction { - ataProgramID := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") - - // Derive the ATA address deterministically from (owner, token_program, mint) ata, _, _ := solana.FindProgramAddress( [][]byte{owner.Bytes(), solana.TokenProgramID.Bytes(), mint.Bytes()}, - ataProgramID, + solana.SPLAssociatedTokenAccountProgramID, ) accounts := []*solana.AccountMeta{ @@ -1797,16 +2331,19 @@ func (tb *TxBuilder) buildCreateATAIdempotentInstruction( {PublicKey: solana.TokenProgramID, IsWritable: false, IsSigner: false}, } - // Instruction index 1 = CreateIdempotent - return solana.NewInstruction(ataProgramID, accounts, []byte{1}) + // ATA program instruction discriminator: 0 = Create (fails if exists), 1 = CreateIdempotent. + return solana.NewInstruction(solana.SPLAssociatedTokenAccountProgramID, accounts, []byte{1}) } -// GetFundMigrationSigningRequest is not supported for SVM - funds are held by the program, not the TSS key. +// ============================================================================= +// Fund Migration (Unsupported on SVM) +// SVM funds are held by the gateway program in PDA-controlled vaults, not by TSS +// ============================================================================= + func (tb *TxBuilder) GetFundMigrationSigningRequest(ctx context.Context, data *common.FundMigrationData, nonce uint64) (*common.UnsignedSigningReq, error) { return nil, fmt.Errorf("fund migration not supported for SVM") } -// BroadcastFundMigrationTx is not supported for SVM - funds are held by the program, not the TSS key. func (tb *TxBuilder) BroadcastFundMigrationTx(ctx context.Context, req *common.UnsignedSigningReq, data *common.FundMigrationData, signature []byte) (string, error) { return "", fmt.Errorf("fund migration not supported for SVM") } diff --git a/universalClient/chains/svm/tx_builder_test.go b/universalClient/chains/svm/tx_builder_test.go index ee5b30624..26842fc58 100644 --- a/universalClient/chains/svm/tx_builder_test.go +++ b/universalClient/chains/svm/tx_builder_test.go @@ -10,6 +10,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "time" @@ -20,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/pushchain/push-chain-node/universalClient/chains/common" "github.com/pushchain/push-chain-node/universalClient/config" uetypes "github.com/pushchain/push-chain-node/x/uexecutor/types" ) @@ -292,26 +294,22 @@ func TestDetermineInstructionID(t *testing.T) { tests := []struct { name string txType uetypes.TxType - isNative bool expected uint8 wantErr bool }{ - {"FUNDS native → 1 (withdraw)", uetypes.TxType_FUNDS, true, 1, false}, - {"FUNDS SPL → 1 (withdraw)", uetypes.TxType_FUNDS, false, 1, false}, - {"FUNDS_AND_PAYLOAD → 2 (execute)", uetypes.TxType_FUNDS_AND_PAYLOAD, true, 2, false}, - {"GAS_AND_PAYLOAD → 2 (execute)", uetypes.TxType_GAS_AND_PAYLOAD, false, 2, false}, - {"INBOUND_REVERT native → 3", uetypes.TxType_INBOUND_REVERT, true, 3, false}, - {"INBOUND_REVERT SPL → 3", uetypes.TxType_INBOUND_REVERT, false, 3, false}, - {"RESCUE_FUNDS native → 4", uetypes.TxType_RESCUE_FUNDS, true, 4, false}, - {"RESCUE_FUNDS SPL → 4", uetypes.TxType_RESCUE_FUNDS, false, 4, false}, - {"UNSPECIFIED → error", uetypes.TxType_UNSPECIFIED_TX, true, 0, true}, - {"GAS → error", uetypes.TxType_GAS, true, 0, true}, - {"PAYLOAD → error", uetypes.TxType_PAYLOAD, true, 0, true}, + {"FUNDS → 1 (withdraw)", uetypes.TxType_FUNDS, 1, false}, + {"FUNDS_AND_PAYLOAD → 2 (execute)", uetypes.TxType_FUNDS_AND_PAYLOAD, 2, false}, + {"GAS_AND_PAYLOAD → 2 (execute)", uetypes.TxType_GAS_AND_PAYLOAD, 2, false}, + {"INBOUND_REVERT → 3", uetypes.TxType_INBOUND_REVERT, 3, false}, + {"RESCUE_FUNDS → 4", uetypes.TxType_RESCUE_FUNDS, 4, false}, + {"UNSPECIFIED → error", uetypes.TxType_UNSPECIFIED_TX, 0, true}, + {"GAS → error", uetypes.TxType_GAS, 0, true}, + {"PAYLOAD → error", uetypes.TxType_PAYLOAD, 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - id, err := builder.determineInstructionID(tt.txType, tt.isNative) + id, err := builder.determineInstructionID(tt.txType) if tt.wantErr { assert.Error(t, err) } else { @@ -347,6 +345,38 @@ func TestAnchorDiscriminator(t *testing.T) { } } +func TestAnchorDiscriminatorKnownValues(t *testing.T) { + // Verify discriminator values are deterministic and can be independently computed + for _, method := range []string{"finalize_universal_tx", "revert_universal_tx", "rescue_funds"} { + disc := anchorDiscriminator(method) + h := sha256.Sum256([]byte("global:" + method)) + assert.Equal(t, h[:8], disc, "discriminator for %s", method) + } +} + +// TestRefRouteDiscriminatorConstants pins the hardcoded discriminator byte +// arrays for the ref-route instructions to their canonical Anchor derivation. +// These are protocol-critical: a single-byte typo would silently make every +// store / finalize-ref / close call fail against the on-chain gateway with a +// "fallback function not found" error, with no signal at compile time. +func TestRefRouteDiscriminatorConstants(t *testing.T) { + cases := []struct { + method string + got [8]byte + }{ + {"store_execute_ix_data", discStoreExecuteIxData}, + {"finalize_universal_tx_with_ix_data_ref", discFinalizeUniversalTxRef}, + {"close_stored_ix_data", discCloseStoredIxData}, + } + for _, c := range cases { + t.Run(c.method, func(t *testing.T) { + h := sha256.Sum256([]byte("global:" + c.method)) + assert.Equal(t, h[:8], c.got[:], + "discriminator constant for %s does not match sha256(\"global:%s\")[:8]", c.method, c.method) + }) + } +} + func TestConstructTSSMessage(t *testing.T) { builder := newTestBuilder(t) @@ -358,7 +388,7 @@ func TestConstructTSSMessage(t *testing.T) { t.Run("withdraw (id=1) message format", func(t *testing.T) { hash, err := builder.constructTSSMessage( - 1, "devnet", 1000000, + 1, "devnet", int64(0), 1000000, txID, utxID, sender, token, 0, // gasFee target, nil, nil, @@ -371,6 +401,7 @@ func TestConstructTSSMessage(t *testing.T) { msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 1) // instruction_id msg = append(msg, []byte("devnet")...) + msg = append(msg, make([]byte, 8)...) // deadline(i64 BE) = 0 amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 1000000) msg = append(msg, amountBE...) @@ -395,7 +426,7 @@ func TestConstructTSSMessage(t *testing.T) { ixData := []byte{0xDE, 0xAD, 0xBE, 0xEF} hash, err := builder.constructTSSMessage( - 2, "devnet", 2000000, + 2, "devnet", int64(0), 2000000, txID, utxID, sender, token, 100, // gasFee target, accs, ixData, @@ -408,6 +439,7 @@ func TestConstructTSSMessage(t *testing.T) { msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 2) msg = append(msg, []byte("devnet")...) + msg = append(msg, make([]byte, 8)...) // deadline(i64 BE) = 0 amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 2000000) msg = append(msg, amountBE...) @@ -442,7 +474,7 @@ func TestConstructTSSMessage(t *testing.T) { t.Run("revert SOL (id=3) message format", func(t *testing.T) { revertRecipient := makeTxID(0xEE) hash, err := builder.constructTSSMessage( - 3, "devnet", 500000, + 3, "devnet", int64(0), 500000, txID, utxID, sender, token, 0, [32]byte{}, nil, nil, revertRecipient, [32]byte{}, nil, @@ -452,6 +484,7 @@ func TestConstructTSSMessage(t *testing.T) { msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 3) msg = append(msg, []byte("devnet")...) + msg = append(msg, make([]byte, 8)...) // deadline(i64 BE) = 0 amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 500000) msg = append(msg, amountBE...) @@ -472,7 +505,7 @@ func TestConstructTSSMessage(t *testing.T) { revertRecipient := makeTxID(0xEE) revertMint := makeTxID(0xFF) hash, err := builder.constructTSSMessage( - 3, "devnet", 750000, + 3, "devnet", int64(0), 750000, txID, utxID, sender, token, 0, [32]byte{}, nil, nil, revertRecipient, revertMint, nil, @@ -482,6 +515,7 @@ func TestConstructTSSMessage(t *testing.T) { msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 3) msg = append(msg, []byte("devnet")...) + msg = append(msg, make([]byte, 8)...) // deadline(i64 BE) = 0 amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 750000) msg = append(msg, amountBE...) @@ -506,14 +540,14 @@ func TestConstructTSSMessage(t *testing.T) { // the same TSS signature. revertRecipient := makeTxID(0xEE) hashA, err := builder.constructTSSMessage( - 3, "devnet", 500000, + 3, "devnet", int64(0), 500000, txID, utxID, sender, token, 0, [32]byte{}, nil, nil, revertRecipient, [32]byte{}, []byte("reason A"), ) require.NoError(t, err) hashB, err := builder.constructTSSMessage( - 3, "devnet", 500000, + 3, "devnet", int64(0), 500000, txID, utxID, sender, token, 0, [32]byte{}, nil, nil, revertRecipient, [32]byte{}, []byte("reason B"), @@ -528,14 +562,14 @@ func TestConstructTSSMessage(t *testing.T) { // still produce the same hash. rescueRecipient := makeTxID(0xEE) hashA, err := builder.constructTSSMessage( - 4, "devnet", 300000, + 4, "devnet", int64(0), 300000, txID, utxID, sender, token, 50, [32]byte{}, nil, nil, rescueRecipient, [32]byte{}, []byte("reason A"), ) require.NoError(t, err) hashB, err := builder.constructTSSMessage( - 4, "devnet", 300000, + 4, "devnet", int64(0), 300000, txID, utxID, sender, token, 50, [32]byte{}, nil, nil, rescueRecipient, [32]byte{}, []byte("reason B"), @@ -547,7 +581,7 @@ func TestConstructTSSMessage(t *testing.T) { t.Run("rescue SOL (id=4) message format", func(t *testing.T) { rescueRecipient := makeTxID(0xEE) hash, err := builder.constructTSSMessage( - 4, "devnet", 300000, + 4, "devnet", int64(0), 300000, txID, utxID, sender, token, 50, [32]byte{}, nil, nil, rescueRecipient, [32]byte{}, nil, @@ -557,6 +591,7 @@ func TestConstructTSSMessage(t *testing.T) { msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 4) msg = append(msg, []byte("devnet")...) + msg = append(msg, make([]byte, 8)...) // deadline(i64 BE) = 0 amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 300000) msg = append(msg, amountBE...) @@ -575,7 +610,7 @@ func TestConstructTSSMessage(t *testing.T) { rescueRecipient := makeTxID(0xEE) rescueMint := makeTxID(0xFF) hash, err := builder.constructTSSMessage( - 4, "devnet", 400000, + 4, "devnet", int64(0), 400000, txID, utxID, sender, token, 75, [32]byte{}, nil, nil, rescueRecipient, rescueMint, nil, @@ -585,6 +620,7 @@ func TestConstructTSSMessage(t *testing.T) { msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 4) msg = append(msg, []byte("devnet")...) + msg = append(msg, make([]byte, 8)...) // deadline(i64 BE) = 0 amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 400000) msg = append(msg, amountBE...) @@ -604,7 +640,7 @@ func TestConstructTSSMessage(t *testing.T) { // Verify that the chain_id in the message is raw UTF-8, not Borsh-encoded chainID := "test_chain" hash1, err := builder.constructTSSMessage( - 1, chainID, 0, + 1, chainID, int64(0), 0, [32]byte{}, [32]byte{}, [20]byte{}, [32]byte{}, 0, [32]byte{}, nil, nil, [32]byte{}, [32]byte{}, nil, @@ -615,6 +651,7 @@ func TestConstructTSSMessage(t *testing.T) { msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 1) msg = append(msg, []byte(chainID)...) // raw UTF-8, no 4-byte length prefix + msg = append(msg, make([]byte, 8)...) // deadline(i64 BE) = 0 msg = append(msg, make([]byte, 8)...) // amount BE msg = append(msg, make([]byte, 32)...) // tx_id msg = append(msg, make([]byte, 32)...) // utx_id @@ -629,7 +666,7 @@ func TestConstructTSSMessage(t *testing.T) { t.Run("unknown instruction ID returns error", func(t *testing.T) { _, err := builder.constructTSSMessage( - 99, "devnet", 0, + 99, "devnet", int64(0), 0, [32]byte{}, [32]byte{}, [20]byte{}, [32]byte{}, 0, [32]byte{}, nil, nil, [32]byte{}, [32]byte{}, nil, @@ -644,18 +681,18 @@ func TestConstructTSSMessage_HashIsKeccak256(t *testing.T) { // Construct a simple withdraw message and verify the hash algo hash, err := builder.constructTSSMessage( - 1, "x", 0, + 1, "x", int64(0), 0, [32]byte{}, [32]byte{}, [20]byte{}, [32]byte{}, 0, [32]byte{}, nil, nil, [32]byte{}, [32]byte{}, nil, ) require.NoError(t, err) - // Build the raw message + // Build the raw message: prefix || id || chain_id || deadline(8) || amount(8) || tx_id(32) || utx_id(32) || sender(20) || token(32) || gas_fee(8) || target(32) msg := []byte("PUSH_CHAIN_SVM") msg = append(msg, 1) msg = append(msg, 'x') - msg = append(msg, make([]byte, 8+32+32+20+32+8+32)...) + msg = append(msg, make([]byte, 8+8+32+32+20+32+8+32)...) // Must be keccak256 (not sha256) keccakHash := crypto.Keccak256(msg) @@ -665,69 +702,192 @@ func TestConstructTSSMessage_HashIsKeccak256(t *testing.T) { } func TestDecodePayload(t *testing.T) { - t.Run("decodes valid execute payload with 2 accounts", func(t *testing.T) { - expectedAccounts := []GatewayAccountMeta{ - {Pubkey: makeTxID(0x11), IsWritable: true}, - {Pubkey: makeTxID(0x22), IsWritable: false}, - } - expectedIxData := []byte{0xAA, 0xBB, 0xCC} - expectedTarget := makeTxID(0xDD) - - payload := buildMockPayload(expectedAccounts, expectedIxData, 2, expectedTarget) - accounts, ixData, instructionID, targetProgram, err := decodePayload(payload) - - require.NoError(t, err) - assert.Equal(t, uint8(2), instructionID) - assert.Len(t, accounts, 2) - assert.Equal(t, expectedAccounts[0].Pubkey, accounts[0].Pubkey) - assert.True(t, accounts[0].IsWritable) - assert.Equal(t, expectedAccounts[1].Pubkey, accounts[1].Pubkey) - assert.False(t, accounts[1].IsWritable) - assert.Equal(t, expectedIxData, ixData) - assert.Equal(t, expectedTarget, targetProgram) - }) + // Roundtrip cases: encode with buildMockPayload, decode, assert every field + // round-trips. Each row is a distinct encoding shape we want to support. + roundtripCases := []struct { + name string + accounts []GatewayAccountMeta + ixData []byte + instructionID uint8 + targetProgram [32]byte + }{ + { + name: "execute with 2 accounts (writable + readonly) and ix_data", + accounts: []GatewayAccountMeta{ + {Pubkey: makeTxID(0x11), IsWritable: true}, + {Pubkey: makeTxID(0x22), IsWritable: false}, + }, + ixData: []byte{0xAA, 0xBB, 0xCC}, + instructionID: 2, + targetProgram: makeTxID(0xDD), + }, + { + name: "withdraw with no accounts and no ix_data", + accounts: nil, + ixData: nil, + instructionID: 1, + targetProgram: [32]byte{}, + }, + { + name: "execute with 1 account and empty ix_data", + accounts: []GatewayAccountMeta{{Pubkey: makeTxID(0x33), IsWritable: true}}, + ixData: nil, + instructionID: 2, + targetProgram: makeTxID(0xEE), + }, + } - t.Run("decodes withdraw payload (0 accounts)", func(t *testing.T) { - payload := buildMockWithdrawPayload() - accounts, ixData, instructionID, _, err := decodePayload(payload) - require.NoError(t, err) - assert.Equal(t, uint8(1), instructionID) - assert.Len(t, accounts, 0) - assert.Len(t, ixData, 0) - }) + for _, tc := range roundtripCases { + t.Run(tc.name, func(t *testing.T) { + payload := buildMockPayload(tc.accounts, tc.ixData, tc.instructionID, tc.targetProgram) + accounts, ixData, instructionID, targetProgram, err := decodePayload(payload) + + require.NoError(t, err) + assert.Equal(t, tc.instructionID, instructionID) + assert.Len(t, accounts, len(tc.accounts)) + for i, want := range tc.accounts { + assert.Equal(t, want.Pubkey, accounts[i].Pubkey, "account %d pubkey", i) + assert.Equal(t, want.IsWritable, accounts[i].IsWritable, "account %d writable", i) + } + assert.Equal(t, len(tc.ixData), len(ixData)) + if len(tc.ixData) > 0 { + assert.Equal(t, tc.ixData, ixData) + } + assert.Equal(t, tc.targetProgram, targetProgram) + }) + } - t.Run("decodes payload with empty ix_data", func(t *testing.T) { - accs := []GatewayAccountMeta{{Pubkey: makeTxID(0x33), IsWritable: true}} - expectedTarget := makeTxID(0xEE) - payload := buildMockPayload(accs, nil, 2, expectedTarget) - accounts, ixData, instructionID, targetProgram, err := decodePayload(payload) - require.NoError(t, err) - assert.Equal(t, uint8(2), instructionID) - assert.Len(t, accounts, 1) - assert.Len(t, ixData, 0) - assert.Equal(t, expectedTarget, targetProgram) - }) + // Malformed-payload cases. Each entry constructs a specific bad payload + // and asserts the returned error contains the expected substring. + errCases := []struct { + name string + payload []byte + wantErr string + }{ + { + name: "below 41-byte minimum", + payload: []byte{0, 0}, + wantErr: "payload too short", + }, + { + name: "ix_data_len exceeds remaining bytes", + payload: func() []byte { + // 41-byte payload (exactly minimum). 0 accounts, claims ix_data_len=100 + // but only 33 bytes remain after the two u32 headers. + p := make([]byte, 41) + binary.BigEndian.PutUint32(p[0:4], 0) // accountsCount + binary.BigEndian.PutUint32(p[4:8], 100) // ixDataLen + return p + }(), + wantErr: "payload too short: expected", + }, + { + name: "oversized accountsCount: 0xFFFFFFFF would OOM-panic on make()", + payload: func() []byte { + p := make([]byte, 41) + binary.BigEndian.PutUint32(p[0:4], 0xFFFFFFFF) + return p + }(), + wantErr: "payload too short for 4294967295 accounts", + }, + { + name: "accountsCount exceeds remaining bytes by one (off-by-one boundary)", + payload: func() []byte { + // 4-byte count + 65 bytes is one short of holding 2 accounts (66 bytes). + p := make([]byte, 4+65) + binary.BigEndian.PutUint32(p[0:4], 2) + return p + }(), + wantErr: "payload too short for 2 accounts", + }, + { + name: "oversized ixDataLen: 0xFFFFFFFF must reject before make([]byte)", + payload: func() []byte { + p := make([]byte, 41) + binary.BigEndian.PutUint32(p[0:4], 0) // accountsCount + binary.BigEndian.PutUint32(p[4:8], 0xFFFFFFFF) // ixDataLen + return p + }(), + wantErr: "payload too short: expected", + }, + { + name: "invalid instruction_id (3 is not withdraw/execute)", + payload: func() []byte { + return buildMockPayload(nil, nil, 3, [32]byte{}) + }(), + wantErr: "invalid instruction_id 3", + }, + { + name: "invalid instruction_id (0)", + payload: func() []byte { + return buildMockPayload(nil, nil, 0, [32]byte{}) + }(), + wantErr: "invalid instruction_id 0", + }, + { + name: "withdraw with non-zero accountsCount", + payload: func() []byte { + return buildMockPayload( + []GatewayAccountMeta{{Pubkey: makeTxID(0x11), IsWritable: true}}, + nil, 1, [32]byte{}, + ) + }(), + wantErr: "withdraw payload must have accountsCount=0", + }, + { + name: "withdraw with non-zero ixDataLen", + payload: func() []byte { + return buildMockPayload(nil, []byte{0xAB}, 1, [32]byte{}) + }(), + wantErr: "withdraw payload must have accountsCount=0", + }, + { + name: "trailing bytes after target_program", + payload: func() []byte { + p := buildMockPayload(nil, nil, 1, [32]byte{}) + return append(p, 0x00, 0x01, 0x02) + }(), + wantErr: "trailing bytes", + }, + } - t.Run("rejects too-short payload", func(t *testing.T) { - _, _, _, _, err := decodePayload([]byte{0, 0}) - assert.Error(t, err) - }) + for _, tc := range errCases { + t.Run(tc.name, func(t *testing.T) { + _, _, _, _, err := decodePayload(tc.payload) + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErr) + }) + } +} - t.Run("rejects truncated account data", func(t *testing.T) { - // Says 1 account but only provides 10 bytes (need 33) - payload := make([]byte, 4+10) - binary.BigEndian.PutUint32(payload[0:4], 1) - _, _, _, _, err := decodePayload(payload) - assert.Error(t, err) - }) +// FuzzDecodePayload feeds arbitrary byte sequences to decodePayload and asserts +// it never panics. Run locally with: +// +// go test ./chains/svm/ -fuzz=FuzzDecodePayload -fuzztime=30s +// +// The seed corpus mixes valid and known-bad shapes so the fuzzer mutates from +// realistic starting points. +func FuzzDecodePayload(f *testing.F) { + f.Add(buildMockWithdrawPayload()) + f.Add(buildMockPayload( + []GatewayAccountMeta{{Pubkey: makeTxID(0x11), IsWritable: true}}, + []byte{0xAA, 0xBB}, + 2, + makeTxID(0xDD), + )) + f.Add([]byte{}) + f.Add([]byte{0, 0}) + // Adversarial seed: max accountsCount (caught by the OOM guard). + { + p := make([]byte, 41) + binary.BigEndian.PutUint32(p[0:4], 0xFFFFFFFF) + f.Add(p) + } - t.Run("rejects truncated ix_data", func(t *testing.T) { - // 0 accounts, says ix_data len=100 but only 4 bytes remain - payload := make([]byte, 4+4+4) - binary.BigEndian.PutUint32(payload[0:4], 0) // 0 accounts - binary.BigEndian.PutUint32(payload[4:8], 100) // ix_data_len = 100 - _, _, _, _, err := decodePayload(payload) - assert.Error(t, err) + f.Fuzz(func(t *testing.T, payload []byte) { + // Contract: decodePayload returns an error for malformed input; it must + // never panic, OOM, or block indefinitely on any byte sequence. + _, _, _, _, _ = decodePayload(payload) }) } @@ -796,7 +956,7 @@ func TestBuildWithdrawAndExecuteData(t *testing.T) { data := builder.buildWithdrawAndExecuteData( 1, txID, utxID, 1000000, sender, []byte{}, []byte{}, // empty writable_flags and ix_data for withdraw - 0, // gasFee + 0, int64(0), // gasFee sig, 2, msgHash, ) @@ -828,18 +988,21 @@ func TestBuildWithdrawAndExecuteData(t *testing.T) { // Check gas_fee (u64 LE) assert.Equal(t, uint64(0), binary.LittleEndian.Uint64(data[109:117]), "gas_fee") - // Check signature (no more rent_fee — directly after gas_fee) - assert.Equal(t, sig, data[117:181], "signature") + // Check deadline (i64 LE) — inserted between gas_fee and signature + assert.Equal(t, uint64(0), binary.LittleEndian.Uint64(data[117:125]), "deadline") + + // Check signature + assert.Equal(t, sig, data[125:189], "signature") // Check recovery_id - assert.Equal(t, byte(2), data[181], "recovery_id") + assert.Equal(t, byte(2), data[189], "recovery_id") // Check message_hash - assert.Equal(t, msgHash, data[182:214], "message_hash") + assert.Equal(t, msgHash, data[190:222], "message_hash") // Total length: 8(disc) + 1(id) + 32(txid) + 32(utxid) + 8(amt) + 20(sender) - // + 4(wf_len) + 0(wf) + 4(ix_len) + 0(ix) + 8(gas) + 64(sig) + 1(recov) + 32(hash) = 214 - assert.Len(t, data, 214) + // + 4(wf_len) + 0(wf) + 4(ix_len) + 0(ix) + 8(gas) + 8(deadline) + 64(sig) + 1(recov) + 32(hash) = 222 + assert.Len(t, data, 222) }) t.Run("execute (id=2) with accounts and ix_data", func(t *testing.T) { @@ -849,7 +1012,7 @@ func TestBuildWithdrawAndExecuteData(t *testing.T) { data := builder.buildWithdrawAndExecuteData( 2, txID, utxID, 500, sender, wf, ixData, - 100, // gasFee + 100, int64(0), // gasFee sig, 0, msgHash, ) @@ -875,7 +1038,11 @@ func TestBuildWithdrawAndExecuteData(t *testing.T) { assert.Equal(t, uint64(100), binary.LittleEndian.Uint64(data[offset:offset+8])) offset += 8 - // signature + recovery_id + message_hash (no more rent_fee) + // deadline (i64 LE) — inserted between gas_fee and signature + assert.Equal(t, uint64(0), binary.LittleEndian.Uint64(data[offset:offset+8])) + offset += 8 + + // signature + recovery_id + message_hash assert.Equal(t, sig, data[offset:offset+64]) offset += 64 assert.Equal(t, byte(0), data[offset]) @@ -894,7 +1061,7 @@ func TestBuildRevertData(t *testing.T) { msgHash := make([]byte, 32) t.Run("revert uses correct discriminator (revert_universal_tx)", func(t *testing.T) { - data := builder.buildRevertData(txID, utxID, 1000, recipient, revertMsg, 0, sig, 1, msgHash) + data := builder.buildRevertData(txID, utxID, 1000, recipient, revertMsg, 0, int64(0), sig, 1, msgHash) expectedDisc := anchorDiscriminator("revert_universal_tx") assert.Equal(t, expectedDisc, data[:8]) @@ -908,7 +1075,7 @@ func TestBuildRevertData(t *testing.T) { }) t.Run("revert with empty revert_msg", func(t *testing.T) { - data := builder.buildRevertData(txID, utxID, 2000, recipient, nil, 0, sig, 0, msgHash) + data := builder.buildRevertData(txID, utxID, 2000, recipient, nil, 0, int64(0), sig, 0, msgHash) expectedDisc := anchorDiscriminator("revert_universal_tx") assert.Equal(t, expectedDisc, data[:8]) @@ -926,7 +1093,7 @@ func TestBuildRescueData(t *testing.T) { msgHash := make([]byte, 32) t.Run("rescue uses correct discriminator", func(t *testing.T) { - data := builder.buildRescueData(txID, utxID, 5000, 100, sig, 1, msgHash) + data := builder.buildRescueData(txID, utxID, 5000, 100, int64(0), sig, 1, msgHash) expectedDisc := anchorDiscriminator("rescue_funds") assert.Equal(t, expectedDisc, data[:8], "discriminator") @@ -934,17 +1101,18 @@ func TestBuildRescueData(t *testing.T) { assert.Equal(t, utxID[:], data[40:72], "universal_tx_id") assert.Equal(t, uint64(5000), binary.LittleEndian.Uint64(data[72:80]), "amount") assert.Equal(t, uint64(100), binary.LittleEndian.Uint64(data[80:88]), "gas_fee") - assert.Equal(t, sig, data[88:152], "signature") - assert.Equal(t, byte(1), data[152], "recovery_id") - assert.Equal(t, msgHash, data[153:185], "message_hash") - // Total: 8(disc) + 32(txid) + 32(utxid) + 8(amount) + 8(gasFee) + 64(sig) + 1(recov) + 32(hash) = 185 - assert.Len(t, data, 185) + assert.Equal(t, uint64(0), binary.LittleEndian.Uint64(data[88:96]), "deadline") + assert.Equal(t, sig, data[96:160], "signature") + assert.Equal(t, byte(1), data[160], "recovery_id") + assert.Equal(t, msgHash, data[161:193], "message_hash") + // Total: 8(disc) + 32(txid) + 32(utxid) + 8(amount) + 8(gasFee) + 8(deadline) + 64(sig) + 1(recov) + 32(hash) = 193 + assert.Len(t, data, 193) }) t.Run("rescue has no revert instructions (unlike revert)", func(t *testing.T) { - rescueData := builder.buildRescueData(txID, utxID, 1000, 50, sig, 0, msgHash) + rescueData := builder.buildRescueData(txID, utxID, 1000, 50, int64(0), sig, 0, msgHash) revertRecipient := solana.MustPublicKeyFromBase58(testGatewayAddress) - revertData := builder.buildRevertData(txID, utxID, 1000, revertRecipient, nil, 50, sig, 0, msgHash) + revertData := builder.buildRevertData(txID, utxID, 1000, revertRecipient, nil, 50, int64(0), sig, 0, msgHash) // Rescue should be shorter than revert (no recipient + revert_msg fields) assert.Less(t, len(rescueData), len(revertData), "rescue data should be shorter than revert data") @@ -968,7 +1136,8 @@ func TestBuildWithdrawAndExecuteAccounts(t *testing.T) { solana.SystemProgramID, // destination_program = system for withdraw true, 1, // isNative, instructionID recipient, solana.PublicKey{}, // recipient, mint (unused for native) - nil, // no execute accounts + nil, // no execute accounts + solana.PublicKey{}, solana.PublicKey{}, // direct route: ref-finalize slots are None ) // First 8 required accounts @@ -1011,7 +1180,11 @@ func TestBuildWithdrawAndExecuteAccounts(t *testing.T) { assert.Equal(t, builder.gatewayAddress, accounts[16].PublicKey, "rateLimitConfig should be gateway sentinel") assert.Equal(t, builder.gatewayAddress, accounts[17].PublicKey, "tokenRateLimit should be gateway sentinel") - assert.Len(t, accounts, 18, "total accounts for SOL withdraw") + // Ref-finalize slots (19-20) should be gateway sentinels for direct route + assert.Equal(t, builder.gatewayAddress, accounts[18].PublicKey, "stored_ix_data should be gateway sentinel for direct route") + assert.Equal(t, builder.gatewayAddress, accounts[19].PublicKey, "store_refund_recipient should be gateway sentinel for direct route") + + assert.Len(t, accounts, 20, "total accounts for SOL withdraw (direct route)") }) t.Run("execute (id=2) appends remaining_accounts", func(t *testing.T) { @@ -1026,13 +1199,14 @@ func TestBuildWithdrawAndExecuteAccounts(t *testing.T) { true, 2, // isNative, instructionID=execute solana.PublicKey{}, solana.PublicKey{}, execAccounts, + solana.PublicKey{}, solana.PublicKey{}, // direct route: ref-finalize slots are None ) // For execute: recipient should be gateway sentinel (None) assert.Equal(t, builder.gatewayAddress, accounts[8].PublicKey, "recipient should be None for execute") // remaining_accounts appended at the end - totalRequired := 18 // 8 required + 8 optional + 2 rate limit + totalRequired := 20 // 8 required + 8 SPL optional + 2 rate limit + 2 ref-finalize optional assert.Len(t, accounts, totalRequired+2) // Check remaining_accounts @@ -1268,7 +1442,7 @@ func TestEndToEndWithdrawMessageAndData(t *testing.T) { target := makeTxID(0xDD) msgHash, err := builder.constructTSSMessage( - 1, "devnet", 1000000, + 1, "devnet", int64(0), 1000000, txID, utxID, sender, token, 0, target, nil, nil, [32]byte{}, [32]byte{}, nil, @@ -1278,27 +1452,18 @@ func TestEndToEndWithdrawMessageAndData(t *testing.T) { sig := make([]byte, 64) instrData := builder.buildWithdrawAndExecuteData( 1, txID, utxID, 1000000, sender, - []byte{}, []byte{}, 0, + []byte{}, []byte{}, 0, int64(0), sig, 0, msgHash, ) // Extract message_hash from instruction data // Offset: 8(disc) + 1(id) + 32(txid) + 32(utxid) + 8(amount) + 20(sender) - // + 4(wf_len) + 0(wf) + 4(ix_len) + 0(ix) + 8(gas) + 64(sig) + 1(recov) - // = 182 - msgHashFromData := instrData[182:214] + // + 4(wf_len) + 0(wf) + 4(ix_len) + 0(ix) + 8(gas) + 8(deadline) + 64(sig) + 1(recov) + // = 190 + msgHashFromData := instrData[190:222] assert.Equal(t, msgHash, msgHashFromData, "message_hash in instruction data must match TSS message hash") } -func TestAnchorDiscriminatorKnownValues(t *testing.T) { - // Verify discriminator values are deterministic and can be independently computed - for _, method := range []string{"finalize_universal_tx", "revert_universal_tx", "rescue_funds"} { - disc := anchorDiscriminator(method) - h := sha256.Sum256([]byte("global:" + method)) - assert.Equal(t, h[:8], disc, "discriminator for %s", method) - } -} - func TestEndToEndWithRealSignature(t *testing.T) { builder := newTestBuilder(t) evmKey, _, _ := generateTestEVMKey(t) @@ -1314,7 +1479,7 @@ func TestEndToEndWithRealSignature(t *testing.T) { // 1. Construct TSS message hash (what TSS nodes would sign) msgHash, err := builder.constructTSSMessage( - 1, "devnet", amount, + 1, "devnet", int64(0), amount, txID, utxID, sender, token, 0, target, nil, nil, [32]byte{}, [32]byte{}, nil, @@ -1327,16 +1492,16 @@ func TestEndToEndWithRealSignature(t *testing.T) { // 3. Build instruction data with real signature instrData := builder.buildWithdrawAndExecuteData( 1, txID, utxID, amount, sender, - []byte{}, []byte{}, 0, + []byte{}, []byte{}, 0, int64(0), sig, recoveryID, msgHash, ) // 4. Verify the instruction data contains the real signature // Offset: 8(disc) + 1(id) + 32(txid) + 32(utxid) + 8(amt) + 20(sender) - // + 4(wf_len) + 0(wf) + 4(ix_len) + 0(ix) + 8(gas) = 117 - assert.Equal(t, sig, instrData[117:181], "real signature in instruction data") - assert.Equal(t, recoveryID, instrData[181], "recovery ID in instruction data") - assert.Equal(t, msgHash, instrData[182:214], "message hash in instruction data") + // + 4(wf_len) + 0(wf) + 4(ix_len) + 0(ix) + 8(gas) + 8(deadline) = 125 + assert.Equal(t, sig, instrData[125:189], "real signature in instruction data") + assert.Equal(t, recoveryID, instrData[189], "recovery ID in instruction data") + assert.Equal(t, msgHash, instrData[190:222], "message hash in instruction data") }) t.Run("execute flow with real signature", func(t *testing.T) { @@ -1347,7 +1512,7 @@ func TestEndToEndWithRealSignature(t *testing.T) { ixData := []byte{0xDE, 0xAD} msgHash, err := builder.constructTSSMessage( - 2, "devnet", amount, + 2, "devnet", int64(0), amount, txID, utxID, sender, token, 0, target, accs, ixData, [32]byte{}, [32]byte{}, nil, @@ -1360,14 +1525,14 @@ func TestEndToEndWithRealSignature(t *testing.T) { instrData := builder.buildWithdrawAndExecuteData( 2, txID, utxID, amount, sender, wf, ixData, - 0, + 0, int64(0), sig, recoveryID, msgHash, ) // Verify instruction data length includes variable-length fields // 8(disc) + 1(id) + 32(txid) + 32(utxid) + 8(amt) + 20(sender) - // + 4+1(wf) + 4+2(ix) + 8(gas) + 64(sig) + 1(recov) + 32(hash) - expectedLen := 8 + 1 + 32 + 32 + 8 + 20 + 5 + 6 + 8 + 64 + 1 + 32 + // + 4+1(wf) + 4+2(ix) + 8(gas) + 8(deadline) + 64(sig) + 1(recov) + 32(hash) + expectedLen := 8 + 1 + 32 + 32 + 8 + 20 + 5 + 6 + 8 + 8 + 64 + 1 + 32 assert.Len(t, instrData, expectedLen) }) @@ -1376,7 +1541,7 @@ func TestEndToEndWithRealSignature(t *testing.T) { revertRecipient := makeTxID(0xEE) msgHash, err := builder.constructTSSMessage( - 3, "devnet", amount, + 3, "devnet", int64(0), amount, txID, utxID, sender, token, 0, [32]byte{}, nil, nil, revertRecipient, [32]byte{}, nil, @@ -1388,7 +1553,7 @@ func TestEndToEndWithRealSignature(t *testing.T) { recipient := solana.PublicKeyFromBytes(revertRecipient[:]) instrData := builder.buildRevertData( txID, utxID, amount, recipient, - []byte("revert msg"), 0, + []byte("revert msg"), 0, int64(0), sig, recoveryID, msgHash, ) @@ -1402,7 +1567,7 @@ func TestEndToEndWithRealSignature(t *testing.T) { rescueRecipient := makeTxID(0xEE) msgHash, err := builder.constructTSSMessage( - 4, "devnet", amount, + 4, "devnet", int64(0), amount, txID, utxID, sender, token, 50, [32]byte{}, nil, nil, rescueRecipient, [32]byte{}, nil, @@ -1412,7 +1577,7 @@ func TestEndToEndWithRealSignature(t *testing.T) { sig, recoveryID := signMessageHash(t, evmKey, msgHash) instrData := builder.buildRescueData( - txID, utxID, amount, 50, + txID, utxID, amount, 50, int64(0), sig, recoveryID, msgHash, ) @@ -1420,106 +1585,657 @@ func TestEndToEndWithRealSignature(t *testing.T) { expectedDisc := anchorDiscriminator("rescue_funds") assert.Equal(t, expectedDisc, instrData[:8]) - // Verify total length: 8(disc) + 32(txid) + 32(utxid) + 8(amt) + 8(gas) + 64(sig) + 1(recov) + 32(hash) = 185 - assert.Len(t, instrData, 185) + // Verify total length: 8(disc) + 32(txid) + 32(utxid) + 8(amt) + 8(gas) + 8(deadline) + 64(sig) + 1(recov) + 32(hash) = 193 + assert.Len(t, instrData, 193) }) } -const ( - devnetGatewayAddress = "DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp" - devnetRPCURL = "https://api.devnet.solana.com" - devnetGenesisHash = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG" - devnetSPLMint = "EiXDnrAg9ea2Q6vEPV7E5TpTU1vh41jcuZqKjU5Dc4ZF" - devnetMemoProgram = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" +func TestGetNextNonce(t *testing.T) { + builder := newTestBuilder(t) - // Hardcoded EVM private key for simulation tests. - // ETH address: 0xc681e7bdacfe4dc7209a15ff052f897c3d87008f - // Set this address in the TSS PDA on the gateway contract for signatures to pass on-chain. - testEVMPrivKeyHex = "d54b0eb459b7c0b82e3c21ced25f52a0a7fae6ed1a8614df46dda86c8d5f1e59" + t.Run("returns 0 with arbitrary address and finalized=true", func(t *testing.T) { + nonce, err := builder.GetNextNonce(context.Background(), "SomeAddress123", true) + require.NoError(t, err) + assert.Equal(t, uint64(0), nonce) + }) - // Hardcoded Solana relayer keypair for simulation tests. - // Pubkey: AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM - testSolanaKeypairJSON = `[226,7,176,193,18,2,55,106,191,150,176,87,157,216,118,97,236,128,2,104,181,206,160,147,5,152,0,115,23,8,103,189,143,19,31,194,227,248,222,123,219,13,143,47,154,104,201,235,13,16,11,45,117,154,117,37,130,196,58,154,89,228,136,32]` -) + t.Run("returns 0 with empty address and finalized=false", func(t *testing.T) { + nonce, err := builder.GetNextNonce(context.Background(), "", false) + require.NoError(t, err) + assert.Equal(t, uint64(0), nonce) + }) +} -// setupDevnetSimulation creates RPCClient and TxBuilder for devnet. -// Uses the hardcoded Solana relayer keypair (AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM). -func setupDevnetSimulation(t *testing.T) (*RPCClient, *TxBuilder) { +func TestGetGasFeeUsed(t *testing.T) { + builder := newTestBuilder(t) - t.Skip("skipping simulation tests") // DELIBERATELY SKIPPING SIMULATION TESTS - t.Helper() - if testing.Short() { - t.Skip("skipping simulation test in short mode") - } + t.Run("returns string zero for any tx hash", func(t *testing.T) { + fee, err := builder.GetGasFeeUsed(context.Background(), "5xYz...someTxHash") + require.NoError(t, err) + assert.Equal(t, "0", fee) + }) - logger := zerolog.New(zerolog.NewTestWriter(t)).Level(zerolog.DebugLevel) - rpcClient, err := NewRPCClient([]string{devnetRPCURL}, devnetGenesisHash, logger) - if err != nil { - t.Skipf("skipping: failed to connect to Devnet RPC: %v", err) - } + t.Run("returns string zero for empty tx hash", func(t *testing.T) { + fee, err := builder.GetGasFeeUsed(context.Background(), "") + require.NoError(t, err) + assert.Equal(t, "0", fee) + }) +} - // Write the hardcoded keypair JSON to the temp dir so loadRelayerKeypair can find it. - tmpDir := t.TempDir() - relayerDir := filepath.Join(tmpDir, "relayer") - require.NoError(t, os.MkdirAll(relayerDir, 0o755)) - require.NoError(t, os.WriteFile(filepath.Join(relayerDir, "solana.json"), []byte(testSolanaKeypairJSON), 0o600)) +func TestNewTxBuilder_ChainConfig(t *testing.T) { + logger := zerolog.Nop() - builder, err := NewTxBuilder(rpcClient, "solana:"+devnetGenesisHash, devnetGatewayAddress, tmpDir, logger, nil) - require.NoError(t, err) + t.Run("valid protocolALT is stored", func(t *testing.T) { + altKey := solana.NewWallet().PublicKey() + cfg := &config.ChainSpecificConfig{ + ProtocolALT: altKey.String(), + } + builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) + require.NoError(t, err) + assert.Equal(t, altKey, builder.protocolALT) + }) - t.Logf("relayer pubkey: AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM") - return rpcClient, builder -} + t.Run("invalid protocolALT is silently skipped", func(t *testing.T) { + cfg := &config.ChainSpecificConfig{ + ProtocolALT: "not-valid-base58!!!", + } + builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) + require.NoError(t, err) + assert.True(t, builder.protocolALT.IsZero(), "invalid ALT should result in zero pubkey") + }) -// loadTestEVMKey loads the hardcoded EVM private key and returns the key + ETH address hex. -func loadTestEVMKey(t *testing.T) (*ecdsa.PrivateKey, string) { - t.Helper() - privBytes, err := hex.DecodeString(testEVMPrivKeyHex) - require.NoError(t, err) - key, err := crypto.ToECDSA(privBytes) - require.NoError(t, err) - pubBytes := crypto.FromECDSAPub(&key.PublicKey) - addrBytes := crypto.Keccak256(pubBytes[1:])[12:] - return key, hex.EncodeToString(addrBytes) -} + t.Run("valid tokenALTs are stored", func(t *testing.T) { + mint := solana.NewWallet().PublicKey() + alt := solana.NewWallet().PublicKey() + cfg := &config.ChainSpecificConfig{ + TokenALTs: map[string]string{ + mint.String(): alt.String(), + }, + } + builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) + require.NoError(t, err) + got, ok := builder.tokenALTs[mint] + require.True(t, ok, "expected token ALT entry for mint") + assert.Equal(t, alt, got) + }) -// newDevnetOutbound creates an OutboundCreatedEvent using the hardcoded EVM key as sender -// and a fresh Solana wallet as recipient. Uses random tx_id/utx_id for each call to avoid -// executed_tx PDA collisions between tests. -func newDevnetOutbound(t *testing.T, amount, assetAddr, payload, revertMsg, txType string) (*uetypes.OutboundCreatedEvent, *ecdsa.PrivateKey) { - t.Helper() + t.Run("invalid tokenALT mint is skipped", func(t *testing.T) { + cfg := &config.ChainSpecificConfig{ + TokenALTs: map[string]string{ + "bad-mint": solana.NewWallet().PublicKey().String(), + }, + } + builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) + require.NoError(t, err) + assert.Len(t, builder.tokenALTs, 0) + }) - evmKey, ethAddrHex := loadTestEVMKey(t) - recipientWallet := solana.NewWallet() + t.Run("invalid tokenALT address is skipped", func(t *testing.T) { + cfg := &config.ChainSpecificConfig{ + TokenALTs: map[string]string{ + solana.NewWallet().PublicKey().String(): "bad-alt", + }, + } + builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) + require.NoError(t, err) + assert.Len(t, builder.tokenALTs, 0) + }) - txIDBytes := make([]byte, 32) - utxIDBytes := make([]byte, 32) - _, err := crand.Read(txIDBytes) - require.NoError(t, err) - _, err = crand.Read(utxIDBytes) - require.NoError(t, err) - return &uetypes.OutboundCreatedEvent{ - TxID: "0x" + hex.EncodeToString(txIDBytes), - UniversalTxId: "0x" + hex.EncodeToString(utxIDBytes), - DestinationChain: "solana:" + devnetGenesisHash, - Sender: "0x" + ethAddrHex, - Recipient: recipientWallet.PublicKey().String(), - Amount: amount, - AssetAddr: assetAddr, - Payload: payload, - GasLimit: "400000", - TxType: txType, - RevertMsg: revertMsg, - }, evmKey + t.Run("nil chainConfig is fine", func(t *testing.T) { + builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, nil) + require.NoError(t, err) + assert.True(t, builder.protocolALT.IsZero()) + assert.Len(t, builder.tokenALTs, 0) + }) } -// buildAndSimulate runs the full pipeline: GetOutboundSigningRequest → sign → BuildOutboundTransaction → SimulateTransaction. -// Uses simulation (no broadcast), so no on-chain state is modified (nonce stays the same, no SOL spent). -// Returns the simulation result and any build errors. -func buildAndSimulate(t *testing.T, rpcClient *RPCClient, builder *TxBuilder, data *uetypes.OutboundCreatedEvent, evmKey *ecdsa.PrivateKey) (*rpc.SimulateTransactionResult, error) { - t.Helper() +func TestBuildCreateATAIdempotentInstruction(t *testing.T) { + builder := newTestBuilder(t) + payer := solana.NewWallet().PublicKey() + owner := solana.NewWallet().PublicKey() + mint := solana.NewWallet().PublicKey() - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ix := builder.buildCreateATAIdempotentInstruction(payer, owner, mint) + + t.Run("program ID is ATA program", func(t *testing.T) { + expected := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + assert.Equal(t, expected, ix.ProgramID()) + }) + + t.Run("has 6 accounts in correct order", func(t *testing.T) { + accounts := ix.Accounts() + require.Len(t, accounts, 6) + + // payer (signer, writable) + assert.Equal(t, payer, accounts[0].PublicKey) + assert.True(t, accounts[0].IsSigner) + assert.True(t, accounts[0].IsWritable) + + // ATA (writable, derived deterministically) + ataProgramID := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + expectedATA, _, _ := solana.FindProgramAddress( + [][]byte{owner.Bytes(), solana.TokenProgramID.Bytes(), mint.Bytes()}, + ataProgramID, + ) + assert.Equal(t, expectedATA, accounts[1].PublicKey) + assert.True(t, accounts[1].IsWritable) + assert.False(t, accounts[1].IsSigner) + + // owner + assert.Equal(t, owner, accounts[2].PublicKey) + assert.False(t, accounts[2].IsWritable) + + // mint + assert.Equal(t, mint, accounts[3].PublicKey) + assert.False(t, accounts[3].IsWritable) + + // system program + assert.Equal(t, solana.SystemProgramID, accounts[4].PublicKey) + + // token program + assert.Equal(t, solana.TokenProgramID, accounts[5].PublicKey) + }) + + t.Run("instruction data is [1] for CreateIdempotent", func(t *testing.T) { + data, err := ix.Data() + require.NoError(t, err) + assert.Equal(t, []byte{1}, data) + }) +} + +// ============================================================================= +// Ref-Finalize Route Tests +// ============================================================================= + +func TestDeriveStoredIxDataPDA(t *testing.T) { + builder := newTestBuilder(t) + subTxID := makeTxID(0xAB) + ixDataHash := makeTxID(0xCD) + + pda1, err := builder.deriveStoredIxDataPDA(subTxID, ixDataHash) + require.NoError(t, err) + require.False(t, pda1.IsZero(), "PDA must be non-zero") + + // Determinism: same inputs → same PDA + pda2, err := builder.deriveStoredIxDataPDA(subTxID, ixDataHash) + require.NoError(t, err) + assert.Equal(t, pda1, pda2, "same seeds must derive same PDA") + + // Sensitivity: different sub_tx_id → different PDA + pdaDifferentSubTx, err := builder.deriveStoredIxDataPDA(makeTxID(0xAC), ixDataHash) + require.NoError(t, err) + assert.NotEqual(t, pda1, pdaDifferentSubTx, "different sub_tx_id must change PDA") + + // Sensitivity: different ix_data_hash → different PDA + pdaDifferentHash, err := builder.deriveStoredIxDataPDA(subTxID, makeTxID(0xCE)) + require.NoError(t, err) + assert.NotEqual(t, pda1, pdaDifferentHash, "different ix_data_hash must change PDA") +} + +func TestBuildStoreIxDataData(t *testing.T) { + builder := newTestBuilder(t) + subTxID := makeTxID(0x11) + ixDataHash := makeTxID(0x22) + ixData := []byte{0xDE, 0xAD, 0xBE, 0xEF} + + data := builder.buildStoreIxDataData(subTxID, ixDataHash, ixData) + + // Discriminator + assert.Equal(t, discStoreExecuteIxData[:], data[0:8], "discriminator") + // sub_tx_id + assert.Equal(t, subTxID[:], data[8:40], "sub_tx_id") + // ix_data_hash + assert.Equal(t, ixDataHash[:], data[40:72], "ix_data_hash") + // ix_data Vec: 4-byte LE length + bytes + assert.Equal(t, uint32(len(ixData)), binary.LittleEndian.Uint32(data[72:76]), "vec length prefix") + assert.Equal(t, ixData, data[76:80], "ix_data bytes") + assert.Equal(t, 8+32+32+4+len(ixData), len(data), "total length") +} + +func TestBuildStoreIxDataData_EmptyIxData(t *testing.T) { + // Building with empty ix_data is technically allowed at the universalClient + // layer; the on-chain program rejects with EmptyIxData. Test that we still + // produce a well-formed payload (zero-length Vec). + builder := newTestBuilder(t) + data := builder.buildStoreIxDataData(makeTxID(0x11), makeTxID(0x22), []byte{}) + assert.Equal(t, uint32(0), binary.LittleEndian.Uint32(data[72:76])) + assert.Equal(t, 8+32+32+4, len(data)) +} + +func TestBuildWithdrawAndExecuteRefData_ArgOrder(t *testing.T) { + // Critical: ix_data_hash (fixed 32) comes BEFORE writable_flags (Vec). + // Direct route has writable_flags before ix_data — swap is intentional. + builder := newTestBuilder(t) + + subTxID := makeTxID(0x01) + utxID := makeTxID(0x02) + pushAccount := makeSender(0x03) + ixDataHash := makeTxID(0x04) + writableFlags := []byte{1, 0, 1} + signature := make([]byte, 64) + for i := range signature { + signature[i] = byte(i) + } + msgHash := make([]byte, 32) + for i := range msgHash { + msgHash[i] = 0xFF + } + + data := builder.buildWithdrawAndExecuteRefData( + 2, // instruction_id + subTxID, // sub_tx_id + utxID, // universal_tx_id + 1_000_000_000, // amount + pushAccount, + ixDataHash, + writableFlags, + 500_000, int64(0), // gas_fee + signature, + 1, // recovery_id + msgHash, + ) + + // Layout: + // 0..8 discriminator + // 8 instruction_id + // 9..41 sub_tx_id + // 41..73 universal_tx_id + // 73..81 amount (LE) + // 81..101 push_account + // 101..133 ix_data_hash ← BEFORE writable_flags + // 133..137 writable_flags len + // 137..140 writable_flags + // 140..148 gas_fee (LE) + // 148..156 deadline (i64 LE) + // 156..220 signature + // 220 recovery_id + // 221..253 message_hash + + assert.Equal(t, discFinalizeUniversalTxRef[:], data[0:8]) + assert.Equal(t, uint8(2), data[8]) + assert.Equal(t, subTxID[:], data[9:41]) + assert.Equal(t, utxID[:], data[41:73]) + assert.Equal(t, uint64(1_000_000_000), binary.LittleEndian.Uint64(data[73:81])) + assert.Equal(t, pushAccount[:], data[81:101]) + assert.Equal(t, ixDataHash[:], data[101:133], "ix_data_hash must precede writable_flags") + assert.Equal(t, uint32(3), binary.LittleEndian.Uint32(data[133:137]), "writable_flags length") + assert.Equal(t, writableFlags, data[137:140], "writable_flags bytes") + assert.Equal(t, uint64(500_000), binary.LittleEndian.Uint64(data[140:148])) + assert.Equal(t, uint64(0), binary.LittleEndian.Uint64(data[148:156]), "deadline") + assert.Equal(t, signature, data[156:220]) + assert.Equal(t, uint8(1), data[220]) + assert.Equal(t, msgHash, data[221:253]) + assert.Equal(t, 253, len(data)) +} + +func TestBuildStoreIxDataAccounts(t *testing.T) { + builder := newTestBuilder(t) + caller := solana.NewWallet().PublicKey() + storedPDA := solana.NewWallet().PublicKey() + + accounts := builder.buildStoreIxDataAccounts(caller, storedPDA) + require.Len(t, accounts, 3) + + assert.Equal(t, caller, accounts[0].PublicKey) + assert.True(t, accounts[0].IsSigner) + assert.True(t, accounts[0].IsWritable) + + assert.Equal(t, storedPDA, accounts[1].PublicKey) + assert.True(t, accounts[1].IsWritable) + assert.False(t, accounts[1].IsSigner) + + assert.Equal(t, solana.SystemProgramID, accounts[2].PublicKey) + assert.False(t, accounts[2].IsWritable) + assert.False(t, accounts[2].IsSigner) +} + +func TestBuildCloseStoredIxDataAccounts(t *testing.T) { + builder := newTestBuilder(t) + caller := solana.NewWallet().PublicKey() + storedPDA := solana.NewWallet().PublicKey() + executedSubTxPDA := solana.NewWallet().PublicKey() + + accounts := builder.buildCloseStoredIxDataAccounts(caller, storedPDA, executedSubTxPDA) + require.Len(t, accounts, 4, "Anchor's Option still requires a meta slot") + + // caller: signer, mut + assert.Equal(t, caller, accounts[0].PublicKey) + assert.True(t, accounts[0].IsSigner) + assert.True(t, accounts[0].IsWritable) + + // stored_ix_data: mut, not signer + assert.Equal(t, storedPDA, accounts[1].PublicKey) + assert.True(t, accounts[1].IsWritable) + assert.False(t, accounts[1].IsSigner) + + // store_refund_recipient: mut, not signer; must equal caller (the relayer + // reclaiming its own rent via the RentReclaimer cron). + assert.Equal(t, caller, accounts[2].PublicKey, "refund recipient must equal caller") + assert.True(t, accounts[2].IsWritable) + assert.False(t, accounts[2].IsSigner) + + // executed_sub_tx: canonical PDA address, read-only. Contract loads the + // account and inspects its data — even if the on-chain account doesn't + // exist (finalize hasn't succeeded), the meta slot must still be populated. + assert.Equal(t, executedSubTxPDA, accounts[3].PublicKey) + assert.False(t, accounts[3].IsWritable, "executed_sub_tx is read-only") + assert.False(t, accounts[3].IsSigner) +} + +func TestBuildWithdrawAndExecuteAccounts_RefRouteSlots(t *testing.T) { + // Verify the ref-finalize slots (#19-20) carry real values when populated, + // and that remaining_accounts still land at position 21+ in execute mode. + builder := newTestBuilder(t) + caller := solana.NewWallet().PublicKey() + configPDA := solana.NewWallet().PublicKey() + vaultPDA := solana.NewWallet().PublicKey() + ceaPDA := solana.NewWallet().PublicKey() + tssPDA := solana.NewWallet().PublicKey() + executedPDA := solana.NewWallet().PublicKey() + recipientPDA := solana.NewWallet().PublicKey() + storedPDA := solana.NewWallet().PublicKey() + storeRefund := solana.NewWallet().PublicKey() + + execAccounts := []GatewayAccountMeta{ + {Pubkey: makeTxID(0xAA), IsWritable: true}, + } + + accounts := builder.buildWithdrawAndExecuteAccounts( + caller, configPDA, vaultPDA, ceaPDA, tssPDA, executedPDA, + recipientPDA, // destination_program + true, 2, // isNative, execute + solana.PublicKey{}, solana.PublicKey{}, // recipient/mint unused + execAccounts, + storedPDA, storeRefund, // ref route: real values + ) + + // Position 18 (0-indexed): stored_ix_data + assert.Equal(t, storedPDA, accounts[18].PublicKey, "stored_ix_data slot") + assert.True(t, accounts[18].IsWritable, "stored_ix_data must be writable; finalize auto-closes it on success") + + // Position 19: store_refund_recipient + assert.Equal(t, storeRefund, accounts[19].PublicKey, "store_refund_recipient slot") + assert.True(t, accounts[19].IsWritable, "store_refund_recipient must be writable for reimbursement") + + // Position 20+: remaining_accounts (the execute CPI accounts) + require.Len(t, accounts, 21, "8 required + 8 SPL + 2 rate-limit + 2 ref + 1 remaining") + expectedRemaining := makeTxID(0xAA) + assert.Equal(t, solana.PublicKeyFromBytes(expectedRemaining[:]), accounts[20].PublicKey, "remaining account 0") + assert.True(t, accounts[20].IsWritable) +} + +// newTestBuilderWithKeypair returns a TxBuilder whose nodeHome contains a +// valid relayer keypair on disk, so loadRelayerKeypair() succeeds in unit +// tests that never touch the network. The embedded RPCClient is still a +// zero-value stub — any code path that actually calls RPC will panic, which +// is intentional: it forces validation tests to fail *before* they reach RPC. +func newTestBuilderWithKeypair(t *testing.T) *TxBuilder { + t.Helper() + tmpDir := t.TempDir() + relayerDir := filepath.Join(tmpDir, "relayer") + require.NoError(t, os.MkdirAll(relayerDir, 0o755)) + require.NoError(t, os.WriteFile( + filepath.Join(relayerDir, "solana.json"), + []byte(testSolanaKeypairJSON), + 0o600, + )) + + logger := zerolog.Nop() + builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, tmpDir, logger, nil) + require.NoError(t, err) + return builder +} + +// buildExecutePayloadForTest assembles a payload in the format decodePayload +// expects: [accountsCount(4 BE) | accounts(N×33) | ixDataLen(4 BE) | ixData | instructionID(1) | targetProgram(32)]. +func buildExecutePayloadForTest(t *testing.T, accounts []GatewayAccountMeta, ixData []byte, instructionID uint8, targetProgram [32]byte) string { + t.Helper() + buf := make([]byte, 0, 4+len(accounts)*33+4+len(ixData)+1+32) + + countBytes := make([]byte, 4) + binary.BigEndian.PutUint32(countBytes, uint32(len(accounts))) + buf = append(buf, countBytes...) + for _, a := range accounts { + buf = append(buf, a.Pubkey[:]...) + if a.IsWritable { + buf = append(buf, 1) + } else { + buf = append(buf, 0) + } + } + + lenBytes := make([]byte, 4) + binary.BigEndian.PutUint32(lenBytes, uint32(len(ixData))) + buf = append(buf, lenBytes...) + buf = append(buf, ixData...) + buf = append(buf, instructionID) + buf = append(buf, targetProgram[:]...) + return "0x" + hex.EncodeToString(buf) +} + +// newBaseRefRouteEvent constructs a minimal OutboundCreatedEvent suitable as +// the "happy template" for ref-route validation tests. Each test mutates a +// single field to exercise a specific error path. +// +// Validation tests don't exercise the signing path, so the sender is a +// hardcoded 20-byte hex string rather than a real EVM key. +func newBaseRefRouteEvent(t *testing.T, payload string) *uetypes.OutboundCreatedEvent { + t.Helper() + recipient := solana.NewWallet().PublicKey() + txIDBytes := make([]byte, 32) + utxIDBytes := make([]byte, 32) + _, err := crand.Read(txIDBytes) + require.NoError(t, err) + _, err = crand.Read(utxIDBytes) + require.NoError(t, err) + return &uetypes.OutboundCreatedEvent{ + TxID: "0x" + hex.EncodeToString(txIDBytes), + UniversalTxId: "0x" + hex.EncodeToString(utxIDBytes), + DestinationChain: "solana:devnet", + Sender: "0xc681e7bdacfe4dc7209a15ff052f897c3d87008f", + Recipient: recipient.String(), + Amount: "0", + Payload: payload, + GasFee: "1000000", + TxType: "GAS_AND_PAYLOAD", + } +} + +func TestBuildRefRouteTransactions_Validation(t *testing.T) { + builder := newTestBuilderWithKeypair(t) + ctx := context.Background() + + validSig := make([]byte, 65) + req := &common.UnsignedSigningReq{SigningHash: make([]byte, 32)} + + // Default "good" payload: execute mode with a small valid ix_data so we + // hit validation paths cleanly without tripping decodePayload. + target := makeTxID(0x99) + smallIxData := []byte{0xDE, 0xAD, 0xBE, 0xEF} + validPayload := buildExecutePayloadForTest(t, []GatewayAccountMeta{}, smallIxData, 2, target) + + t.Run("nil signing request", func(t *testing.T) { + _, _, _, err := builder.BuildRefRouteTransactions(ctx, nil, newBaseRefRouteEvent(t, validPayload), validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "signing request is nil") + }) + + t.Run("nil event data", func(t *testing.T) { + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, nil, validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "outbound event data is nil") + }) + + t.Run("wrong signature length", func(t *testing.T) { + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, newBaseRefRouteEvent(t, validPayload), make([]byte, 64)) + require.Error(t, err) + require.Contains(t, err.Error(), "signature must be 65 bytes") + }) + + t.Run("withdraw payload (id=1) rejected — ref route is execute-only", func(t *testing.T) { + withdrawPayload := buildExecutePayloadForTest(t, []GatewayAccountMeta{}, nil, 1, target) + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, newBaseRefRouteEvent(t, withdrawPayload), validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "ref route only valid for execute mode") + }) + + t.Run("empty payload (instructionID=0) rejected", func(t *testing.T) { + ev := newBaseRefRouteEvent(t, "") + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, ev, validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "ref route only valid for execute mode") + }) + + t.Run("oversized ix_data rejected", func(t *testing.T) { + bigIxData := make([]byte, maxRefRouteIxData+1) + bigPayload := buildExecutePayloadForTest(t, []GatewayAccountMeta{}, bigIxData, 2, target) + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, newBaseRefRouteEvent(t, bigPayload), validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "exceeds ref-route max") + }) + + t.Run("invalid txID hex", func(t *testing.T) { + ev := newBaseRefRouteEvent(t, validPayload) + ev.TxID = "0xnothex" + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, ev, validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid txID") + }) + + t.Run("invalid sender length", func(t *testing.T) { + ev := newBaseRefRouteEvent(t, validPayload) + ev.Sender = "0xdeadbeef" // 4 bytes, not 20 + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, ev, validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid sender length") + }) + + t.Run("amount overflows u64", func(t *testing.T) { + ev := newBaseRefRouteEvent(t, validPayload) + ev.Amount = "99999999999999999999999999" // > u64 max + _, _, _, err := builder.BuildRefRouteTransactions(ctx, req, ev, validSig) + require.Error(t, err) + require.Contains(t, err.Error(), "amount exceeds u64 max") + }) +} + +// ============================================================================= +// Devnet Simulation Tests +// +// Below this line: integration tests that build and simulate transactions +// against a real Solana devnet RPC + the deployed dummy gateway program. +// All are gated by t.Skip("skipping simulation tests") in setupDevnetSimulation +// — a developer un-skips locally to manually verify wire-level correctness +// against an actual cluster. CI never runs them. +// +// Constants (devnetRPCURL, testEVMPrivKeyHex, testSolanaKeypairJSON, etc.) +// and helpers (loadTestEVMKey, buildAndSimulate, …) live here because they're +// only meaningful in the devnet path. Unit tests above this line use the +// in-package mocks defined at the top of the file. +// ============================================================================= + +const ( + devnetGatewayAddress = "DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp" + devnetRPCURL = "https://api.devnet.solana.com" + devnetGenesisHash = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG" + devnetSPLMint = "EiXDnrAg9ea2Q6vEPV7E5TpTU1vh41jcuZqKjU5Dc4ZF" + devnetMemoProgram = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + + // Hardcoded EVM private key for simulation tests. + // ETH address: 0xc681e7bdacfe4dc7209a15ff052f897c3d87008f + // Set this address in the TSS PDA on the gateway contract for signatures to pass on-chain. + testEVMPrivKeyHex = "d54b0eb459b7c0b82e3c21ced25f52a0a7fae6ed1a8614df46dda86c8d5f1e59" + + // Hardcoded Solana relayer keypair for simulation tests. + // Pubkey: AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM + testSolanaKeypairJSON = `[226,7,176,193,18,2,55,106,191,150,176,87,157,216,118,97,236,128,2,104,181,206,160,147,5,152,0,115,23,8,103,189,143,19,31,194,227,248,222,123,219,13,143,47,154,104,201,235,13,16,11,45,117,154,117,37,130,196,58,154,89,228,136,32]` +) + +// setupDevnetSimulation creates RPCClient and TxBuilder for devnet. +// Uses the hardcoded Solana relayer keypair (AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM). +func setupDevnetSimulation(t *testing.T) (*RPCClient, *TxBuilder) { + + t.Skip("skipping simulation tests") // DELIBERATELY SKIPPING SIMULATION TESTS + t.Helper() + if testing.Short() { + t.Skip("skipping simulation test in short mode") + } + + logger := zerolog.New(zerolog.NewTestWriter(t)).Level(zerolog.DebugLevel) + rpcClient, err := NewRPCClient([]string{devnetRPCURL}, devnetGenesisHash, logger) + if err != nil { + t.Skipf("skipping: failed to connect to Devnet RPC: %v", err) + } + + // Write the hardcoded keypair JSON to the temp dir so loadRelayerKeypair can find it. + tmpDir := t.TempDir() + relayerDir := filepath.Join(tmpDir, "relayer") + require.NoError(t, os.MkdirAll(relayerDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(relayerDir, "solana.json"), []byte(testSolanaKeypairJSON), 0o600)) + + builder, err := NewTxBuilder(rpcClient, "solana:"+devnetGenesisHash, devnetGatewayAddress, tmpDir, logger, nil) + require.NoError(t, err) + + t.Logf("relayer pubkey: AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM") + return rpcClient, builder +} + +// loadTestEVMKey loads the hardcoded EVM private key and returns the key + ETH address hex. +func loadTestEVMKey(t *testing.T) (*ecdsa.PrivateKey, string) { + t.Helper() + privBytes, err := hex.DecodeString(testEVMPrivKeyHex) + require.NoError(t, err) + key, err := crypto.ToECDSA(privBytes) + require.NoError(t, err) + pubBytes := crypto.FromECDSAPub(&key.PublicKey) + addrBytes := crypto.Keccak256(pubBytes[1:])[12:] + return key, hex.EncodeToString(addrBytes) +} + +// newDevnetOutbound creates an OutboundCreatedEvent using the hardcoded EVM key as sender +// and a fresh Solana wallet as recipient. Uses random tx_id/utx_id for each call to avoid +// executed_tx PDA collisions between tests. +func newDevnetOutbound(t *testing.T, amount, assetAddr, payload, revertMsg, txType string) (*uetypes.OutboundCreatedEvent, *ecdsa.PrivateKey) { + t.Helper() + + evmKey, ethAddrHex := loadTestEVMKey(t) + recipientWallet := solana.NewWallet() + + txIDBytes := make([]byte, 32) + utxIDBytes := make([]byte, 32) + _, err := crand.Read(txIDBytes) + require.NoError(t, err) + _, err = crand.Read(utxIDBytes) + require.NoError(t, err) + return &uetypes.OutboundCreatedEvent{ + TxID: "0x" + hex.EncodeToString(txIDBytes), + UniversalTxId: "0x" + hex.EncodeToString(utxIDBytes), + DestinationChain: "solana:" + devnetGenesisHash, + Sender: "0x" + ethAddrHex, + Recipient: recipientWallet.PublicKey().String(), + Amount: amount, + AssetAddr: assetAddr, + Payload: payload, + GasLimit: "400000", + // Gateway enforces gas_fee ≥ on-chain gas_used. Native SOL paths need + // ~952k (signature fee + executed_sub_tx rent). SPL paths additionally + // create the CEA ATA (~2.04M rent). 3M covers both with headroom. + GasFee: "3000000", + TxType: txType, + RevertMsg: revertMsg, + // 10-minute window past wall-clock — the on-chain program enforces + // Clock::unix_timestamp <= signing_deadline + SigningDeadline: time.Now().Unix() + 600, + }, evmKey +} + +// buildAndSimulate runs the full pipeline: GetOutboundSigningRequest → sign → BuildOutboundTransaction → SimulateTransaction. +// Uses simulation (no broadcast), so no on-chain state is modified (nonce stays the same, no SOL spent). +// Returns the simulation result and any build errors. +func buildAndSimulate(t *testing.T, rpcClient *RPCClient, builder *TxBuilder, data *uetypes.OutboundCreatedEvent, evmKey *ecdsa.PrivateKey) (*rpc.SimulateTransactionResult, error) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Step 1: Build signing request (fetches chain ID from on-chain TSS PDA) @@ -1701,8 +2417,10 @@ func buildAndSimulateRescue(t *testing.T, rpcClient *RPCClient, builder *TxBuild } gasFee := uint64(0) + // 10-minute window past wall-clock; gateway enforces Clock::unix_timestamp <= deadline. + deadline := time.Now().Unix() + 600 messageHash, err := builder.constructTSSMessage( - 4, chainID, amount, + 4, chainID, deadline, amount, txID, universalTxID, sender, token, gasFee, [32]byte{}, nil, nil, revertRecipient, revertMint, nil, @@ -1711,7 +2429,7 @@ func buildAndSimulateRescue(t *testing.T, rpcClient *RPCClient, builder *TxBuild sig, recoveryID := signMessageHash(t, evmKey, messageHash) - instructionData := builder.buildRescueData(txID, universalTxID, amount, gasFee, sig, recoveryID, messageHash) + instructionData := builder.buildRescueData(txID, universalTxID, amount, gasFee, deadline, sig, recoveryID, messageHash) // Derive PDAs configPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("config")}, builder.gatewayAddress) @@ -1788,155 +2506,215 @@ func TestSimulate_Rescue_SPLToken(t *testing.T) { requireSimulationSuccess(t, result) } -func TestGetNextNonce(t *testing.T) { - builder := newTestBuilder(t) +// buildAndSimulateRefRoute drives the ref-finalize pipeline through +// simulation only — no broadcasts, no state changes, no SOL spent. +// +// Pipeline: GetOutboundSigningRequest → sign → BuildRefRouteTransactions → +// +// verify both txs fit the 1232-byte limit → simulate(storeTx). +// +// Limitation: the ref-finalize tx is built and size-checked, but NOT simulated. +// Its simulation would always fail because the StoredIxData PDA only exists +// after the store tx actually lands on-chain, and SimulateTransaction is not +// stateful. Asserting "ref-finalize is well-formed" is therefore left to the +// unit tests (TestBuildWithdrawAndExecuteRefData_ArgOrder, +// TestBuildWithdrawAndExecuteAccounts_RefRouteSlots). +func buildAndSimulateRefRoute( + t *testing.T, + rpcClient *RPCClient, + builder *TxBuilder, + data *uetypes.OutboundCreatedEvent, + evmKey *ecdsa.PrivateKey, +) (storeSim *rpc.SimulateTransactionResult, storedPDA solana.PublicKey, err error) { + t.Helper() - t.Run("returns 0 with arbitrary address and finalized=true", func(t *testing.T) { - nonce, err := builder.GetNextNonce(context.Background(), "SomeAddress123", true) - require.NoError(t, err) - assert.Equal(t, uint64(0), nonce) - }) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() - t.Run("returns 0 with empty address and finalized=false", func(t *testing.T) { - nonce, err := builder.GetNextNonce(context.Background(), "", false) - require.NoError(t, err) - assert.Equal(t, uint64(0), nonce) - }) -} + req, err := builder.GetOutboundSigningRequest(ctx, data, 0) + if err != nil { + return nil, solana.PublicKey{}, fmt.Errorf("GetOutboundSigningRequest: %w", err) + } + t.Logf(" signing_hash=0x%s", hex.EncodeToString(req.SigningHash)) -func TestGetGasFeeUsed(t *testing.T) { - builder := newTestBuilder(t) + sig, recoveryID := signMessageHash(t, evmKey, req.SigningHash) + fullSig := append(sig, recoveryID) - t.Run("returns string zero for any tx hash", func(t *testing.T) { - fee, err := builder.GetGasFeeUsed(context.Background(), "5xYz...someTxHash") - require.NoError(t, err) - assert.Equal(t, "0", fee) - }) + storeTx, refTx, storedPDA, err := builder.BuildRefRouteTransactions(ctx, req, data, fullSig) + if err != nil { + return nil, solana.PublicKey{}, fmt.Errorf("BuildRefRouteTransactions: %w", err) + } + t.Logf(" stored_ix_data PDA=%s", storedPDA.String()) - t.Run("returns string zero for empty tx hash", func(t *testing.T) { - fee, err := builder.GetGasFeeUsed(context.Background(), "") - require.NoError(t, err) - assert.Equal(t, "0", fee) - }) + // Size sanity for both txs (the ref-finalize won't be simulated, so this + // is our only check that it's wire-correct on the size axis). + if storeBytes, mErr := storeTx.MarshalBinary(); mErr == nil { + t.Logf(" store_tx_bytes=%d", len(storeBytes)) + require.LessOrEqual(t, len(storeBytes), solanaTxMaxBytes, "store tx exceeds 1232-byte limit") + } + if refBytes, mErr := refTx.MarshalBinary(); mErr == nil { + t.Logf(" ref_finalize_tx_bytes=%d", len(refBytes)) + require.LessOrEqual(t, len(refBytes), solanaTxMaxBytes, "ref-finalize tx exceeds 1232-byte limit") + } + + storeSim, err = rpcClient.SimulateTransaction(ctx, storeTx) + if err != nil { + return nil, storedPDA, fmt.Errorf("simulate store: %w", err) + } + return storeSim, storedPDA, nil } -func TestNewTxBuilder_ChainConfig(t *testing.T) { - logger := zerolog.Nop() +// TestSimulate_CloseStoredIxData_MetaShape verifies the close_stored_ix_data +// account-meta list shape against devnet. We target a deliberately +// non-existent PDA so the contract advances past meta-count validation and +// fails at stored_ix_data deserialization. +// +// - PASS: simulation errors with Anchor 3012 (AccountNotInitialized) on +// stored_ix_data — meta count was correct, contract reached step 2. +// - FAIL: simulation errors with Anchor 3005 (AccountNotEnoughKeys) on +// executed_sub_tx — meta count is wrong (the production bug). +// +// No on-chain state needed; no fees spent. +func TestSimulate_CloseStoredIxData_MetaShape(t *testing.T) { + rpcClient, builder := setupDevnetSimulation(t) + defer rpcClient.Close() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() - t.Run("valid protocolALT is stored", func(t *testing.T) { - altKey := solana.NewWallet().PublicKey() - cfg := &config.ChainSpecificConfig{ - ProtocolALT: altKey.String(), - } - builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) - require.NoError(t, err) - assert.Equal(t, altKey, builder.protocolALT) - }) + relayerKey, err := builder.loadRelayerKeypair() + require.NoError(t, err) - t.Run("invalid protocolALT is silently skipped", func(t *testing.T) { - cfg := &config.ChainSpecificConfig{ - ProtocolALT: "not-valid-base58!!!", - } - builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) - require.NoError(t, err) - assert.True(t, builder.protocolALT.IsZero(), "invalid ALT should result in zero pubkey") - }) + // Deterministic fake sub_tx_id + ix_data so the derived PDAs don't exist. + var subTxID [32]byte + for i := range subTxID { + subTxID[i] = 0xAB + } + fakeIxData := []byte("never-actually-stored") + var ixDataHash [32]byte + copy(ixDataHash[:], crypto.Keccak256(fakeIxData)) - t.Run("valid tokenALTs are stored", func(t *testing.T) { - mint := solana.NewWallet().PublicKey() - alt := solana.NewWallet().PublicKey() - cfg := &config.ChainSpecificConfig{ - TokenALTs: map[string]string{ - mint.String(): alt.String(), - }, - } - builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) - require.NoError(t, err) - got, ok := builder.tokenALTs[mint] - require.True(t, ok, "expected token ALT entry for mint") - assert.Equal(t, alt, got) - }) + storedPDA, err := builder.deriveStoredIxDataPDA(subTxID, ixDataHash) + require.NoError(t, err) + executedSubTxPDA, _, err := solana.FindProgramAddress( + [][]byte{executedSubTxSeed, subTxID[:]}, + builder.gatewayAddress, + ) + require.NoError(t, err) - t.Run("invalid tokenALT mint is skipped", func(t *testing.T) { - cfg := &config.ChainSpecificConfig{ - TokenALTs: map[string]string{ - "bad-mint": solana.NewWallet().PublicKey().String(), - }, - } - builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) - require.NoError(t, err) - assert.Len(t, builder.tokenALTs, 0) - }) + accounts := builder.buildCloseStoredIxDataAccounts(relayerKey.PublicKey(), storedPDA, executedSubTxPDA) + require.Len(t, accounts, 4, "must include the executed_sub_tx meta to satisfy Anchor Option") + closeIx := solana.NewInstruction(builder.gatewayAddress, accounts, discCloseStoredIxData[:]) - t.Run("invalid tokenALT address is skipped", func(t *testing.T) { - cfg := &config.ChainSpecificConfig{ - TokenALTs: map[string]string{ - solana.NewWallet().PublicKey().String(): "bad-alt", - }, + blockhash, err := rpcClient.GetRecentBlockhash(ctx) + require.NoError(t, err) + + tx, err := solana.NewTransaction( + []solana.Instruction{closeIx}, + blockhash, + solana.TransactionPayer(relayerKey.PublicKey()), + ) + require.NoError(t, err) + _, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if key.Equals(relayerKey.PublicKey()) { + priv := relayerKey + return &priv } - builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, cfg) - require.NoError(t, err) - assert.Len(t, builder.tokenALTs, 0) + return nil }) + require.NoError(t, err) - t.Run("nil chainConfig is fine", func(t *testing.T) { - builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, nil) - require.NoError(t, err) - assert.True(t, builder.protocolALT.IsZero()) - assert.Len(t, builder.tokenALTs, 0) - }) + sim, err := rpcClient.SimulateTransaction(ctx, tx) + require.NoError(t, err) + + // The simulation MUST fail (PDA doesn't exist) but the failure must be on + // stored_ix_data (Anchor 3012) — NOT on executed_sub_tx (Anchor 3005, + // AccountNotEnoughKeys, which is the production-incident bug). + require.NotNil(t, sim.Err, "expected simulation to fail against a non-existent PDA") + + for _, log := range sim.Logs { + t.Log(log) + } + + joined := strings.Join(sim.Logs, "\n") + require.NotContains(t, joined, "AccountNotEnoughKeys", + "meta-count is wrong — Anchor rejected before reaching stored_ix_data deserialization") + require.Contains(t, joined, "AccountNotInitialized", + "expected stored_ix_data AccountNotInitialized (proves meta-count is correct)") } -func TestBuildCreateATAIdempotentInstruction(t *testing.T) { - builder := newTestBuilder(t) - payer := solana.NewWallet().PublicKey() - owner := solana.NewWallet().PublicKey() - mint := solana.NewWallet().PublicKey() +// TestSimulate_FinalizeRef_MetaShape simulates a finalize_universal_tx_with_ix_data_ref +// against a non-existent StoredIxData PDA and asserts the failure is at +// data-deserialization (Anchor 3012 / AccountNotInitialized), not at an +// earlier shape-level check (3005 AccountNotEnoughKeys / 2003 ConstraintSeeds / +// etc). +// +// Caveat: this does NOT catch ConstraintMut (2000). For `Option`, +// Anchor short-circuits at "account empty → 3012" before checking mut — so a +// missing writable flag only surfaces against a *real* on-chain PDA. Covering +// that needs a broadcast-then-simulate flow with real lamports. +func TestSimulate_FinalizeRef_MetaShape(t *testing.T) { + rpcClient, builder := setupDevnetSimulation(t) + defer rpcClient.Close() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() - ix := builder.buildCreateATAIdempotentInstruction(payer, owner, mint) + // Build a real ref-route outbound; we sim only the finalize tx against + // a PDA that hasn't been stored. The store-then-finalize ordering doesn't + // matter — Anchor's meta-level checks (mut, signer, seeds, etc.) fire + // before any account is deserialized. + ixData := make([]byte, 600) + for i := range ixData { + ixData[i] = byte('A' + (i % 26)) + } + payload := buildMockExecutePayload(nil, ixData) + payloadHex := "0x" + hex.EncodeToString(payload) + data, evmKey := newDevnetOutbound(t, "10000000", "", payloadHex, "", "FUNDS_AND_PAYLOAD") + data.Recipient = devnetMemoProgram - t.Run("program ID is ATA program", func(t *testing.T) { - expected := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") - assert.Equal(t, expected, ix.ProgramID()) - }) + req, err := builder.GetOutboundSigningRequest(ctx, data, 0) + require.NoError(t, err) + sig, recoveryID := signMessageHash(t, evmKey, req.SigningHash) + fullSig := append(sig, recoveryID) - t.Run("has 6 accounts in correct order", func(t *testing.T) { - accounts := ix.Accounts() - require.Len(t, accounts, 6) + _, refTx, _, err := builder.BuildRefRouteTransactions(ctx, req, data, fullSig) + require.NoError(t, err) - // payer (signer, writable) - assert.Equal(t, payer, accounts[0].PublicKey) - assert.True(t, accounts[0].IsSigner) - assert.True(t, accounts[0].IsWritable) + sim, err := rpcClient.SimulateTransaction(ctx, refTx) + require.NoError(t, err) + require.NotNil(t, sim.Err, "expected sim to fail against a non-existent stored_ix_data PDA") - // ATA (writable, derived deterministically) - ataProgramID := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") - expectedATA, _, _ := solana.FindProgramAddress( - [][]byte{owner.Bytes(), solana.TokenProgramID.Bytes(), mint.Bytes()}, - ataProgramID, - ) - assert.Equal(t, expectedATA, accounts[1].PublicKey) - assert.True(t, accounts[1].IsWritable) - assert.False(t, accounts[1].IsSigner) + for _, log := range sim.Logs { + t.Log(log) + } - // owner - assert.Equal(t, owner, accounts[2].PublicKey) - assert.False(t, accounts[2].IsWritable) + joined := strings.Join(sim.Logs, "\n") + require.NotContains(t, joined, "AccountNotEnoughKeys", + "finalize_ref meta-count too low (Anchor 3005)") + require.Contains(t, joined, "AccountNotInitialized", + "expected to reach stored_ix_data deserialization (proves meta-count + shape constraints passed)") +} - // mint - assert.Equal(t, mint, accounts[3].PublicKey) - assert.False(t, accounts[3].IsWritable) +// TestSimulate_RefRoute_Execute simulates the store half of the ref-finalize +// pipeline against devnet. The ref-finalize half can't be simulated standalone +// because it depends on the store tx having actually landed on-chain first; +// see buildAndSimulateRefRoute for the full rationale. +func TestSimulate_RefRoute_Execute(t *testing.T) { + rpcClient, builder := setupDevnetSimulation(t) + defer rpcClient.Close() - // system program - assert.Equal(t, solana.SystemProgramID, accounts[4].PublicKey) + // 600 bytes of ix_data — large enough to force the ref route, well under + // the 921-byte cap on the store tx itself. + ixData := make([]byte, 600) + for i := range ixData { + ixData[i] = byte('A' + (i % 26)) + } + payload := buildMockExecutePayload(nil, ixData) + payloadHex := "0x" + hex.EncodeToString(payload) - // token program - assert.Equal(t, solana.TokenProgramID, accounts[5].PublicKey) - }) + data, evmKey := newDevnetOutbound(t, "10000000", "", payloadHex, "", "FUNDS_AND_PAYLOAD") + data.Recipient = devnetMemoProgram - t.Run("instruction data is [1] for CreateIdempotent", func(t *testing.T) { - data, err := ix.Data() - require.NoError(t, err) - assert.Equal(t, []byte{1}, data) - }) + storeSim, _, err := buildAndSimulateRefRoute(t, rpcClient, builder, data, evmKey) + require.NoError(t, err) + requireSimulationSuccess(t, storeSim) } diff --git a/universalClient/tss/coordinator/coordinator_test.go b/universalClient/tss/coordinator/coordinator_test.go index 68481a858..88af3f2c1 100644 --- a/universalClient/tss/coordinator/coordinator_test.go +++ b/universalClient/tss/coordinator/coordinator_test.go @@ -52,9 +52,9 @@ func (m *coordMockTxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash str return args.Bool(0), args.Get(1).(uint64), args.Get(2).(uint64), args.Get(3).(uint8), args.Error(4) } -func (m *coordMockTxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, error) { +func (m *coordMockTxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, int64, error) { args := m.Called(ctx, txID) - return args.Bool(0), args.Error(1) + return args.Bool(0), args.Get(1).(int64), args.Error(2) } func (m *coordMockTxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, error) { diff --git a/universalClient/tss/txbroadcaster/broadcaster_test.go b/universalClient/tss/txbroadcaster/broadcaster_test.go index 6404119b5..7a601df74 100644 --- a/universalClient/tss/txbroadcaster/broadcaster_test.go +++ b/universalClient/tss/txbroadcaster/broadcaster_test.go @@ -53,9 +53,9 @@ func (m *mockTxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) return args.Bool(0), args.Get(1).(uint64), args.Get(2).(uint64), args.Get(3).(uint8), args.Error(4) } -func (m *mockTxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, error) { +func (m *mockTxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, int64, error) { args := m.Called(ctx, txID) - return args.Bool(0), args.Error(1) + return args.Bool(0), args.Get(1).(int64), args.Error(2) } func (m *mockTxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, error) { @@ -153,6 +153,42 @@ func insertSignedEvent(t *testing.T, db *gorm.DB, eventID, destChain string, non require.NoError(t, db.Create(&event).Error) } +// insertSignedSVMEventWithDeadline inserts a SIGNED outbound with an explicit +// SigningDeadline on the persisted payload. Used by tests that exercise the +// broadcaster's deadline-gated retry behavior. +func insertSignedSVMEventWithDeadline(t *testing.T, db *gorm.DB, eventID, destChain string, nonce uint64, deadlineUnix int64) { + t.Helper() + sig := hex.EncodeToString(make([]byte, 64)) + hash := hex.EncodeToString(make([]byte, 32)) + data := SignedOutboundData{ + OutboundCreatedEvent: uexecutortypes.OutboundCreatedEvent{ + TxID: "tx-123", + UniversalTxId: "utx-456", + DestinationChain: destChain, + Recipient: "0xRecipient", + Amount: "1000000", + SigningDeadline: deadlineUnix, + }, + SigningData: &SigningData{ + Signature: sig, + SigningHash: hash, + Nonce: nonce, + }, + } + body, err := json.Marshal(data) + require.NoError(t, err) + event := store.Event{ + EventID: eventID, + BlockHeight: 100, + ExpiryBlockHeight: 99999, + Type: "SIGN_OUTBOUND", + ConfirmationType: "STANDARD", + Status: store.StatusSigned, + EventData: body, + } + require.NoError(t, db.Create(&event).Error) +} + func getEvent(t *testing.T, db *gorm.DB, eventID string) store.Event { t.Helper() var ev store.Event @@ -306,7 +342,7 @@ func TestSVM_BroadcastSuccess_MarksBroadcasted(t *testing.T) { client := &mockChainClient{builder: builder} ch := newTestChains(t, "solana:mainnet", uregistrytypes.VmType_SVM, client) - insertSignedEvent(t, db, "ev-1", "solana:mainnet", 0) + insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, time.Now().Unix()+600) builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("solTxSig123", nil) @@ -330,7 +366,7 @@ func TestSVM_BroadcastFails_PDAExists_MarksBroadcasted(t *testing.T) { builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("", fmt.Errorf("tx simulation failed: account already exists")) - builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(true, nil) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(true, int64(0), nil) b := newBroadcaster(evtStore, ch, "") b.processSigned(context.Background()) @@ -340,25 +376,110 @@ func TestSVM_BroadcastFails_PDAExists_MarksBroadcasted(t *testing.T) { require.Equal(t, "solana:mainnet:", ev.BroadcastedTxHash) // empty tx hash } -func TestSVM_BroadcastFails_PDANotFound_MarksBroadcasted(t *testing.T) { - // Broadcast fails, PDA not found → permanent failure (bad payload) → BROADCASTED for resolver to REVERT. +func TestSVM_BroadcastFails_BeforeDeadline_StaysSigned(t *testing.T) { + // Broadcast fails before deadline → stay SIGNED, retry next tick. The + // deadline is the only retry cap; failures inside the window keep cycling. evtStore, db := setupTestDB(t) builder := &mockTxBuilder{} client := &mockChainClient{builder: builder} ch := newTestChains(t, "solana:mainnet", uregistrytypes.VmType_SVM, client) - insertSignedEvent(t, db, "ev-1", "solana:mainnet", 0) + insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, time.Now().Unix()+600) builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("", fmt.Errorf("simulation failed: invalid instruction")) - builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, nil) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), nil) + + b := newBroadcaster(evtStore, ch, "") + b.processSigned(context.Background()) + + ev := getEvent(t, db, "ev-1") + require.Equal(t, store.StatusSigned, ev.Status, "before deadline, failures stay SIGNED for retry") +} + +func TestSVM_BroadcastFails_PastDeadline_MarksBroadcastedForRevert(t *testing.T) { + // Past local deadline + cluster confirms deadline expired + cluster fresh → + // BROADCASTED("") so the resolver REVERTs. No broadcast attempt. + evtStore, db := setupTestDB(t) + builder := &mockTxBuilder{} + client := &mockChainClient{builder: builder} + ch := newTestChains(t, "solana:mainnet", uregistrytypes.VmType_SVM, client) + + insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, time.Now().Unix()-3600) + // PDA absent, cluster time = now (fresh) and well past deadline → cluster-confirmed expiry. + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, time.Now().Unix(), nil) b := newBroadcaster(evtStore, ch, "") b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") require.Equal(t, store.StatusBroadcasted, ev.Status) - require.Equal(t, "solana:mainnet:", ev.BroadcastedTxHash) // empty tx hash + require.Equal(t, "solana:mainnet:", ev.BroadcastedTxHash, "empty tx hash signals REVERT to resolver") + builder.AssertNotCalled(t, "BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestSVM_PastLocalDeadline_ExecutedByPeer_MarksBroadcasted(t *testing.T) { + // Past local deadline + peer landed the tx (PDA exists) → BROADCASTED("") + // so the resolver sees it and marks COMPLETED. No broadcast attempt. + evtStore, db := setupTestDB(t) + builder := &mockTxBuilder{} + client := &mockChainClient{builder: builder} + ch := newTestChains(t, "solana:mainnet", uregistrytypes.VmType_SVM, client) + + insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, time.Now().Unix()-3600) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(true, time.Now().Unix(), nil) + + b := newBroadcaster(evtStore, ch, "") + b.processSigned(context.Background()) + + ev := getEvent(t, db, "ev-1") + require.Equal(t, store.StatusBroadcasted, ev.Status) + require.Equal(t, "solana:mainnet:", ev.BroadcastedTxHash) + builder.AssertNotCalled(t, "BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestSVM_PastLocalDeadline_ClusterSaysStillInWindow_FallsThroughToBroadcast(t *testing.T) { + // Local clock ahead of cluster: local says past deadline, but the + // cluster's own (fresh) clock is still before the deadline → broadcaster + // falls through and attempts to broadcast. + evtStore, db := setupTestDB(t) + builder := &mockTxBuilder{} + client := &mockChainClient{builder: builder} + ch := newTestChains(t, "solana:mainnet", uregistrytypes.VmType_SVM, client) + + now := time.Now().Unix() + deadline := now - 1 // local says 1s past deadline + clusterTime := now - 30 // cluster says 30s before deadline; well within freshness + insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, deadline) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, clusterTime, nil) + builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return("tx-hash-ok", nil) + + b := newBroadcaster(evtStore, ch, "") + b.processSigned(context.Background()) + + ev := getEvent(t, db, "ev-1") + require.Equal(t, store.StatusBroadcasted, ev.Status) + require.Equal(t, "solana:mainnet:tx-hash-ok", ev.BroadcastedTxHash) +} + +func TestSVM_PastLocalDeadline_RPCError_StaysSigned(t *testing.T) { + // Past local deadline but cluster check itself errors → defer give-up. + // Stays SIGNED, no broadcast attempt. + evtStore, db := setupTestDB(t) + builder := &mockTxBuilder{} + client := &mockChainClient{builder: builder} + ch := newTestChains(t, "solana:mainnet", uregistrytypes.VmType_SVM, client) + + insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, time.Now().Unix()-3600) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), fmt.Errorf("RPC down")) + + b := newBroadcaster(evtStore, ch, "") + b.processSigned(context.Background()) + + ev := getEvent(t, db, "ev-1") + require.Equal(t, store.StatusSigned, ev.Status) + builder.AssertNotCalled(t, "BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything) } func TestSVM_BroadcastFails_PDACheckFails_StaysSigned(t *testing.T) { @@ -372,7 +493,7 @@ func TestSVM_BroadcastFails_PDACheckFails_StaysSigned(t *testing.T) { builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("", fmt.Errorf("RPC timeout")) - builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, fmt.Errorf("RPC down")) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), fmt.Errorf("RPC down")) b := newBroadcaster(evtStore, ch, "") b.processSigned(context.Background()) diff --git a/universalClient/tss/txbroadcaster/svm.go b/universalClient/tss/txbroadcaster/svm.go index 70ea667b0..fd0f9436b 100644 --- a/universalClient/tss/txbroadcaster/svm.go +++ b/universalClient/tss/txbroadcaster/svm.go @@ -2,25 +2,34 @@ package txbroadcaster import ( "context" + "time" "github.com/pushchain/push-chain-node/universalClient/store" ) -// broadcastSVM broadcasts a signed Solana transaction. +// broadcastSVM broadcasts a signed Solana transaction and moves the event to +// its next state. // -// With the V2 gateway contract, Solana transactions either land atomically or -// fully revert — there is no partial state. Unlike EVM (where reverted txs still -// consume nonce and land on-chain), a failed Solana CPI means nothing is created -// on-chain (no ExecutedTx PDA, no event emitted). +// Three phases, top to bottom: // -// Flow: -// 1. Broadcast the signed tx -// 2. Success → BROADCASTED with tx hash -// 3. Error → check if ExecutedTx PDA exists on-chain: -// - PDA exists (another relayer already processed it) → BROADCASTED -// - PDA not found (permanent failure: bad payload, simulation error) → BROADCASTED -// with empty tx hash, resolver will verify and REVERT -// - PDA check fails (RPC truly down) → stay SIGNED, retry next tick +// 1. If local clock is past the signed deadline, check the cluster's own +// clock (latest finalized block time) before giving up. The cluster +// clock — not the host clock — is what the gateway program enforces +// against, so it's the authoritative cutoff. +// 2. Broadcast. +// 3. On broadcast error, check whether a peer landed the same signed tx. +// +// The give-up cutoff is exactly `clusterTime > deadline`. The finalized block +// time lags the on-chain `Clock::unix_timestamp` by ~13s, so by the time our +// reading crosses the deadline the program has already been rejecting new +// attempts. Cluster staleness is not handled here: the resolver gates the +// irreversible REVERT vote on freshness, and falling through to "broadcast" +// is the safe direction if our cluster view is unreliable. +// +// Outcomes: +// - BROADCASTED(real-hash) → broadcast succeeded +// - BROADCASTED("") → peer landed it, or cluster confirmed expiry +// - stay SIGNED → retry next tick func (b *Broadcaster) broadcastSVM(ctx context.Context, event *store.Event, data *SignedOutboundData, chainID string) { client, err := b.chains.GetClient(chainID) if err != nil { @@ -32,7 +41,6 @@ func (b *Broadcaster) broadcastSVM(ctx context.Context, event *store.Event, data b.logger.Warn().Err(err).Str("event_id", event.EventID).Msg("failed to get tx builder") return } - signingReq, signature, err := decodeSigningData(data.SigningData) if err != nil { b.logger.Warn().Err(err).Str("event_id", event.EventID).Msg("failed to decode signing data") @@ -40,33 +48,50 @@ func (b *Broadcaster) broadcastSVM(ctx context.Context, event *store.Event, data } outboundData := data.OutboundCreatedEvent - txHash, broadcastErr := builder.BroadcastOutboundSigningRequest(ctx, signingReq, &outboundData, signature) + txID := outboundData.TxID + deadline := data.SigningDeadline + now := time.Now().Unix() - if broadcastErr == nil { - b.markBroadcasted(event, chainID, txHash) - return + // Past local deadline — confirm with the cluster before giving up. + if now > deadline { + executed, clusterTime, checkErr := builder.IsAlreadyExecuted(ctx, txID) + log := b.logger.With(). + Str("event_id", event.EventID).Str("chain", chainID). + Int64("signing_deadline", deadline).Int64("cluster_block_time", clusterTime).Logger() + + switch { + case checkErr != nil: + log.Debug().Err(checkErr).Msg("SVM cluster check failed at deadline, retry next tick") + return + case executed: + log.Info().Msg("SVM tx executed by peer past local deadline, marking BROADCASTED") + b.markBroadcasted(event, chainID, "") + return + case clusterTime > deadline: + log.Warn().Msg("SVM deadline cluster-confirmed expired, marking BROADCASTED for resolver REVERT") + b.markBroadcasted(event, chainID, "") + return + } + // Cluster says still inside the window (or freshness unknown) — broadcast. } - // Broadcast failed — check PDA to distinguish permanent vs transient failure. - executed, execErr := builder.IsAlreadyExecuted(ctx, outboundData.TxID) - if execErr != nil { - // RPC truly down (both broadcast and PDA check failed) — stay SIGNED, retry next tick. - b.logger.Debug().Err(broadcastErr).Str("event_id", event.EventID).Str("chain", chainID). - Msg("SVM broadcast failed and PDA check unreachable, will retry next tick") + // Broadcast attempt. + txHash, broadcastErr := builder.BroadcastOutboundSigningRequest(ctx, signingReq, &outboundData, signature) + if broadcastErr == nil { + b.markBroadcasted(event, chainID, txHash) return } - if executed { - // Another relayer already executed this tx. + // Race: a peer may have landed the same signed tx in the meantime. + if executed, _, _ := builder.IsAlreadyExecuted(ctx, txID); executed { b.logger.Info().Err(broadcastErr).Str("event_id", event.EventID).Str("chain", chainID). - Msg("broadcast failed but tx already executed on-chain, marking BROADCASTED") + Msg("SVM broadcast failed but tx executed on chain (race), marking BROADCASTED") b.markBroadcasted(event, chainID, "") return } - // RPC is reachable but PDA not found — permanent failure (bad payload, simulation error). - // Mark BROADCASTED with empty hash so resolver can verify and REVERT. - b.logger.Warn().Err(broadcastErr).Str("event_id", event.EventID).Str("chain", chainID). - Msg("SVM broadcast failed and PDA not found, marking BROADCASTED for resolver to REVERT") - b.markBroadcasted(event, chainID, "") + b.logger.Info().Err(broadcastErr). + Str("event_id", event.EventID).Str("chain", chainID). + Int64("signing_deadline", deadline). + Msg("SVM broadcast failed, staying SIGNED for next tick") } diff --git a/universalClient/tss/txresolver/resolver_test.go b/universalClient/tss/txresolver/resolver_test.go index 8ac6c0990..c3c9d783e 100644 --- a/universalClient/tss/txresolver/resolver_test.go +++ b/universalClient/tss/txresolver/resolver_test.go @@ -51,9 +51,9 @@ func (m *mockTxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) return args.Bool(0), args.Get(1).(uint64), args.Get(2).(uint64), args.Get(3).(uint8), args.Error(4) } -func (m *mockTxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, error) { +func (m *mockTxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, int64, error) { args := m.Called(ctx, txID) - return args.Bool(0), args.Error(1) + return args.Bool(0), args.Get(1).(int64), args.Error(2) } func (m *mockTxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, error) { @@ -259,7 +259,7 @@ func TestSVM_PDAExists_MarksCompleted(t *testing.T) { eventData := makeOutboundEventData("tx-123", "utx-456", "solana:mainnet") insertBroadcastedEvent(t, db, "ev-1", "solana:mainnet", "solana:mainnet:solTxSig", eventData) - builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(true, nil) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(true, int64(0), nil) resolver := newResolver(evtStore, ch) ev := getEvent(t, db, "ev-1") @@ -279,7 +279,7 @@ func TestSVM_PDANotFound_VotesFailureAndReverts(t *testing.T) { eventData := makeOutboundEventData("tx-123", "utx-456", "solana:mainnet") insertBroadcastedEvent(t, db, "ev-1", "solana:mainnet", "solana:mainnet:", eventData) - builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, nil) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), nil) // No PushSigner — voteFailure will log warning and return nil, but won't mark REVERTED // (because pushSigner is nil, it returns early). This validates the code path. @@ -303,7 +303,7 @@ func TestSVM_PDACheckFails_StaysBroadcasted(t *testing.T) { eventData := makeOutboundEventData("tx-123", "utx-456", "solana:mainnet") insertBroadcastedEvent(t, db, "ev-1", "solana:mainnet", "solana:mainnet:solTxSig", eventData) - builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, assert.AnError) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), assert.AnError) resolver := newResolver(evtStore, ch) ev := getEvent(t, db, "ev-1") @@ -776,7 +776,7 @@ func TestResolveOutbound_SVM_RoutingPath(t *testing.T) { insertBroadcastedEvent(t, db, "ev-svm-route", "solana:mainnet", "solana:mainnet:someSig", eventData) // PDA found → COMPLETED - builder.On("IsAlreadyExecuted", mock.Anything, "tx-svm-1").Return(true, nil) + builder.On("IsAlreadyExecuted", mock.Anything, "tx-svm-1").Return(true, int64(0), nil) resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) diff --git a/universalClient/tss/txresolver/svm.go b/universalClient/tss/txresolver/svm.go index 6243afa51..610fb07e5 100644 --- a/universalClient/tss/txresolver/svm.go +++ b/universalClient/tss/txresolver/svm.go @@ -2,19 +2,58 @@ package txresolver import ( "context" + "encoding/json" + "time" "github.com/pushchain/push-chain-node/universalClient/store" ) +// svmRevertSlackSeconds is the buffer past the signed deadline before the +// resolver finalizes REVERT. Gives an in-flight tx that's already confirmed +// time to reach `finalized` before we vote against it. +const svmRevertSlackSeconds int64 = 60 + +// svmClusterStaleSeconds is how far the latest finalized block's timestamp +// can lag wall-clock before the cluster is treated as halted or stalled — +// either case means our `finalized` queries may be missing recently-included +// txs, so we defer REVERT. +const svmClusterStaleSeconds int64 = 120 + +// svmEventEnvelope is the slice of the persisted outbound event the resolver +// needs to make a REVERT decision: just the chain-emitted signing deadline. +type svmEventEnvelope struct { + SigningDeadline int64 `json:"signing_deadline,omitempty"` +} + +// extractSVMDeadline returns the unix-second deadline emitted by Push chain on +// the OutboundCreatedEvent. Zero means the destination chain didn't configure +// a deadline window — caller falls back to the pre-deadline eager-revert +// behavior. +func extractSVMDeadline(event *store.Event) int64 { + var env svmEventEnvelope + if err := json.Unmarshal(event.EventData, &env); err != nil { + return 0 + } + return env.SigningDeadline +} + // resolveSVM checks the on-chain ExecutedTx PDA and moves the event to COMPLETED or REVERTED. // -// With the V2 gateway contract, Solana transactions either land atomically or fully revert. -// A failed CPI means nothing is created on-chain (no PDA, no event). The resolver checks -// whether the ExecutedTx PDA exists to determine the outcome: +// The REVERT decision is gated on the cluster's own clock (latest finalized +// block timestamp returned by IsAlreadyExecuted) rather than the host's local +// clock. This catches three failure modes that would otherwise cause a false +// REVERT: host clock skew, full cluster halt (block time stops advancing), +// and finalization stalls (production continues but finalized lags). +// +// - PDA exists → COMPLETED. +// - PDA check RPC error → stay BROADCASTED, retry. +// - PDA absent + cluster time unknown (0) → stay BROADCASTED, retry. +// - PDA absent + cluster stale (>120s old) → stay BROADCASTED, retry. +// - PDA absent + cluster says still in window → stay BROADCASTED, retry. +// - PDA absent + cluster confirms past deadline → REVERT. // -// - PDA exists → mark COMPLETED (success vote comes from destination chain event listening) -// - PDA absent → vote failure on Push chain and mark REVERTED (triggers user refund) -// - RPC error → stay BROADCASTED, retry next tick +// Legacy events (deadline = 0) preserve the pre-deadline eager-revert path — +// REVERT as soon as PDA is absent, no cluster check needed. func (r *Resolver) resolveSVM(ctx context.Context, event *store.Event, chainID string) { txID, utxID, err := extractOutboundIDs(event) if err != nil { @@ -35,9 +74,8 @@ func (r *Resolver) resolveSVM(ctx context.Context, event *store.Event, chainID s return } - executed, err := builder.IsAlreadyExecuted(ctx, txID) + executed, clusterTime, err := builder.IsAlreadyExecuted(ctx, txID) if err != nil { - // RPC error — stay BROADCASTED, retry next tick r.logger.Debug().Err(err).Str("event_id", event.EventID).Str("tx_id", txID). Msg("SVM PDA check failed, will retry next tick") return @@ -53,6 +91,35 @@ func (r *Resolver) resolveSVM(ctx context.Context, event *store.Event, chainID s return } - // PDA not found — tx was not executed on destination chain, no gas consumed + // PDA absent. Decide REVERT using the cluster's own clock so we don't + // false-revert during halt/stall or host clock skew. + deadline := extractSVMDeadline(event) + if deadline == 0 { + // Legacy event: no deadline, fall back to eager revert. + _ = r.voteOutboundFailureAndMarkReverted(ctx, event, txID, utxID, "", 0, "0", "tx not executed on destination chain") + return + } + + switch { + case clusterTime == 0: + r.logger.Debug(). + Str("event_id", event.EventID).Str("tx_id", txID).Str("chain_id", chainID). + Msg("SVM cluster time unavailable, deferring REVERT decision") + return + case time.Now().Unix()-clusterTime > svmClusterStaleSeconds: + r.logger.Warn(). + Str("event_id", event.EventID).Str("tx_id", txID).Str("chain_id", chainID). + Int64("cluster_block_time", clusterTime). + Msg("SVM cluster appears stale, deferring REVERT") + return + case clusterTime <= deadline+svmRevertSlackSeconds: + r.logger.Debug(). + Str("event_id", event.EventID).Str("tx_id", txID).Str("chain_id", chainID). + Int64("signing_deadline", deadline). + Int64("cluster_block_time", clusterTime). + Msg("SVM PDA absent but cluster clock still inside deadline window, will retry next tick") + return + } + _ = r.voteOutboundFailureAndMarkReverted(ctx, event, txID, utxID, "", 0, "0", "tx not executed on destination chain") } diff --git a/x/uexecutor/keeper/create_outbound.go b/x/uexecutor/keeper/create_outbound.go index 16ca83a0a..3391c7c29 100644 --- a/x/uexecutor/keeper/create_outbound.go +++ b/x/uexecutor/keeper/create_outbound.go @@ -352,11 +352,20 @@ func (k Keeper) attachOutboundsToUtx( utx.OutboundTx = append(utx.OutboundTx, outbound) + // Compute signature expiry deadline for the destination chain. + var signingDeadline int64 + if chainCfg, err := k.uregistryKeeper.GetChainConfig(ctx, outbound.DestinationChain); err == nil { + if chainCfg.TssSigningDeadline != nil && *chainCfg.TssSigningDeadline > 0 { + signingDeadline = ctx.BlockTime().Unix() + int64(chainCfg.TssSigningDeadline.Seconds()) + } + } + // Write to pending outbounds index (inside UpdateUniversalTx closure for atomicity) if err := k.PendingOutbounds.Set(ctx, outbound.Id, types.PendingOutboundEntry{ - OutboundId: outbound.Id, - UniversalTxId: utxId, - CreatedAt: ctx.BlockHeight(), + OutboundId: outbound.Id, + UniversalTxId: utxId, + CreatedAt: ctx.BlockHeight(), + SigningDeadline: signingDeadline, }); err != nil { return fmt.Errorf("failed to set pending outbound index for %s: %w", outbound.Id, err) } @@ -386,6 +395,7 @@ func (k Keeper) attachOutboundsToUtx( PcTxHash: pcTxHash, LogIndex: logIndex, RevertMsg: revertMsg, + SigningDeadline: signingDeadline, }) if err == nil { ctx.EventManager().EmitEvent(evt) diff --git a/x/uexecutor/keeper/export_test.go b/x/uexecutor/keeper/export_test.go new file mode 100644 index 000000000..2a4060035 --- /dev/null +++ b/x/uexecutor/keeper/export_test.go @@ -0,0 +1,10 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +func (k Keeper) TestAttachOutboundsToUtx(ctx sdk.Context, utxId string, outbounds []*types.OutboundTx, revertMsg string) error { + return k.attachOutboundsToUtx(ctx, utxId, outbounds, revertMsg) +} diff --git a/x/uexecutor/keeper/pending_outbound_test.go b/x/uexecutor/keeper/pending_outbound_test.go index b4ce8bbdc..263a41b0a 100644 --- a/x/uexecutor/keeper/pending_outbound_test.go +++ b/x/uexecutor/keeper/pending_outbound_test.go @@ -3,9 +3,11 @@ package keeper_test import ( "fmt" "testing" + "time" "github.com/golang/mock/gomock" "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" "github.com/stretchr/testify/require" ) @@ -235,3 +237,133 @@ func TestPendingOutbound_MultipleOutboundsPerUTX(t *testing.T) { require.Len(resp.Entries, 2) require.Len(resp.Outbounds, 2) } + +func TestPendingOutbound_SigningDeadline_Set(t *testing.T) { + f := setupPendingOutboundFixture(t) + require := require.New(t) + + tenMin := 10 * time.Minute + f.mockUregistryKeeper.EXPECT(). + GetChainConfig(gomock.Any(), "solana:devnet"). + Return(uregistrytypes.ChainConfig{ + Chain: "solana:devnet", + TssSigningDeadline: &tenMin, + }, nil).AnyTimes() + + // Seed UTX so attachOutboundsToUtx can find it. + utx := types.UniversalTx{Id: "utx-dl-1"} + require.NoError(f.k.UniversalTx.Set(f.ctx, "utx-dl-1", utx)) + + outbound := &types.OutboundTx{ + Id: "outbound-dl-1", + DestinationChain: "solana:devnet", + Recipient: "SomeRecipient", + Amount: "5000", + OutboundStatus: types.Status_PENDING, + } + + err := f.k.TestAttachOutboundsToUtx(f.ctx, "utx-dl-1", []*types.OutboundTx{outbound}, "") + require.NoError(err) + + entry, err := f.k.PendingOutbounds.Get(f.ctx, "outbound-dl-1") + require.NoError(err) + + expectedDeadline := f.ctx.BlockTime().Unix() + int64(tenMin.Seconds()) + require.Equal(expectedDeadline, entry.SigningDeadline, + "signing_deadline should be block_time + 10 minutes") +} + +func TestPendingOutbound_SigningDeadline_NilDuration(t *testing.T) { + f := setupPendingOutboundFixture(t) + require := require.New(t) + + f.mockUregistryKeeper.EXPECT(). + GetChainConfig(gomock.Any(), "eip155:1"). + Return(uregistrytypes.ChainConfig{ + Chain: "eip155:1", + TssSigningDeadline: nil, + }, nil).AnyTimes() + + utx := types.UniversalTx{Id: "utx-dl-2"} + require.NoError(f.k.UniversalTx.Set(f.ctx, "utx-dl-2", utx)) + + outbound := &types.OutboundTx{ + Id: "outbound-dl-2", + DestinationChain: "eip155:1", + Recipient: "0xRecipient", + Amount: "1000", + OutboundStatus: types.Status_PENDING, + } + + err := f.k.TestAttachOutboundsToUtx(f.ctx, "utx-dl-2", []*types.OutboundTx{outbound}, "") + require.NoError(err) + + entry, err := f.k.PendingOutbounds.Get(f.ctx, "outbound-dl-2") + require.NoError(err) + require.Equal(int64(0), entry.SigningDeadline, + "signing_deadline should be 0 when chain has no tss_signing_deadline") +} + +func TestPendingOutbound_SigningDeadline_ChainConfigNotFound(t *testing.T) { + f := setupPendingOutboundFixture(t) + require := require.New(t) + + f.mockUregistryKeeper.EXPECT(). + GetChainConfig(gomock.Any(), "eip155:999"). + Return(uregistrytypes.ChainConfig{}, fmt.Errorf("not found")).AnyTimes() + + utx := types.UniversalTx{Id: "utx-dl-3"} + require.NoError(f.k.UniversalTx.Set(f.ctx, "utx-dl-3", utx)) + + outbound := &types.OutboundTx{ + Id: "outbound-dl-3", + DestinationChain: "eip155:999", + Recipient: "0xRecipient", + Amount: "1000", + OutboundStatus: types.Status_PENDING, + } + + err := f.k.TestAttachOutboundsToUtx(f.ctx, "utx-dl-3", []*types.OutboundTx{outbound}, "") + require.NoError(err) + + entry, err := f.k.PendingOutbounds.Get(f.ctx, "outbound-dl-3") + require.NoError(err) + require.Equal(int64(0), entry.SigningDeadline, + "signing_deadline should be 0 when chain config is not found") +} + +func TestPendingOutbound_SigningDeadline_VisibleInQuery(t *testing.T) { + f := setupPendingOutboundFixture(t) + require := require.New(t) + + // Directly set an entry with a deadline to verify the query surfaces it. + require.NoError(f.k.PendingOutbounds.Set(f.ctx, "outbound-q-1", types.PendingOutboundEntry{ + OutboundId: "outbound-q-1", + UniversalTxId: "utx-q-1", + CreatedAt: 100, + SigningDeadline: 1716700000, + })) + + utx := types.UniversalTx{ + Id: "utx-q-1", + OutboundTx: []*types.OutboundTx{{ + Id: "outbound-q-1", + DestinationChain: "solana:devnet", + Recipient: "SomeRecipient", + Amount: "5000", + OutboundStatus: types.Status_PENDING, + }}, + } + require.NoError(f.k.UniversalTx.Set(f.ctx, "utx-q-1", utx)) + + resp, err := f.queryServer.GetPendingOutbound(f.ctx, &types.QueryGetPendingOutboundRequest{ + OutboundId: "outbound-q-1", + }) + require.NoError(err) + require.Equal(int64(1716700000), resp.Entry.SigningDeadline) + + allResp, err := f.queryServer.AllPendingOutbounds(f.ctx, &types.QueryAllPendingOutboundsRequest{}) + require.NoError(err) + require.Len(allResp.Entries, 1) + require.Equal(int64(1716700000), allResp.Entries[0].SigningDeadline) +} diff --git a/x/uexecutor/types/events.go b/x/uexecutor/types/events.go index 59e621870..e4685d35c 100644 --- a/x/uexecutor/types/events.go +++ b/x/uexecutor/types/events.go @@ -3,6 +3,7 @@ package types import ( "encoding/json" "fmt" + "strconv" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -29,6 +30,7 @@ type OutboundCreatedEvent struct { PcTxHash string `json:"pc_tx_hash"` LogIndex string `json:"log_index"` RevertMsg string `json:"revert_msg"` + SigningDeadline int64 `json:"signing_deadline,omitempty"` } // NewOutboundCreatedEvent creates a Cosmos SDK event for outbound creation. @@ -60,6 +62,7 @@ func NewOutboundCreatedEvent(e OutboundCreatedEvent) (sdk.Event, error) { sdk.NewAttribute("pc_tx_hash", e.PcTxHash), sdk.NewAttribute("log_index", e.LogIndex), sdk.NewAttribute("revert_msg", e.RevertMsg), + sdk.NewAttribute("signing_deadline", strconv.FormatInt(e.SigningDeadline, 10)), sdk.NewAttribute("data", string(bz)), // full JSON payload for indexers ) diff --git a/x/uexecutor/types/query.pb.go b/x/uexecutor/types/query.pb.go index 1f2d6c209..4e964cbed 100644 --- a/x/uexecutor/types/query.pb.go +++ b/x/uexecutor/types/query.pb.go @@ -768,9 +768,10 @@ func (m *QueryAllUniversalTxResponse) GetPagination() *query.PageResponse { // Pending outbound index entry type PendingOutboundEntry struct { - OutboundId string `protobuf:"bytes,1,opt,name=outbound_id,json=outboundId,proto3" json:"outbound_id,omitempty"` - UniversalTxId string `protobuf:"bytes,2,opt,name=universal_tx_id,json=universalTxId,proto3" json:"universal_tx_id,omitempty"` - CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + OutboundId string `protobuf:"bytes,1,opt,name=outbound_id,json=outboundId,proto3" json:"outbound_id,omitempty"` + UniversalTxId string `protobuf:"bytes,2,opt,name=universal_tx_id,json=universalTxId,proto3" json:"universal_tx_id,omitempty"` + CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + SigningDeadline int64 `protobuf:"varint,4,opt,name=signing_deadline,json=signingDeadline,proto3" json:"signing_deadline,omitempty"` } func (m *PendingOutboundEntry) Reset() { *m = PendingOutboundEntry{} } @@ -827,6 +828,13 @@ func (m *PendingOutboundEntry) GetCreatedAt() int64 { return 0 } +func (m *PendingOutboundEntry) GetSigningDeadline() int64 { + if m != nil { + return m.SigningDeadline + } + return 0 +} + type QueryGetPendingOutboundRequest struct { OutboundId string `protobuf:"bytes,1,opt,name=outbound_id,json=outboundId,proto3" json:"outbound_id,omitempty"` } @@ -1054,76 +1062,77 @@ func init() { func init() { proto.RegisterFile("uexecutor/v1/query.proto", fileDescriptor_94816af5d57d33a7) } var fileDescriptor_94816af5d57d33a7 = []byte{ - // 1090 bytes of a gzipped FileDescriptorProto + // 1117 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x97, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0xc7, 0x3b, 0xa9, 0xb6, 0xdb, 0xbc, 0xfe, 0x40, 0x7a, 0x1b, 0x4a, 0xea, 0xb6, 0x69, 0xea, - 0x2e, 0x6d, 0x58, 0x5a, 0x5b, 0xe9, 0x2e, 0x15, 0x07, 0x84, 0xd4, 0x5d, 0x41, 0x15, 0x69, 0x11, - 0x21, 0x5a, 0x2e, 0x5c, 0xa2, 0x49, 0x3c, 0x4a, 0x2d, 0x5a, 0x3b, 0x1b, 0xdb, 0x55, 0xaa, 0xaa, - 0x20, 0x40, 0x5c, 0x00, 0x09, 0x10, 0x27, 0x84, 0x10, 0x37, 0xf8, 0x57, 0x38, 0xae, 0xc4, 0x85, - 0x23, 0xb4, 0xfc, 0x21, 0x28, 0xe3, 0x19, 0xc7, 0x76, 0xc6, 0x69, 0xb4, 0xca, 0xcd, 0x99, 0x79, - 0x6f, 0xde, 0xe7, 0xfb, 0x66, 0xe6, 0xcd, 0x0b, 0x14, 0x03, 0xd6, 0x67, 0xed, 0xc0, 0x77, 0x7b, - 0xe6, 0x79, 0xd5, 0x7c, 0x1e, 0xb0, 0xde, 0x85, 0xd1, 0xed, 0xb9, 0xbe, 0x8b, 0x8b, 0xd1, 0x8c, - 0x71, 0x5e, 0xd5, 0xd6, 0x3b, 0xae, 0xdb, 0x39, 0x65, 0x26, 0xed, 0xda, 0x26, 0x75, 0x1c, 0xd7, - 0xa7, 0xbe, 0xed, 0x3a, 0x5e, 0x68, 0xab, 0x25, 0x57, 0xf1, 0x2f, 0xba, 0x4c, 0xce, 0xac, 0x27, - 0x66, 0x3a, 0xd4, 0x6b, 0x76, 0x7b, 0x76, 0x9b, 0x89, 0xd9, 0x8d, 0xc4, 0x6c, 0xfb, 0x84, 0xda, - 0x4e, 0xf3, 0x8c, 0xf9, 0x54, 0x4c, 0x3f, 0x68, 0xbb, 0xde, 0x99, 0xeb, 0x99, 0x2d, 0xea, 0xb1, - 0x90, 0xcd, 0x3c, 0xaf, 0xb6, 0x98, 0x4f, 0xab, 0x66, 0x97, 0x76, 0x6c, 0x87, 0x33, 0x84, 0xb6, - 0x7a, 0x15, 0x0a, 0x1f, 0x0d, 0x2c, 0x8e, 0xa9, 0x57, 0x1f, 0x44, 0x68, 0xb0, 0xe7, 0x01, 0xf3, - 0x7c, 0x5c, 0x85, 0xf9, 0x70, 0x5d, 0xdb, 0x2a, 0x92, 0x32, 0xa9, 0xe4, 0x1b, 0x77, 0xf9, 0xef, - 0x9a, 0xa5, 0x3f, 0x85, 0x57, 0x53, 0x2e, 0x5e, 0xd7, 0x75, 0x3c, 0x86, 0x0f, 0x21, 0x1f, 0x91, - 0x72, 0xa7, 0x85, 0x83, 0x15, 0x23, 0x9e, 0x0e, 0x23, 0x72, 0x99, 0xef, 0x88, 0x2f, 0xbd, 0x05, - 0x45, 0xbe, 0xda, 0xd1, 0xe9, 0xa9, 0x9c, 0xf5, 0x24, 0xc4, 0xfb, 0x00, 0x43, 0x60, 0xb1, 0xe2, - 0x8e, 0x11, 0xaa, 0x33, 0x06, 0xea, 0x8c, 0x30, 0xf3, 0x42, 0x9d, 0x51, 0xa7, 0x1d, 0x29, 0xa0, - 0x11, 0xf3, 0xd4, 0x7f, 0x21, 0xb0, 0xaa, 0x08, 0x22, 0xb0, 0xdf, 0x02, 0x88, 0xb0, 0xbd, 0x22, - 0x29, 0xcf, 0x8e, 0xe1, 0xce, 0x4b, 0x6e, 0x0f, 0x8f, 0x13, 0x70, 0x39, 0x0e, 0xb7, 0x7b, 0x2b, - 0x5c, 0x18, 0x33, 0x41, 0x77, 0x20, 0xf2, 0xf9, 0x64, 0x90, 0xdf, 0x0f, 0x98, 0x4f, 0x27, 0xd8, - 0x83, 0x3a, 0xac, 0xa4, 0x7d, 0x84, 0x9a, 0x43, 0x80, 0xe1, 0x81, 0x10, 0x39, 0x7b, 0x2d, 0xa9, - 0x66, 0xe8, 0x94, 0x6f, 0xcb, 0x4f, 0xbd, 0x3d, 0x4c, 0x51, 0x34, 0x3f, 0xf5, 0x8d, 0xf8, 0x8d, - 0x80, 0xa6, 0x8a, 0x22, 0xd8, 0xdf, 0x86, 0x85, 0x21, 0xbb, 0xdc, 0x8a, 0x4c, 0x78, 0x88, 0xe0, - 0xa7, 0xb8, 0x19, 0x05, 0x40, 0x0e, 0x58, 0xa7, 0x3d, 0x7a, 0x26, 0xf5, 0xeb, 0x4f, 0xe0, 0x5e, - 0x62, 0x54, 0xf0, 0xee, 0xc1, 0x5c, 0x97, 0x8f, 0x88, 0x94, 0x14, 0x92, 0xa8, 0xc2, 0x5a, 0xd8, - 0xe8, 0x27, 0x50, 0x92, 0xda, 0xeb, 0xcc, 0xb1, 0x6c, 0xa7, 0x53, 0x73, 0x5a, 0x6e, 0xe0, 0x58, - 0x53, 0x4f, 0xf3, 0xb7, 0x04, 0x36, 0x33, 0x43, 0x09, 0xf6, 0x4d, 0x58, 0xb0, 0xc3, 0xb1, 0xa6, - 0x6d, 0x85, 0xb9, 0xce, 0x37, 0x40, 0x0c, 0xd5, 0xac, 0x29, 0xa6, 0x74, 0x4f, 0xec, 0xf9, 0x31, - 0xf3, 0x3f, 0x76, 0xec, 0x73, 0xd6, 0xf3, 0xe8, 0xe9, 0xb3, 0xbe, 0xd4, 0xbc, 0x0c, 0xb9, 0xe8, - 0x78, 0xe7, 0x6c, 0x4b, 0xa7, 0xb0, 0xa6, 0xb4, 0x16, 0xd8, 0x8f, 0x61, 0x31, 0x90, 0xc3, 0x4d, - 0xbf, 0x2f, 0x92, 0xb4, 0x99, 0x4c, 0x7c, 0xcc, 0xf1, 0x29, 0xeb, 0xd0, 0xf6, 0x45, 0x63, 0x21, - 0x18, 0x0e, 0xe9, 0xd6, 0xf0, 0x10, 0x2a, 0x80, 0xa6, 0xb5, 0x09, 0xbf, 0x13, 0xa1, 0x24, 0x1d, - 0x46, 0x28, 0x79, 0x17, 0x96, 0xe2, 0x4a, 0xe4, 0x71, 0x5f, 0xcd, 0x94, 0xd2, 0x58, 0x8c, 0x89, - 0x98, 0xe2, 0xfe, 0x7c, 0x06, 0x05, 0x71, 0x48, 0x3e, 0x0c, 0x7c, 0xbe, 0xfd, 0xef, 0x39, 0x7e, - 0xef, 0x62, 0x70, 0x42, 0x5c, 0x31, 0x30, 0xac, 0x40, 0x20, 0x87, 0x6a, 0x16, 0xee, 0xc0, 0x2b, - 0x71, 0x05, 0x03, 0xa3, 0x1c, 0x37, 0x5a, 0x8a, 0x81, 0xd6, 0x2c, 0xdc, 0x00, 0x68, 0xf7, 0x18, - 0xf5, 0x99, 0xd5, 0xa4, 0x7e, 0x71, 0xb6, 0x4c, 0x2a, 0xb3, 0x8d, 0xbc, 0x18, 0x39, 0xf2, 0xf5, - 0x23, 0x71, 0x2f, 0x8e, 0x99, 0x9f, 0xe2, 0x90, 0x5b, 0x72, 0x1b, 0x89, 0xfe, 0xa3, 0x3c, 0xf0, - 0xaa, 0x35, 0xa2, 0xe2, 0x72, 0x87, 0x0d, 0x74, 0x89, 0x2d, 0xd5, 0x53, 0x77, 0x55, 0x91, 0x81, - 0x46, 0xe8, 0x80, 0x8f, 0x60, 0x5e, 0xc6, 0x12, 0x79, 0x2e, 0x26, 0x9d, 0xa5, 0xd7, 0xb3, 0x7e, - 0x23, 0xb2, 0xd4, 0xed, 0x91, 0x3b, 0x28, 0xcd, 0xa6, 0x7e, 0xdf, 0xff, 0x25, 0x50, 0xce, 0x8e, - 0x25, 0xf4, 0xbf, 0x03, 0x77, 0x07, 0x72, 0xec, 0xe8, 0x8d, 0x9b, 0x24, 0x03, 0xd2, 0x05, 0x0f, - 0x21, 0x2f, 0x95, 0x79, 0xc5, 0x1c, 0xf7, 0xcf, 0x4e, 0xc2, 0xd0, 0x34, 0x75, 0x4a, 0x67, 0x5f, - 0xfa, 0x94, 0x1e, 0x7c, 0xb7, 0x00, 0x77, 0xb8, 0x46, 0xfc, 0x14, 0xe6, 0xc2, 0xca, 0x8a, 0xe5, - 0x24, 0xc1, 0x68, 0xe1, 0xd6, 0xb6, 0xc6, 0x58, 0x84, 0x41, 0xf4, 0xf5, 0x2f, 0xff, 0xfa, 0xef, - 0xa7, 0xdc, 0x0a, 0x16, 0xcc, 0x44, 0x57, 0x15, 0x16, 0x6d, 0xfc, 0x99, 0x00, 0x8e, 0x56, 0x51, - 0xdc, 0x53, 0xac, 0x9b, 0x59, 0xd7, 0xb5, 0xfd, 0x09, 0xad, 0x05, 0xd1, 0x0e, 0x27, 0x2a, 0x63, - 0x29, 0x45, 0x14, 0x9a, 0x37, 0x6d, 0x09, 0xf1, 0x3d, 0x81, 0xe5, 0x64, 0x99, 0xc4, 0x8a, 0x22, - 0x92, 0xb2, 0xee, 0x6a, 0x6f, 0x4c, 0x60, 0x29, 0x78, 0x2a, 0x9c, 0x47, 0xc7, 0x72, 0x92, 0x27, - 0x51, 0xbd, 0xcc, 0x4b, 0xdb, 0xba, 0xc2, 0x6f, 0x08, 0x2c, 0x27, 0xcb, 0x9d, 0x92, 0x48, 0x59, - 0x78, 0x95, 0x44, 0xea, 0xda, 0xa9, 0x6f, 0x73, 0xa2, 0x0d, 0x5c, 0x1b, 0x43, 0x84, 0x9f, 0xc3, - 0xbc, 0xec, 0xdb, 0x50, 0x57, 0xa9, 0x4d, 0xb6, 0xbc, 0xda, 0xf6, 0x58, 0x1b, 0x11, 0xf9, 0x01, - 0x8f, 0x7c, 0x1f, 0x75, 0x53, 0xdd, 0xa1, 0x9b, 0x97, 0xb2, 0x65, 0xbb, 0xc2, 0x2f, 0x08, 0x2c, - 0xc6, 0x3b, 0x4e, 0xdc, 0x51, 0x2b, 0x4c, 0xf7, 0xbd, 0xda, 0xee, 0xad, 0x76, 0x82, 0xa6, 0xcc, - 0x69, 0x34, 0x2c, 0x66, 0xd0, 0x78, 0xf8, 0x15, 0x81, 0x7c, 0xd4, 0x32, 0xa1, 0x4a, 0x62, 0xba, - 0xed, 0xd4, 0xee, 0x8f, 0x37, 0x12, 0xa1, 0xdf, 0xe4, 0xa1, 0x5f, 0xc7, 0x6d, 0x33, 0xe3, 0xcf, - 0x48, 0x3c, 0x13, 0x5f, 0x13, 0x58, 0x4a, 0xb4, 0x7c, 0x98, 0x21, 0x71, 0xa4, 0xf5, 0xd4, 0x2a, - 0xb7, 0x1b, 0x0a, 0xa2, 0x2d, 0x4e, 0xb4, 0x86, 0xab, 0x59, 0x44, 0x1e, 0xfe, 0x41, 0x00, 0x47, - 0x9f, 0x08, 0xe5, 0x6d, 0xce, 0x7c, 0x8d, 0x94, 0xb7, 0x39, 0xfb, 0xdd, 0xd1, 0x1f, 0x71, 0x2c, - 0x03, 0xf7, 0xd4, 0xb7, 0x59, 0x96, 0x4a, 0xf3, 0x32, 0xf6, 0xc4, 0x5d, 0xe1, 0xaf, 0x04, 0xee, - 0x29, 0xaa, 0x39, 0x8e, 0x2f, 0x25, 0xe9, 0x17, 0x46, 0x33, 0x26, 0x35, 0x17, 0xb0, 0xbb, 0x1c, - 0x76, 0x0b, 0x37, 0xc7, 0xc3, 0x7a, 0x8f, 0xeb, 0x7f, 0x5e, 0x97, 0xc8, 0x8b, 0xeb, 0x12, 0xf9, - 0xe7, 0xba, 0x44, 0x7e, 0xb8, 0x29, 0xcd, 0xbc, 0xb8, 0x29, 0xcd, 0xfc, 0x7d, 0x53, 0x9a, 0xf9, - 0xe4, 0xb0, 0x63, 0xfb, 0x27, 0x41, 0xcb, 0x68, 0xbb, 0x67, 0x66, 0x37, 0xf0, 0x4e, 0x78, 0xfe, - 0xf9, 0xd7, 0x3e, 0xff, 0xdc, 0x77, 0x5c, 0x8b, 0x99, 0xfd, 0x58, 0x00, 0xfe, 0xc7, 0xb7, 0x35, - 0xc7, 0xff, 0x90, 0x3e, 0xfc, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xe5, 0x73, 0xda, 0x5b, 0x0f, - 0x00, 0x00, + 0x14, 0xc7, 0x3b, 0x29, 0xdb, 0x6d, 0x5e, 0x7f, 0x2c, 0x9a, 0x0d, 0x25, 0x75, 0xdb, 0x34, 0x75, + 0x97, 0x36, 0xbb, 0xb4, 0xb6, 0xd2, 0x5d, 0x2a, 0x0e, 0x08, 0xa9, 0xbb, 0x40, 0x15, 0x69, 0x11, + 0x21, 0x5a, 0x2e, 0x5c, 0xa2, 0x49, 0x3c, 0x72, 0x2d, 0x52, 0x3b, 0x9b, 0xb1, 0xab, 0x54, 0x55, + 0x85, 0x00, 0x71, 0x01, 0x24, 0x40, 0x9c, 0x10, 0x42, 0xdc, 0x40, 0xfc, 0x27, 0x1c, 0x57, 0xe2, + 0xc2, 0x11, 0x5a, 0xfe, 0x10, 0x94, 0xf1, 0x8c, 0x63, 0x3b, 0xe3, 0x34, 0x42, 0xb9, 0x39, 0x33, + 0xef, 0xcd, 0xfb, 0x7c, 0xdf, 0xcc, 0xbc, 0x79, 0x81, 0x62, 0x40, 0xfb, 0xb4, 0x1d, 0xf8, 0x5e, + 0xcf, 0x3c, 0xab, 0x9a, 0xcf, 0x03, 0xda, 0x3b, 0x37, 0xba, 0x3d, 0xcf, 0xf7, 0xf0, 0x62, 0x34, + 0x63, 0x9c, 0x55, 0xb5, 0x75, 0xdb, 0xf3, 0xec, 0x0e, 0x35, 0x49, 0xd7, 0x31, 0x89, 0xeb, 0x7a, + 0x3e, 0xf1, 0x1d, 0xcf, 0x65, 0xa1, 0xad, 0x96, 0x5c, 0xc5, 0x3f, 0xef, 0x52, 0x39, 0xb3, 0x9e, + 0x98, 0xb1, 0x09, 0x6b, 0x76, 0x7b, 0x4e, 0x9b, 0x8a, 0xd9, 0x8d, 0xc4, 0x6c, 0xfb, 0x84, 0x38, + 0x6e, 0xf3, 0x94, 0xfa, 0x44, 0x4c, 0x3f, 0x68, 0x7b, 0xec, 0xd4, 0x63, 0x66, 0x8b, 0x30, 0x1a, + 0xb2, 0x99, 0x67, 0xd5, 0x16, 0xf5, 0x49, 0xd5, 0xec, 0x12, 0xdb, 0x71, 0x39, 0x43, 0x68, 0xab, + 0x57, 0xa1, 0xf0, 0xe1, 0xc0, 0xe2, 0x98, 0xb0, 0xfa, 0x20, 0x42, 0x83, 0x3e, 0x0f, 0x28, 0xf3, + 0xf1, 0x2a, 0xcc, 0x87, 0xeb, 0x3a, 0x56, 0x11, 0x95, 0x51, 0x25, 0xdf, 0xb8, 0xcd, 0x7f, 0xd7, + 0x2c, 0xfd, 0x29, 0xbc, 0x92, 0x72, 0x61, 0x5d, 0xcf, 0x65, 0x14, 0x3f, 0x84, 0x7c, 0x44, 0xca, + 0x9d, 0x16, 0x0e, 0x56, 0x8c, 0x78, 0x3a, 0x8c, 0xc8, 0x65, 0xde, 0x16, 0x5f, 0x7a, 0x0b, 0x8a, + 0x7c, 0xb5, 0xa3, 0x4e, 0x47, 0xce, 0x32, 0x09, 0xf1, 0x1e, 0xc0, 0x10, 0x58, 0xac, 0xb8, 0x63, + 0x84, 0xea, 0x8c, 0x81, 0x3a, 0x23, 0xcc, 0xbc, 0x50, 0x67, 0xd4, 0x89, 0x2d, 0x05, 0x34, 0x62, + 0x9e, 0xfa, 0x4f, 0x08, 0x56, 0x15, 0x41, 0x04, 0xf6, 0x1b, 0x00, 0x11, 0x36, 0x2b, 0xa2, 0xf2, + 0xec, 0x18, 0xee, 0xbc, 0xe4, 0x66, 0xf8, 0x38, 0x01, 0x97, 0xe3, 0x70, 0xbb, 0x37, 0xc2, 0x85, + 0x31, 0x13, 0x74, 0x07, 0x22, 0x9f, 0x4f, 0x06, 0xf9, 0x7d, 0x9f, 0xfa, 0x64, 0x82, 0x3d, 0xa8, + 0xc3, 0x4a, 0xda, 0x47, 0xa8, 0x39, 0x04, 0x18, 0x1e, 0x08, 0x91, 0xb3, 0x57, 0x93, 0x6a, 0x86, + 0x4e, 0xf9, 0xb6, 0xfc, 0xd4, 0xdb, 0xc3, 0x14, 0x45, 0xf3, 0x53, 0xdf, 0x88, 0x5f, 0x10, 0x68, + 0xaa, 0x28, 0x82, 0xfd, 0x4d, 0x58, 0x18, 0xb2, 0xcb, 0xad, 0xc8, 0x84, 0x87, 0x08, 0x7e, 0x8a, + 0x9b, 0x51, 0x00, 0xcc, 0x01, 0xeb, 0xa4, 0x47, 0x4e, 0xa5, 0x7e, 0xfd, 0x09, 0xdc, 0x4d, 0x8c, + 0x0a, 0xde, 0x3d, 0x98, 0xeb, 0xf2, 0x11, 0x91, 0x92, 0x42, 0x12, 0x55, 0x58, 0x0b, 0x1b, 0xfd, + 0x04, 0x4a, 0x52, 0x7b, 0x9d, 0xba, 0x96, 0xe3, 0xda, 0x35, 0xb7, 0xe5, 0x05, 0xae, 0x35, 0xf5, + 0x34, 0x7f, 0x8d, 0x60, 0x33, 0x33, 0x94, 0x60, 0xdf, 0x84, 0x05, 0x27, 0x1c, 0x6b, 0x3a, 0x56, + 0x98, 0xeb, 0x7c, 0x03, 0xc4, 0x50, 0xcd, 0x9a, 0x62, 0x4a, 0xf7, 0xc4, 0x9e, 0x1f, 0x53, 0xff, + 0x23, 0xd7, 0x39, 0xa3, 0x3d, 0x46, 0x3a, 0xcf, 0xfa, 0x52, 0xf3, 0x32, 0xe4, 0xa2, 0xe3, 0x9d, + 0x73, 0x2c, 0x9d, 0xc0, 0x9a, 0xd2, 0x5a, 0x60, 0x3f, 0x86, 0xc5, 0x40, 0x0e, 0x37, 0xfd, 0xbe, + 0x48, 0xd2, 0x66, 0x32, 0xf1, 0x31, 0xc7, 0xa7, 0xd4, 0x26, 0xed, 0xf3, 0xc6, 0x42, 0x30, 0x1c, + 0xd2, 0xad, 0xe1, 0x21, 0x54, 0x00, 0x4d, 0x6b, 0x13, 0x7e, 0x45, 0x42, 0x49, 0x3a, 0x8c, 0x50, + 0xf2, 0x36, 0x2c, 0xc5, 0x95, 0xc8, 0xe3, 0xbe, 0x9a, 0x29, 0xa5, 0xb1, 0x18, 0x13, 0x31, 0xc5, + 0xfd, 0xf9, 0x1d, 0x41, 0x41, 0x9c, 0x92, 0x0f, 0x02, 0x9f, 0xef, 0xff, 0xbb, 0xae, 0xdf, 0x3b, + 0x1f, 0x1c, 0x11, 0x4f, 0x0c, 0x0c, 0x4b, 0x10, 0xc8, 0xa1, 0x9a, 0x85, 0x77, 0xe0, 0x4e, 0x5c, + 0xc2, 0xc0, 0x28, 0xc7, 0x8d, 0x96, 0x62, 0xa4, 0x35, 0x0b, 0x6f, 0x00, 0xb4, 0x7b, 0x94, 0xf8, + 0xd4, 0x6a, 0x12, 0xbf, 0x38, 0x5b, 0x46, 0x95, 0xd9, 0x46, 0x5e, 0x8c, 0x1c, 0xf9, 0xf8, 0x3e, + 0xbc, 0xcc, 0x1c, 0xdb, 0x75, 0x5c, 0xbb, 0x69, 0x51, 0x62, 0x75, 0x1c, 0x97, 0x16, 0x5f, 0xe2, + 0x46, 0x77, 0xc4, 0xf8, 0x3b, 0x62, 0x58, 0x3f, 0x12, 0x77, 0xe8, 0x98, 0xfa, 0x29, 0x64, 0xb9, + 0x7d, 0x37, 0x41, 0xeb, 0xdf, 0xcb, 0xcb, 0xa1, 0x5a, 0x23, 0x2a, 0x44, 0xb7, 0xe8, 0x20, 0x05, + 0x62, 0xfb, 0xf5, 0xd4, 0xbd, 0x56, 0x24, 0xab, 0x11, 0x3a, 0xe0, 0x47, 0x30, 0x2f, 0x63, 0x89, + 0x3d, 0x29, 0x26, 0x9d, 0xa5, 0xd7, 0xb3, 0x7e, 0x23, 0xb2, 0xd4, 0x9d, 0x91, 0xfb, 0x2a, 0xcd, + 0xa6, 0x5e, 0x1b, 0xfe, 0x41, 0x50, 0xce, 0x8e, 0x25, 0xf4, 0xbf, 0x05, 0xb7, 0x07, 0x72, 0x9c, + 0xe8, 0x3d, 0x9c, 0x24, 0x03, 0xd2, 0x05, 0x1f, 0x42, 0x5e, 0x2a, 0x63, 0xc5, 0x1c, 0xf7, 0xcf, + 0x4e, 0xc2, 0xd0, 0x34, 0x75, 0xa2, 0x67, 0xff, 0xf7, 0x89, 0x3e, 0xf8, 0x66, 0x01, 0x6e, 0x71, + 0x8d, 0xf8, 0x13, 0x98, 0x0b, 0xab, 0x30, 0x2e, 0x27, 0x09, 0x46, 0x8b, 0xbc, 0xb6, 0x35, 0xc6, + 0x22, 0x0c, 0xa2, 0xaf, 0x7f, 0xfe, 0xe7, 0xbf, 0x3f, 0xe4, 0x56, 0x70, 0xc1, 0x4c, 0x74, 0x60, + 0x61, 0x81, 0xc7, 0x3f, 0x22, 0xc0, 0xa3, 0x15, 0x17, 0xef, 0x29, 0xd6, 0xcd, 0x7c, 0x03, 0xb4, + 0xfd, 0x09, 0xad, 0x05, 0xd1, 0x0e, 0x27, 0x2a, 0xe3, 0x52, 0x8a, 0x28, 0x34, 0x6f, 0x3a, 0x12, + 0xe2, 0x5b, 0x04, 0xcb, 0xc9, 0x92, 0x8a, 0x2b, 0x8a, 0x48, 0xca, 0x1a, 0xad, 0xdd, 0x9f, 0xc0, + 0x52, 0xf0, 0x54, 0x38, 0x8f, 0x8e, 0xcb, 0x49, 0x9e, 0x44, 0xa5, 0x33, 0x2f, 0x1c, 0xeb, 0x12, + 0x7f, 0x85, 0x60, 0x39, 0x59, 0x1a, 0x95, 0x44, 0xca, 0x22, 0xad, 0x24, 0x52, 0xd7, 0x59, 0x7d, + 0x9b, 0x13, 0x6d, 0xe0, 0xb5, 0x31, 0x44, 0xf8, 0x53, 0x98, 0x97, 0x3d, 0x1e, 0xd6, 0x55, 0x6a, + 0x93, 0xed, 0xb1, 0xb6, 0x3d, 0xd6, 0x46, 0x44, 0x7e, 0xc0, 0x23, 0xdf, 0xc3, 0xba, 0xa9, 0xee, + 0xe6, 0xcd, 0x0b, 0xd9, 0xde, 0x5d, 0xe2, 0xcf, 0x10, 0x2c, 0xc6, 0xbb, 0x53, 0xbc, 0xa3, 0x56, + 0x98, 0xee, 0x91, 0xb5, 0xdd, 0x1b, 0xed, 0x04, 0x4d, 0x99, 0xd3, 0x68, 0xb8, 0x98, 0x41, 0xc3, + 0xf0, 0x17, 0x08, 0xf2, 0x51, 0x7b, 0x85, 0x55, 0x12, 0xd3, 0x2d, 0xaa, 0x76, 0x6f, 0xbc, 0x91, + 0x08, 0xfd, 0x3a, 0x0f, 0xfd, 0x1a, 0xde, 0x36, 0x33, 0xfe, 0xb8, 0xc4, 0x33, 0xf1, 0x25, 0x82, + 0xa5, 0x44, 0x7b, 0x88, 0x33, 0x24, 0x8e, 0xb4, 0xa9, 0x5a, 0xe5, 0x66, 0x43, 0x41, 0xb4, 0xc5, + 0x89, 0xd6, 0xf0, 0x6a, 0x16, 0x11, 0xc3, 0xbf, 0x21, 0xc0, 0xa3, 0x4f, 0x84, 0xf2, 0x36, 0x67, + 0xbe, 0x46, 0xca, 0xdb, 0x9c, 0xfd, 0xee, 0xe8, 0x8f, 0x38, 0x96, 0x81, 0xf7, 0xd4, 0xb7, 0x59, + 0x96, 0x4a, 0xf3, 0x22, 0xf6, 0xc4, 0x5d, 0xe2, 0x9f, 0x11, 0xdc, 0x55, 0x54, 0x73, 0x3c, 0xbe, + 0x94, 0xa4, 0x5f, 0x18, 0xcd, 0x98, 0xd4, 0x5c, 0xc0, 0xee, 0x72, 0xd8, 0x2d, 0xbc, 0x39, 0x1e, + 0x96, 0x3d, 0xae, 0xff, 0x71, 0x55, 0x42, 0x2f, 0xae, 0x4a, 0xe8, 0xef, 0xab, 0x12, 0xfa, 0xee, + 0xba, 0x34, 0xf3, 0xe2, 0xba, 0x34, 0xf3, 0xd7, 0x75, 0x69, 0xe6, 0xe3, 0x43, 0xdb, 0xf1, 0x4f, + 0x82, 0x96, 0xd1, 0xf6, 0x4e, 0xcd, 0x6e, 0xc0, 0x4e, 0x78, 0xfe, 0xf9, 0xd7, 0x3e, 0xff, 0xdc, + 0x77, 0x3d, 0x8b, 0x9a, 0xfd, 0x58, 0x00, 0xfe, 0x27, 0xb9, 0x35, 0xc7, 0xff, 0xbc, 0x3e, 0xfc, + 0x2f, 0x00, 0x00, 0xff, 0xff, 0x4d, 0x8b, 0xef, 0x08, 0x87, 0x0f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2154,6 +2163,11 @@ func (m *PendingOutboundEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.SigningDeadline != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.SigningDeadline)) + i-- + dAtA[i] = 0x20 + } if m.CreatedAt != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.CreatedAt)) i-- @@ -2607,6 +2621,9 @@ func (m *PendingOutboundEntry) Size() (n int) { if m.CreatedAt != 0 { n += 1 + sovQuery(uint64(m.CreatedAt)) } + if m.SigningDeadline != 0 { + n += 1 + sovQuery(uint64(m.SigningDeadline)) + } return n } @@ -4258,6 +4275,25 @@ func (m *PendingOutboundEntry) Unmarshal(dAtA []byte) error { break } } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SigningDeadline", wireType) + } + m.SigningDeadline = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SigningDeadline |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:]) diff --git a/x/uregistry/types/chain_config.go b/x/uregistry/types/chain_config.go index 1d8c1ae42..e9f6a6322 100644 --- a/x/uregistry/types/chain_config.go +++ b/x/uregistry/types/chain_config.go @@ -62,6 +62,10 @@ func (p ChainConfig) ValidateBasic() error { } } + if p.TssSigningDeadline != nil && *p.TssSigningDeadline < 0 { + return errors.Wrap(sdkerrors.ErrInvalidRequest, "tss_signing_deadline must not be negative") + } + if p.BlockConfirmation == nil { return errors.Wrap(sdkerrors.ErrInvalidRequest, "block_confirmation is required") } diff --git a/x/uregistry/types/chain_config_test.go b/x/uregistry/types/chain_config_test.go index f470dea26..464aa61dc 100644 --- a/x/uregistry/types/chain_config_test.go +++ b/x/uregistry/types/chain_config_test.go @@ -2,11 +2,17 @@ package types_test import ( "testing" + "time" "github.com/pushchain/push-chain-node/x/uregistry/types" "github.com/stretchr/testify/require" ) +func durationPtr(seconds int64) *time.Duration { + d := time.Duration(seconds) * time.Second + return &d +} + func TestChainConfig_ValidateBasic(t *testing.T) { validMethod := &types.GatewayMethods{ Name: "add_funds", @@ -194,6 +200,49 @@ func TestChainConfig_ValidateBasic(t *testing.T) { }, expectErr: false, }, + { + name: "valid - with tss_signing_deadline", + config: types.ChainConfig{ + Chain: "solana:devnet", + VmType: types.VmType_SVM, + PublicRpcUrl: "https://api.devnet.solana.com", + GatewayAddress: "addr", + BlockConfirmation: validBlockConfirmation, + GatewayMethods: []*types.GatewayMethods{validMethod}, + GasOracleFetchInterval: 30, + TssSigningDeadline: durationPtr(10 * 60), // 10 minutes + }, + expectErr: false, + }, + { + name: "valid - nil tss_signing_deadline", + config: types.ChainConfig{ + Chain: "eip155:1", + VmType: types.VmType_EVM, + PublicRpcUrl: "https://mainnet.infura.io", + GatewayAddress: "0x1234", + BlockConfirmation: validBlockConfirmation, + GatewayMethods: []*types.GatewayMethods{validMethod}, + GasOracleFetchInterval: 30, + TssSigningDeadline: nil, + }, + expectErr: false, + }, + { + name: "invalid - negative tss_signing_deadline", + config: types.ChainConfig{ + Chain: "solana:devnet", + VmType: types.VmType_SVM, + PublicRpcUrl: "https://api.devnet.solana.com", + GatewayAddress: "addr", + BlockConfirmation: validBlockConfirmation, + GatewayMethods: []*types.GatewayMethods{validMethod}, + GasOracleFetchInterval: 30, + TssSigningDeadline: durationPtr(-60), + }, + expectErr: true, + errMsg: "tss_signing_deadline must not be negative", + }, { name: "invalid - bad vault method inside vault_methods", config: types.ChainConfig{ diff --git a/x/uregistry/types/types.pb.go b/x/uregistry/types/types.pb.go index 3d26d1d8c..3eed1c5bd 100644 --- a/x/uregistry/types/types.pb.go +++ b/x/uregistry/types/types.pb.go @@ -439,6 +439,7 @@ type ChainConfig struct { Enabled *ChainEnabled `protobuf:"bytes,7,opt,name=enabled,proto3" json:"enabled,omitempty"` GasOracleFetchInterval time.Duration `protobuf:"bytes,8,opt,name=gas_oracle_fetch_interval,json=gasOracleFetchInterval,proto3,stdduration" json:"gas_oracle_fetch_interval"` VaultMethods []*VaultMethods `protobuf:"bytes,9,rep,name=vault_methods,json=vaultMethods,proto3" json:"vault_methods,omitempty"` + TssSigningDeadline *time.Duration `protobuf:"bytes,10,opt,name=tss_signing_deadline,json=tssSigningDeadline,proto3,stdduration" json:"tss_signing_deadline,omitempty"` } func (m *ChainConfig) Reset() { *m = ChainConfig{} } @@ -536,6 +537,13 @@ func (m *ChainConfig) GetVaultMethods() []*VaultMethods { return nil } +func (m *ChainConfig) GetTssSigningDeadline() *time.Duration { + if m != nil { + return m.TssSigningDeadline + } + return nil +} + type NativeRepresentation struct { Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` ContractAddress string `protobuf:"bytes,2,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty"` @@ -711,78 +719,80 @@ func init() { func init() { proto.RegisterFile("uregistry/v1/types.proto", fileDescriptor_11eea54f17422d86) } var fileDescriptor_11eea54f17422d86 = []byte{ - // 1132 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0xcd, 0x6e, 0xe3, 0xd4, - 0x17, 0x8f, 0x93, 0x36, 0x1f, 0x27, 0x69, 0xea, 0x5c, 0xe5, 0xdf, 0x71, 0xa3, 0xf9, 0x27, 0x25, - 0x33, 0x82, 0x4e, 0x35, 0x4d, 0x68, 0x61, 0x06, 0xa9, 0x12, 0x42, 0x69, 0x9a, 0x42, 0xd4, 0x26, - 0xa9, 0x6e, 0x3c, 0xa9, 0x60, 0x81, 0x75, 0x63, 0xdf, 0x26, 0xd6, 0xf8, 0x23, 0xd8, 0x4e, 0x20, - 0x0f, 0xc0, 0x06, 0x21, 0x04, 0xbb, 0x59, 0xce, 0x23, 0xb0, 0xe0, 0x21, 0x66, 0x39, 0x4b, 0x56, - 0x30, 0x6a, 0x91, 0xe0, 0x19, 0x58, 0x21, 0x5f, 0xdb, 0x89, 0xdd, 0x74, 0xd8, 0xb3, 0x69, 0xef, - 0xf9, 0x9d, 0xe3, 0xf3, 0xf9, 0x3b, 0xa7, 0x05, 0x61, 0x6a, 0xd1, 0x91, 0x6a, 0x3b, 0xd6, 0xbc, - 0x3e, 0x3b, 0xa8, 0x3b, 0xf3, 0x09, 0xb5, 0x6b, 0x13, 0xcb, 0x74, 0x4c, 0x94, 0x5b, 0x68, 0x6a, - 0xb3, 0x83, 0x52, 0x71, 0x64, 0x8e, 0x4c, 0xa6, 0xa8, 0xbb, 0x2f, 0xcf, 0xa6, 0x54, 0x20, 0xba, - 0x6a, 0x98, 0x75, 0xf6, 0xd3, 0x87, 0xca, 0x23, 0xd3, 0x1c, 0x69, 0xb4, 0xce, 0xa4, 0xe1, 0xf4, - 0xaa, 0xae, 0x4c, 0x2d, 0xe2, 0xa8, 0xa6, 0xe1, 0xe9, 0xab, 0x1f, 0x43, 0xf2, 0x82, 0x58, 0x44, - 0xb7, 0x51, 0x11, 0xd6, 0x89, 0xa2, 0xab, 0x86, 0xc0, 0xed, 0x70, 0xbb, 0x19, 0xec, 0x09, 0x47, - 0xff, 0x7f, 0xf1, 0xb2, 0x12, 0xfb, 0xeb, 0x65, 0x85, 0xfb, 0xee, 0xcf, 0x9f, 0xf7, 0xf8, 0x65, - 0x76, 0x13, 0xf6, 0x51, 0xf5, 0x0f, 0x0e, 0xf2, 0x9f, 0x12, 0x87, 0x7e, 0x4d, 0xe6, 0x1d, 0xea, - 0x8c, 0x4d, 0xc5, 0x46, 0x08, 0xd6, 0x0c, 0xa2, 0x53, 0xdf, 0x0d, 0x7b, 0xa3, 0x32, 0x80, 0xaa, - 0x50, 0xc3, 0x51, 0xaf, 0x54, 0x6a, 0x09, 0x71, 0xa6, 0x09, 0x21, 0xe8, 0x11, 0xf0, 0x74, 0x46, - 0x0d, 0x47, 0x0a, 0x59, 0x25, 0x98, 0xd5, 0x26, 0xc3, 0xdb, 0x4b, 0xd3, 0x33, 0x28, 0xc8, 0xa6, - 0x71, 0xa5, 0x5a, 0x3a, 0x2b, 0x43, 0x72, 0x7b, 0x24, 0xac, 0xed, 0x70, 0xbb, 0xf9, 0xc3, 0x72, - 0x2d, 0xdc, 0xa3, 0x5a, 0x33, 0x64, 0x26, 0xce, 0x27, 0x14, 0xf3, 0xf2, 0x2d, 0xe4, 0xe8, 0xdd, - 0x70, 0x75, 0xdb, 0xcb, 0xea, 0x46, 0x5e, 0x49, 0x92, 0xee, 0xd5, 0x54, 0x7d, 0xc3, 0x41, 0x6e, - 0x40, 0xa6, 0x9a, 0xf3, 0x5f, 0x2c, 0xf2, 0x61, 0xb8, 0xc8, 0x7b, 0x21, 0x82, 0xb9, 0x05, 0x2d, - 0x4a, 0xfc, 0x9e, 0x83, 0xc2, 0xb1, 0x66, 0xca, 0xcf, 0xc3, 0x1e, 0xd1, 0x3b, 0x90, 0xbb, 0x22, - 0xb6, 0x23, 0xa9, 0xc6, 0xd0, 0x9c, 0x1a, 0x0a, 0xab, 0x77, 0x03, 0x67, 0x5d, 0xac, 0xed, 0x41, - 0x6e, 0x59, 0xb6, 0x43, 0x0c, 0x85, 0x58, 0xca, 0xc2, 0x2c, 0xce, 0xcc, 0x36, 0x03, 0xdc, 0x37, - 0x3d, 0x7a, 0x14, 0xce, 0xe4, 0xfe, 0x32, 0x93, 0xa1, 0x1b, 0x57, 0x0a, 0x27, 0x5e, 0xfd, 0x81, - 0x83, 0x5c, 0x73, 0x4c, 0x54, 0xa3, 0x65, 0x90, 0xa1, 0x46, 0x15, 0xb4, 0x07, 0xbc, 0x6a, 0xfb, - 0x8e, 0x7c, 0x8c, 0x65, 0x93, 0xc6, 0x2b, 0x38, 0x7a, 0x0c, 0x05, 0xd5, 0xee, 0x4d, 0x9d, 0x88, - 0x71, 0x9c, 0x19, 0xaf, 0x2a, 0xde, 0xda, 0x1f, 0xd9, 0x0d, 0x2f, 0x51, 0xcf, 0xaa, 0xfa, 0xcb, - 0x1a, 0x64, 0x59, 0x42, 0xac, 0x3f, 0x23, 0x77, 0x5d, 0x98, 0x41, 0xb0, 0x2e, 0x4c, 0x40, 0xfb, - 0x90, 0x9a, 0xe9, 0xde, 0xb8, 0xe2, 0x6c, 0x5c, 0xc5, 0xe8, 0xb8, 0x06, 0x3a, 0x1b, 0x52, 0x72, - 0xc6, 0x7e, 0xa3, 0x87, 0x90, 0x9f, 0x4c, 0x87, 0x9a, 0x2a, 0x4b, 0xd6, 0x44, 0x96, 0xa6, 0x96, - 0xe6, 0x13, 0x22, 0xe7, 0xa1, 0x78, 0x22, 0x3f, 0xb3, 0x34, 0xf4, 0x1e, 0x6c, 0x06, 0x84, 0x24, - 0x8a, 0x62, 0x51, 0xdb, 0x66, 0x5c, 0xc8, 0xe0, 0xbc, 0x0f, 0x37, 0x3c, 0x14, 0x75, 0x01, 0xad, - 0xb6, 0x52, 0x58, 0xdf, 0xe1, 0x76, 0xb3, 0x87, 0x95, 0x68, 0x22, 0x2b, 0xa3, 0xc6, 0x85, 0xe1, - 0xca, 0xf4, 0x5b, 0xcb, 0xc0, 0x3e, 0x4d, 0x84, 0xe4, 0x4e, 0x62, 0x37, 0x7b, 0x78, 0x3f, 0xea, - 0x2c, 0x7a, 0x01, 0x16, 0x69, 0x05, 0xcb, 0xf2, 0x21, 0xa4, 0xfc, 0x2e, 0x0a, 0x29, 0x96, 0x4b, - 0xe9, 0x16, 0x87, 0x43, 0x73, 0xc6, 0x81, 0x29, 0xfa, 0x12, 0xb6, 0x47, 0xc4, 0x96, 0x4c, 0x8b, - 0xc8, 0x1a, 0x95, 0xae, 0xa8, 0x23, 0x8f, 0x25, 0xd5, 0x70, 0xa8, 0x35, 0x23, 0x9a, 0x90, 0x66, - 0x7e, 0xb6, 0x6b, 0xde, 0x75, 0xab, 0x05, 0xd7, 0xad, 0x76, 0xe2, 0x5f, 0xb7, 0xe3, 0xf4, 0xab, - 0xdf, 0x2a, 0xb1, 0x17, 0xbf, 0x57, 0x38, 0xbc, 0x35, 0x22, 0x76, 0x8f, 0x39, 0x39, 0x75, 0x7d, - 0xb4, 0x7d, 0x17, 0xe8, 0x13, 0xd8, 0x88, 0x6c, 0x80, 0x90, 0x61, 0xa5, 0xdd, 0xca, 0x2d, 0xbc, - 0xf5, 0x38, 0x37, 0x0b, 0x49, 0x47, 0x0f, 0xc2, 0xbc, 0xd9, 0xba, 0xcd, 0x1b, 0x36, 0x82, 0x51, - 0xf5, 0x5b, 0x0e, 0x8a, 0x5d, 0xe2, 0xa8, 0x33, 0x8a, 0xe9, 0xc4, 0xa2, 0x36, 0x35, 0x1c, 0xaf, - 0xb7, 0x45, 0x58, 0x57, 0xa8, 0x61, 0xea, 0x01, 0x7f, 0x98, 0xe0, 0x2e, 0x93, 0x6c, 0x1a, 0x8e, - 0x45, 0x64, 0x67, 0x31, 0x6b, 0xef, 0x92, 0x6c, 0x06, 0xb8, 0x3f, 0xec, 0xa3, 0xc7, 0xe1, 0xf0, - 0x95, 0x65, 0x78, 0x83, 0x45, 0x93, 0xac, 0x48, 0xb8, 0xea, 0xdf, 0x71, 0xc8, 0x8a, 0xe6, 0x73, - 0xfa, 0xef, 0xf4, 0x15, 0x20, 0x15, 0x8d, 0x1a, 0x88, 0x8b, 0x83, 0x97, 0x08, 0x1d, 0xbc, 0x2d, - 0x48, 0xda, 0x73, 0x7d, 0x68, 0x6a, 0x3e, 0x1d, 0x7d, 0x09, 0x95, 0x20, 0xad, 0x50, 0x59, 0xd5, - 0x89, 0x66, 0x33, 0xf2, 0x6d, 0xe0, 0x85, 0xec, 0x46, 0x08, 0xb8, 0x90, 0x64, 0x0b, 0xb9, 0x98, - 0xf7, 0x03, 0xd8, 0xd0, 0xd4, 0xaf, 0xa6, 0xaa, 0xa2, 0x3a, 0x73, 0x49, 0x26, 0x13, 0xc6, 0x95, - 0x0c, 0xce, 0x2d, 0xc0, 0x26, 0x99, 0xa0, 0xa7, 0x00, 0x8e, 0x5b, 0x85, 0xb7, 0x62, 0x69, 0xb6, - 0x62, 0xf7, 0xa2, 0x13, 0x63, 0x55, 0xb2, 0x2d, 0xcb, 0x38, 0xc1, 0x13, 0x5d, 0xc2, 0xff, 0xee, - 0xec, 0x8b, 0x90, 0x61, 0x44, 0xaa, 0x46, 0x5d, 0xdc, 0x35, 0x30, 0x5c, 0x34, 0xee, 0x40, 0xdf, - 0x4a, 0x02, 0x2f, 0x4b, 0x8f, 0x04, 0x7b, 0x3f, 0x71, 0x90, 0xf4, 0x36, 0x1f, 0xe5, 0x01, 0x9e, - 0x75, 0xcf, 0xba, 0xbd, 0xcb, 0xae, 0x34, 0xe8, 0xf0, 0x31, 0x94, 0x82, 0x44, 0x6b, 0xd0, 0xe1, - 0x39, 0xf7, 0xd1, 0x1f, 0x74, 0xf8, 0x38, 0xca, 0x42, 0xaa, 0xd3, 0x1b, 0xb4, 0x5c, 0x75, 0xc2, - 0x15, 0x2e, 0x1b, 0xfd, 0x8e, 0x2b, 0xac, 0xa1, 0x1c, 0xa4, 0x9b, 0x8d, 0x36, 0xee, 0xb9, 0xd2, - 0xba, 0xab, 0x12, 0x71, 0x8f, 0xb9, 0x49, 0xba, 0x6e, 0xfb, 0x62, 0xeb, 0xfc, 0xbc, 0x81, 0x5d, - 0x39, 0x85, 0x10, 0xe4, 0x8f, 0xdb, 0x62, 0xb3, 0xd7, 0xee, 0x4a, 0xfd, 0x26, 0x6e, 0x5f, 0x88, - 0x7c, 0xda, 0xfd, 0xbc, 0x27, 0x7e, 0xd6, 0x62, 0x16, 0x99, 0xbd, 0x33, 0xc8, 0x2c, 0x3a, 0x85, - 0x0a, 0xb0, 0x11, 0x64, 0x25, 0xf6, 0xce, 0x5a, 0x5d, 0x3e, 0x86, 0x32, 0xb0, 0xde, 0xc2, 0xcd, - 0xc3, 0xf7, 0x79, 0x0e, 0x01, 0x24, 0x5b, 0xb8, 0xf9, 0xd1, 0xe1, 0x81, 0x97, 0x5d, 0x0b, 0x37, - 0x0f, 0x0e, 0x9e, 0x3c, 0xe1, 0x13, 0x2c, 0xe7, 0x8b, 0x73, 0x7e, 0x6d, 0x6f, 0x0c, 0xfc, 0xed, - 0x3f, 0x44, 0x48, 0x80, 0x62, 0xb3, 0xd7, 0x3d, 0x6d, 0xe3, 0x4e, 0x43, 0x6c, 0xf7, 0xba, 0x92, - 0x1f, 0x80, 0x8f, 0xa1, 0x32, 0x94, 0x22, 0x1a, 0xf1, 0xf3, 0x8b, 0x96, 0xd4, 0x17, 0x1b, 0xdd, - 0x93, 0x06, 0x3e, 0xe1, 0x39, 0x54, 0x82, 0xad, 0x55, 0xfd, 0x69, 0xa3, 0x2f, 0xf2, 0xf1, 0xe3, - 0x8b, 0x57, 0xd7, 0x65, 0xee, 0xf5, 0x75, 0x99, 0x7b, 0x73, 0x5d, 0xe6, 0x7e, 0xbc, 0x29, 0xc7, - 0x5e, 0xdf, 0x94, 0x63, 0xbf, 0xde, 0x94, 0x63, 0x5f, 0x3c, 0x1d, 0xa9, 0xce, 0x78, 0x3a, 0xac, - 0xc9, 0xa6, 0x5e, 0x9f, 0x4c, 0xed, 0x31, 0x63, 0x34, 0x7b, 0xed, 0xb3, 0xe7, 0xbe, 0x61, 0x2a, - 0xb4, 0xfe, 0x4d, 0x3d, 0x34, 0x23, 0xf7, 0xdf, 0xab, 0x61, 0x92, 0x1d, 0x8f, 0x0f, 0xfe, 0x09, - 0x00, 0x00, 0xff, 0xff, 0x7e, 0xcd, 0xc1, 0xec, 0x7b, 0x09, 0x00, 0x00, + // 1167 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x5d, 0x6f, 0xdb, 0xe4, + 0x17, 0x8f, 0x93, 0xe6, 0xed, 0x24, 0x4d, 0x9d, 0x47, 0xf9, 0x77, 0x5e, 0xb4, 0x7f, 0x52, 0xb2, + 0x09, 0xba, 0x6a, 0x4b, 0x68, 0x61, 0x43, 0xaa, 0x84, 0x50, 0x9a, 0x66, 0x10, 0xb5, 0x49, 0x8a, + 0xe3, 0xa5, 0x82, 0x0b, 0xac, 0x27, 0xf6, 0x53, 0xc7, 0x9a, 0x5f, 0x82, 0xfd, 0x24, 0x90, 0x0f, + 0x80, 0x90, 0x10, 0x42, 0x70, 0xb7, 0xcb, 0x7d, 0x04, 0x3e, 0xc6, 0x2e, 0x77, 0xc9, 0x15, 0x4c, + 0x2d, 0x12, 0x7c, 0x06, 0xae, 0x90, 0x1f, 0xdb, 0x89, 0xd3, 0x74, 0x70, 0xcd, 0x4d, 0xfb, 0x9c, + 0x17, 0x9f, 0xf3, 0x3b, 0xe7, 0xfc, 0xce, 0x69, 0x41, 0x98, 0x3a, 0x44, 0xd3, 0x5d, 0xea, 0xcc, + 0x1b, 0xb3, 0xfd, 0x06, 0x9d, 0x4f, 0x88, 0x5b, 0x9f, 0x38, 0x36, 0xb5, 0x51, 0x7e, 0x61, 0xa9, + 0xcf, 0xf6, 0xcb, 0x25, 0xcd, 0xd6, 0x6c, 0x66, 0x68, 0x78, 0x2f, 0xdf, 0xa7, 0x5c, 0xc4, 0xa6, + 0x6e, 0xd9, 0x0d, 0xf6, 0x33, 0x50, 0x55, 0x34, 0xdb, 0xd6, 0x0c, 0xd2, 0x60, 0xd2, 0x68, 0x7a, + 0xd1, 0x50, 0xa7, 0x0e, 0xa6, 0xba, 0x6d, 0xf9, 0xf6, 0xda, 0x87, 0x90, 0x3a, 0xc3, 0x0e, 0x36, + 0x5d, 0x54, 0x82, 0x24, 0x56, 0x4d, 0xdd, 0x12, 0xb8, 0x1d, 0x6e, 0x37, 0x2b, 0xfa, 0xc2, 0xe1, + 0xff, 0x9f, 0xbf, 0xa8, 0xc6, 0xfe, 0x7c, 0x51, 0xe5, 0xbe, 0xfb, 0xe3, 0xe7, 0x3d, 0x7e, 0x89, + 0x6e, 0xc2, 0x3e, 0xaa, 0xfd, 0xce, 0x41, 0xe1, 0x63, 0x4c, 0xc9, 0x57, 0x78, 0xde, 0x25, 0x74, + 0x6c, 0xab, 0x2e, 0x42, 0xb0, 0x61, 0x61, 0x93, 0x04, 0x61, 0xd8, 0x1b, 0x55, 0x00, 0x74, 0x95, + 0x58, 0x54, 0xbf, 0xd0, 0x89, 0x23, 0xc4, 0x99, 0x25, 0xa2, 0x41, 0xf7, 0x81, 0x27, 0x33, 0x62, + 0x51, 0x39, 0xe2, 0x95, 0x60, 0x5e, 0x5b, 0x4c, 0xdf, 0x59, 0xba, 0x9e, 0x40, 0x51, 0xb1, 0xad, + 0x0b, 0xdd, 0x31, 0x59, 0x19, 0xb2, 0xd7, 0x23, 0x61, 0x63, 0x87, 0xdb, 0x2d, 0x1c, 0x54, 0xea, + 0xd1, 0x1e, 0xd5, 0x5b, 0x11, 0x37, 0x69, 0x3e, 0x21, 0x22, 0xaf, 0x5c, 0xd3, 0x1c, 0xbe, 0x1d, + 0xad, 0xee, 0xf6, 0xb2, 0x3a, 0xcd, 0x2f, 0x49, 0x36, 0xfd, 0x9a, 0x6a, 0xaf, 0x39, 0xc8, 0x0f, + 0xf1, 0xd4, 0xa0, 0xff, 0xc5, 0x22, 0xef, 0x45, 0x8b, 0xbc, 0x15, 0x21, 0x98, 0x57, 0xd0, 0xa2, + 0xc4, 0xef, 0x39, 0x28, 0x1e, 0x19, 0xb6, 0xf2, 0x2c, 0x1a, 0x11, 0xbd, 0x05, 0xf9, 0x0b, 0xec, + 0x52, 0x59, 0xb7, 0x46, 0xf6, 0xd4, 0x52, 0x59, 0xbd, 0x9b, 0x62, 0xce, 0xd3, 0x75, 0x7c, 0x95, + 0x57, 0x96, 0x4b, 0xb1, 0xa5, 0x62, 0x47, 0x5d, 0xb8, 0xc5, 0x99, 0xdb, 0x56, 0xa8, 0x0f, 0x5c, + 0x0f, 0xef, 0x47, 0x91, 0xdc, 0x59, 0x22, 0x19, 0x79, 0x79, 0xe5, 0x28, 0xf0, 0xda, 0x0f, 0x1c, + 0xe4, 0x5b, 0x63, 0xac, 0x5b, 0x6d, 0x0b, 0x8f, 0x0c, 0xa2, 0xa2, 0x3d, 0xe0, 0x75, 0x37, 0x08, + 0x14, 0xe8, 0x18, 0x9a, 0x8c, 0xb8, 0xa6, 0x47, 0x0f, 0xa0, 0xa8, 0xbb, 0xfd, 0x29, 0x5d, 0x71, + 0x8e, 0x33, 0xe7, 0x75, 0xc3, 0x1b, 0xfb, 0xa3, 0x78, 0xe9, 0x65, 0xe2, 0x7b, 0xd5, 0xbe, 0x4d, + 0x42, 0x8e, 0x01, 0x62, 0xfd, 0xd1, 0xbc, 0x75, 0x61, 0x0e, 0xe1, 0xba, 0x30, 0x01, 0x3d, 0x84, + 0xf4, 0xcc, 0xf4, 0xc7, 0x15, 0x67, 0xe3, 0x2a, 0xad, 0x8e, 0x6b, 0x68, 0xb2, 0x21, 0xa5, 0x66, + 0xec, 0x37, 0xba, 0x07, 0x85, 0xc9, 0x74, 0x64, 0xe8, 0x8a, 0xec, 0x4c, 0x14, 0x79, 0xea, 0x18, + 0x01, 0x21, 0xf2, 0xbe, 0x56, 0x9c, 0x28, 0x4f, 0x1d, 0x03, 0xbd, 0x03, 0x5b, 0x21, 0x21, 0xb1, + 0xaa, 0x3a, 0xc4, 0x75, 0x19, 0x17, 0xb2, 0x62, 0x21, 0x50, 0x37, 0x7d, 0x2d, 0xea, 0x01, 0x5a, + 0x6f, 0xa5, 0x90, 0xdc, 0xe1, 0x76, 0x73, 0x07, 0xd5, 0x55, 0x20, 0x6b, 0xa3, 0x16, 0x8b, 0xa3, + 0xb5, 0xe9, 0xb7, 0x97, 0x89, 0x03, 0x9a, 0x08, 0xa9, 0x9d, 0xc4, 0x6e, 0xee, 0xe0, 0xce, 0x6a, + 0xb0, 0xd5, 0x0b, 0xb0, 0x80, 0x15, 0x2e, 0xcb, 0xfb, 0x90, 0x0e, 0xba, 0x28, 0xa4, 0x19, 0x96, + 0xf2, 0x35, 0x0e, 0x47, 0xe6, 0x2c, 0x86, 0xae, 0xe8, 0x0b, 0xb8, 0xad, 0x61, 0x57, 0xb6, 0x1d, + 0xac, 0x18, 0x44, 0xbe, 0x20, 0x54, 0x19, 0xcb, 0xba, 0x45, 0x89, 0x33, 0xc3, 0x86, 0x90, 0x61, + 0x71, 0x6e, 0xd7, 0xfd, 0xeb, 0x56, 0x0f, 0xaf, 0x5b, 0xfd, 0x38, 0xb8, 0x6e, 0x47, 0x99, 0x97, + 0xbf, 0x56, 0x63, 0xcf, 0x7f, 0xab, 0x72, 0xe2, 0xb6, 0x86, 0xdd, 0x3e, 0x0b, 0xf2, 0xc4, 0x8b, + 0xd1, 0x09, 0x42, 0xa0, 0x8f, 0x60, 0x73, 0x65, 0x03, 0x84, 0x2c, 0x2b, 0xed, 0x1a, 0xb6, 0xe8, + 0xd6, 0x8b, 0xf9, 0x59, 0xf4, 0x06, 0x7c, 0x0a, 0x25, 0xea, 0xba, 0xb2, 0xab, 0x6b, 0x96, 0x6e, + 0x69, 0xb2, 0x4a, 0xb0, 0x6a, 0xe8, 0x16, 0x11, 0xe0, 0xdf, 0xb0, 0x6d, 0x30, 0x5c, 0x88, 0xba, + 0xee, 0xc0, 0xff, 0xf6, 0x38, 0xf8, 0xf4, 0xf0, 0x6e, 0x94, 0x8a, 0xdb, 0xd7, 0xa9, 0xc8, 0xa6, + 0xaa, 0xd5, 0xbe, 0xe1, 0xa0, 0xd4, 0xc3, 0x54, 0x9f, 0x11, 0x91, 0x4c, 0x1c, 0xe2, 0x12, 0x8b, + 0xfa, 0xe3, 0x2a, 0x41, 0x52, 0x25, 0x96, 0x6d, 0x86, 0x94, 0x64, 0x82, 0xb7, 0x9f, 0x8a, 0x6d, + 0x51, 0x07, 0x2b, 0x74, 0x41, 0x1f, 0xff, 0x38, 0x6d, 0x85, 0xfa, 0x80, 0x3f, 0x87, 0x0f, 0xa2, + 0xe9, 0xab, 0xcb, 0xf4, 0x16, 0xcb, 0x26, 0x3b, 0x2b, 0xe9, 0x6a, 0x7f, 0xc5, 0x21, 0x27, 0xd9, + 0xcf, 0xc8, 0x3f, 0x6f, 0x84, 0x00, 0xe9, 0xd5, 0xac, 0xa1, 0xb8, 0xb8, 0xa1, 0x89, 0xc8, 0x0d, + 0xdd, 0x86, 0x94, 0x3b, 0x37, 0x47, 0xb6, 0x11, 0x30, 0x3c, 0x90, 0x50, 0x19, 0x32, 0x2a, 0x51, + 0x74, 0x13, 0x1b, 0x2e, 0xe3, 0xf3, 0xa6, 0xb8, 0x90, 0xbd, 0x0c, 0x21, 0xbd, 0x52, 0x6c, 0xc7, + 0x17, 0x14, 0xba, 0x0b, 0x9b, 0x86, 0xfe, 0xe5, 0x54, 0x57, 0x75, 0x3a, 0x97, 0x15, 0x3c, 0x61, + 0xf4, 0xcb, 0x8a, 0xf9, 0x85, 0xb2, 0x85, 0x27, 0xe8, 0x31, 0x00, 0xf5, 0xaa, 0xf0, 0xb7, 0x36, + 0xc3, 0xb6, 0xf6, 0xd6, 0x2a, 0x09, 0x58, 0x95, 0x6c, 0x71, 0xb3, 0x34, 0x7c, 0xa2, 0x73, 0xf8, + 0xdf, 0x8d, 0x7d, 0x11, 0xb2, 0x6c, 0xfe, 0xb5, 0xd5, 0x10, 0x37, 0x0d, 0x4c, 0x2c, 0x59, 0x37, + 0x68, 0xdf, 0x48, 0x02, 0x1f, 0xa5, 0x4f, 0x82, 0xbd, 0x9f, 0x38, 0x48, 0xf9, 0xc7, 0x04, 0x15, + 0x00, 0x9e, 0xf6, 0x4e, 0x7a, 0xfd, 0xf3, 0x9e, 0x3c, 0xec, 0xf2, 0x31, 0x94, 0x86, 0x44, 0x7b, + 0xd8, 0xe5, 0x39, 0xef, 0x31, 0x18, 0x76, 0xf9, 0x38, 0xca, 0x41, 0xba, 0xdb, 0x1f, 0xb6, 0x3d, + 0x73, 0xc2, 0x13, 0xce, 0x9b, 0x83, 0xae, 0x27, 0x6c, 0xa0, 0x3c, 0x64, 0x5a, 0xcd, 0x8e, 0xd8, + 0xf7, 0xa4, 0xa4, 0x67, 0x92, 0xc4, 0x3e, 0x0b, 0x93, 0xf2, 0xc2, 0x0e, 0xa4, 0xf6, 0xe9, 0x69, + 0x53, 0xf4, 0xe4, 0x34, 0x42, 0x50, 0x38, 0xea, 0x48, 0xad, 0x7e, 0xa7, 0x27, 0x0f, 0x5a, 0x62, + 0xe7, 0x4c, 0xe2, 0x33, 0xde, 0xe7, 0x7d, 0xe9, 0x93, 0x36, 0xf3, 0xc8, 0xee, 0x9d, 0x40, 0x76, + 0xd1, 0x29, 0x54, 0x84, 0xcd, 0x10, 0x95, 0xd4, 0x3f, 0x69, 0xf7, 0xf8, 0x18, 0xca, 0x42, 0xb2, + 0x2d, 0xb6, 0x0e, 0xde, 0xe5, 0x39, 0x04, 0x90, 0x6a, 0x8b, 0xad, 0x0f, 0x0e, 0xf6, 0x7d, 0x74, + 0x6d, 0xb1, 0xb5, 0xbf, 0xff, 0xe8, 0x11, 0x9f, 0x60, 0x98, 0xcf, 0x4e, 0xf9, 0x8d, 0xbd, 0x31, + 0xf0, 0xd7, 0xff, 0xb6, 0x21, 0x01, 0x4a, 0xad, 0x7e, 0xef, 0x49, 0x47, 0xec, 0x36, 0xa5, 0x4e, + 0xbf, 0x27, 0x07, 0x09, 0xf8, 0x18, 0xaa, 0x40, 0x79, 0xc5, 0x22, 0x7d, 0x76, 0xd6, 0x96, 0x07, + 0x52, 0xb3, 0x77, 0xdc, 0x14, 0x8f, 0x79, 0x0e, 0x95, 0x61, 0x7b, 0xdd, 0xfe, 0xa4, 0x39, 0x90, + 0xf8, 0xf8, 0xd1, 0xd9, 0xcb, 0xcb, 0x0a, 0xf7, 0xea, 0xb2, 0xc2, 0xbd, 0xbe, 0xac, 0x70, 0x3f, + 0x5e, 0x55, 0x62, 0xaf, 0xae, 0x2a, 0xb1, 0x5f, 0xae, 0x2a, 0xb1, 0xcf, 0x1f, 0x6b, 0x3a, 0x1d, + 0x4f, 0x47, 0x75, 0xc5, 0x36, 0x1b, 0x93, 0xa9, 0x3b, 0x66, 0x8c, 0x66, 0xaf, 0x87, 0xec, 0xf9, + 0xd0, 0xb2, 0x55, 0xd2, 0xf8, 0xba, 0x11, 0x99, 0x91, 0xf7, 0x1f, 0xdb, 0x28, 0xc5, 0x76, 0xfe, + 0xbd, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x52, 0x86, 0x14, 0xc9, 0xce, 0x09, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -985,6 +995,15 @@ func (this *ChainConfig) Equal(that interface{}) bool { return false } } + if this.TssSigningDeadline != nil && that1.TssSigningDeadline != nil { + if *this.TssSigningDeadline != *that1.TssSigningDeadline { + return false + } + } else if this.TssSigningDeadline != nil { + return false + } else if that1.TssSigningDeadline != nil { + return false + } return true } func (this *NativeRepresentation) Equal(that interface{}) bool { @@ -1286,6 +1305,16 @@ func (m *ChainConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TssSigningDeadline != nil { + n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(*m.TssSigningDeadline, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(*m.TssSigningDeadline):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintTypes(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x52 + } if len(m.VaultMethods) > 0 { for iNdEx := len(m.VaultMethods) - 1; iNdEx >= 0; iNdEx-- { { @@ -1300,12 +1329,12 @@ func (m *ChainConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x4a } } - n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.GasOracleFetchInterval, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.GasOracleFetchInterval):]) - if err1 != nil { - return 0, err1 + n2, err2 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.GasOracleFetchInterval, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.GasOracleFetchInterval):]) + if err2 != nil { + return 0, err2 } - i -= n1 - i = encodeVarintTypes(dAtA, i, uint64(n1)) + i -= n2 + i = encodeVarintTypes(dAtA, i, uint64(n2)) i-- dAtA[i] = 0x42 if m.Enabled != nil { @@ -1647,6 +1676,10 @@ func (m *ChainConfig) Size() (n int) { n += 1 + l + sovTypes(uint64(l)) } } + if m.TssSigningDeadline != nil { + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(*m.TssSigningDeadline) + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -2622,6 +2655,42 @@ func (m *ChainConfig) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TssSigningDeadline", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TssSigningDeadline == nil { + m.TssSigningDeadline = new(time.Duration) + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(m.TssSigningDeadline, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:])