diff --git a/api/grpc/disperser/disperser.pb.go b/api/grpc/disperser/disperser.pb.go index 0f766c33de..ba949323f3 100644 --- a/api/grpc/disperser/disperser.pb.go +++ b/api/grpc/disperser/disperser.pb.go @@ -447,22 +447,10 @@ type DispersePaidBlobRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The data to be dispersed. - // The size of data must be <= 2MiB. Every 32 bytes of data chunk is interpreted as an integer in big endian format - // where the lower address has more significant bits. The integer must stay in the valid range to be interpreted - // as a field element on the bn254 curve. The valid range is - // 0 <= x < 21888242871839275222246405745257275088548364400416034343698204186575808495617 - // containing slightly less than 254 bits and more than 253 bits. If any one of the 32 bytes chunk is outside the range, - // the whole request is deemed as invalid, and rejected. - // NOTE: I want to include dataLength here, not the data itself. + // The data to be dispersed. Same requirements as DisperseBlobRequest. Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` - // The quorums to which the blob will be sent, in addition to the required quorums which are configured - // on the EigenDA smart contract. If required quorums are included here, an error will be returned. - // The disperser will ensure that the encoded blobs for each quorum are all processed - // within the same batch. The request doesn't need to include the payment split because the information is registered on-chain. - // In theory the quorum numbers should be the same as the ones in the DisperseBlobRequest, but I'm allowing freedom - // for individual requests. - CustomQuorumNumbers []uint32 `protobuf:"varint,2,rep,packed,name=custom_quorum_numbers,json=customQuorumNumbers,proto3" json:"custom_quorum_numbers,omitempty"` + // The quorums to which the blob to be sent + QuorumNumbers []uint32 `protobuf:"varint,2,rep,packed,name=quorum_numbers,json=quorumNumbers,proto3" json:"quorum_numbers,omitempty"` // Payment header contains AccountID, BinIndex, and CumulativePayment PaymentHeader *common.PaymentHeader `protobuf:"bytes,3,opt,name=payment_header,json=paymentHeader,proto3" json:"payment_header,omitempty"` // signature of payment_header @@ -508,9 +496,9 @@ func (x *DispersePaidBlobRequest) GetData() []byte { return nil } -func (x *DispersePaidBlobRequest) GetCustomQuorumNumbers() []uint32 { +func (x *DispersePaidBlobRequest) GetQuorumNumbers() []uint32 { if x != nil { - return x.CustomQuorumNumbers + return x.QuorumNumbers } return nil } @@ -1314,162 +1302,161 @@ var file_disperser_disperser_proto_rawDesc = []byte{ 0x28, 0x0d, 0x52, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xcc, 0x01, 0x0a, 0x17, 0x44, 0x69, 0x73, 0x70, 0x65, + 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x50, 0x61, 0x69, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x5f, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x51, 0x75, 0x6f, - 0x72, 0x75, 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x61, 0x0a, 0x11, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, - 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x64, 0x69, 0x73, - 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x32, 0x0a, 0x11, 0x42, 0x6c, 0x6f, 0x62, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x69, 0x0a, 0x0f, - 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, - 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x15, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x27, - 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, - 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x60, 0x0a, 0x13, 0x52, 0x65, 0x74, 0x72, 0x69, - 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, - 0x0a, 0x11, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x62, 0x61, 0x74, 0x63, 0x68, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, - 0x6f, 0x62, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x27, 0x0a, 0x11, 0x52, 0x65, 0x74, - 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x22, 0x9c, 0x01, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x36, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0a, 0x62, 0x6c, 0x6f, - 0x62, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x58, 0x0a, 0x17, 0x62, 0x6c, 0x6f, 0x62, 0x5f, - 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, - 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x15, 0x62, 0x6c, 0x6f, 0x62, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x22, 0xad, 0x01, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x62, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x31, - 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x64, 0x61, 0x74, - 0x61, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x48, 0x0a, 0x12, 0x62, 0x6c, 0x6f, 0x62, 0x5f, - 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, - 0x42, 0x6c, 0x6f, 0x62, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x52, - 0x10, 0x62, 0x6c, 0x6f, 0x62, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x73, 0x22, 0xeb, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x62, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, - 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x71, 0x75, - 0x6f, 0x72, 0x75, 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x1e, 0x61, 0x64, - 0x76, 0x65, 0x72, 0x73, 0x61, 0x72, 0x79, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x1c, 0x61, 0x64, 0x76, 0x65, 0x72, 0x73, 0x61, 0x72, 0x79, 0x54, 0x68, 0x72, - 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, - 0x12, 0x4a, 0x0a, 0x21, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, - 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1f, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, - 0x6c, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, - 0xe2, 0x01, 0x0a, 0x15, 0x42, 0x6c, 0x6f, 0x62, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x74, - 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, 0x74, - 0x63, 0x68, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x12, 0x3f, 0x0a, 0x0e, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x69, - 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x62, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x25, 0x0a, - 0x0e, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x65, 0x73, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, 0x0c, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, - 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x52, 0x0b, 0x62, 0x61, 0x74, 0x63, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x72, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x13, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x48, 0x61, 0x73, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x17, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, - 0x62, 0x61, 0x74, 0x63, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 0x22, - 0xc5, 0x01, 0x0a, 0x0b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, - 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x25, - 0x0a, 0x0e, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x4e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x3a, 0x0a, 0x19, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x17, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, - 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, - 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x14, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2a, 0x80, 0x01, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x62, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x49, 0x4e, - 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, - 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, - 0x0a, 0x09, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x04, 0x12, 0x1b, 0x0a, - 0x17, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x49, - 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x53, 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, - 0x53, 0x50, 0x45, 0x52, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x32, 0xb1, 0x03, 0x0a, 0x09, 0x44, - 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x70, - 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, - 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, - 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x56, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x70, - 0x65, 0x72, 0x73, 0x65, 0x50, 0x61, 0x69, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x22, 0x2e, 0x64, - 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, - 0x65, 0x50, 0x61, 0x69, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, - 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, - 0x12, 0x5f, 0x0a, 0x19, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, - 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x2e, - 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, - 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, - 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x28, 0x01, 0x30, - 0x01, 0x12, 0x4b, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, + 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0d, + 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x3c, 0x0a, + 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0d, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x61, 0x0a, 0x11, 0x44, 0x69, 0x73, 0x70, + 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2d, 0x0a, + 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, + 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x32, 0x0a, 0x11, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, - 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4e, - 0x0a, 0x0c, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x1e, - 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, - 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, - 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, - 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x31, - 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4c, 0x61, 0x79, - 0x72, 0x2d, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, 0x61, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, + 0x69, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, + 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x60, 0x0a, 0x13, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x62, 0x61, + 0x74, 0x63, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, + 0x0a, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x27, 0x0a, 0x11, + 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x9c, 0x01, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x36, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0a, + 0x62, 0x6c, 0x6f, 0x62, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x58, 0x0a, 0x17, 0x62, 0x6c, + 0x6f, 0x62, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x15, 0x62, + 0x6c, 0x6f, 0x62, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x22, 0xad, 0x01, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x62, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x47, 0x31, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, + 0x64, 0x61, 0x74, 0x61, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x48, 0x0a, 0x12, 0x62, 0x6c, + 0x6f, 0x62, 0x5f, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x62, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x22, 0xeb, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x62, 0x51, 0x75, 0x6f, + 0x72, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x71, 0x75, 0x6f, 0x72, + 0x75, 0x6d, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0c, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x44, 0x0a, + 0x1e, 0x61, 0x64, 0x76, 0x65, 0x72, 0x73, 0x61, 0x72, 0x79, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, + 0x68, 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1c, 0x61, 0x64, 0x76, 0x65, 0x72, 0x73, 0x61, 0x72, 0x79, + 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, + 0x61, 0x67, 0x65, 0x12, 0x4a, 0x0a, 0x21, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1f, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x65, + 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x4c, 0x65, 0x6e, 0x67, + 0x74, 0x68, 0x22, 0xe2, 0x01, 0x0a, 0x15, 0x42, 0x6c, 0x6f, 0x62, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x19, 0x0a, 0x08, + 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x62, 0x61, 0x74, 0x63, 0x68, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x62, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x62, 0x6c, 0x6f, + 0x62, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3f, 0x0a, 0x0e, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x62, 0x61, 0x74, 0x63, 0x68, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x12, 0x25, 0x0a, 0x0e, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, 0x0c, 0x62, 0x61, 0x74, + 0x63, 0x68, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0b, 0x62, 0x61, 0x74, 0x63, 0x68, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x6f, 0x72, + 0x79, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x13, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x48, 0x61, 0x73, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x17, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x61, + 0x73, 0x68, 0x22, 0xc5, 0x01, 0x0a, 0x0b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x52, 0x6f, 0x6f, + 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x72, 0x75, + 0x6d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x3a, 0x0a, 0x19, 0x71, 0x75, 0x6f, 0x72, + 0x75, 0x6d, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, + 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x17, 0x71, 0x75, 0x6f, + 0x72, 0x75, 0x6d, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, + 0x61, 0x67, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2a, 0x80, 0x01, 0x0a, 0x0a, 0x42, + 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, + 0x53, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, + 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x04, + 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, + 0x5f, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x53, 0x10, 0x05, 0x12, 0x0e, 0x0a, + 0x0a, 0x44, 0x49, 0x53, 0x50, 0x45, 0x52, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x32, 0xb1, 0x03, + 0x0a, 0x09, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0c, 0x44, + 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, + 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, + 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x56, 0x0a, 0x10, 0x44, + 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x50, 0x61, 0x69, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x12, + 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, + 0x65, 0x72, 0x73, 0x65, 0x50, 0x61, 0x69, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, + 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x22, 0x00, 0x12, 0x5f, 0x0a, 0x19, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, + 0x6c, 0x6f, 0x62, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, + 0x12, 0x1f, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x28, 0x01, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, + 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, + 0x00, 0x12, 0x4e, 0x0a, 0x0c, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, + 0x62, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, + 0x00, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x4c, 0x61, 0x79, 0x72, 0x2d, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, + 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/disperser/disperser.proto b/api/proto/disperser/disperser.proto index 7df6c5eab8..61ddfe638b 100644 --- a/api/proto/disperser/disperser.proto +++ b/api/proto/disperser/disperser.proto @@ -101,22 +101,10 @@ message DisperseBlobRequest { } message DispersePaidBlobRequest { - // The data to be dispersed. - // The size of data must be <= 2MiB. Every 32 bytes of data chunk is interpreted as an integer in big endian format - // where the lower address has more significant bits. The integer must stay in the valid range to be interpreted - // as a field element on the bn254 curve. The valid range is - // 0 <= x < 21888242871839275222246405745257275088548364400416034343698204186575808495617 - // containing slightly less than 254 bits and more than 253 bits. If any one of the 32 bytes chunk is outside the range, - // the whole request is deemed as invalid, and rejected. - // NOTE: I want to include dataLength here, not the data itself. + // The data to be dispersed. Same requirements as DisperseBlobRequest. bytes data = 1; - // The quorums to which the blob will be sent, in addition to the required quorums which are configured - // on the EigenDA smart contract. If required quorums are included here, an error will be returned. - // The disperser will ensure that the encoded blobs for each quorum are all processed - // within the same batch. The request doesn't need to include the payment split because the information is registered on-chain. - // In theory the quorum numbers should be the same as the ones in the DisperseBlobRequest, but I'm allowing freedom - // for individual requests. - repeated uint32 custom_quorum_numbers = 2; + // The quorums to which the blob to be sent + repeated uint32 quorum_numbers = 2; // Payment header contains AccountID, BinIndex, and CumulativePayment common.PaymentHeader payment_header = 3; diff --git a/core/auth.go b/core/auth.go index f4aadb96fe..349f5b1aff 100644 --- a/core/auth.go +++ b/core/auth.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" geth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -47,5 +48,9 @@ func VerifySignature(message []byte, accountAddr geth.Address, sig []byte) error } return nil +} +type PaymentSigner interface { + SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) + GetAccountID() string } diff --git a/core/auth/payment_signer.go b/core/auth/payment_signer.go new file mode 100644 index 0000000000..0d873e75fe --- /dev/null +++ b/core/auth/payment_signer.go @@ -0,0 +1,98 @@ +package auth + +import ( + "crypto/ecdsa" + "fmt" + "log" + + commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/Layr-Labs/eigenda/core" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type PaymentSigner struct { + PrivateKey *ecdsa.PrivateKey +} + +var _ core.PaymentSigner = &PaymentSigner{} + +func NewPaymentSigner(privateKeyHex string) *PaymentSigner { + + privateKeyBytes := common.FromHex(privateKeyHex) + privateKey, err := crypto.ToECDSA(privateKeyBytes) + if err != nil { + log.Fatalf("Failed to parse private key: %v", err) + } + + return &PaymentSigner{ + PrivateKey: privateKey, + } +} + +// SignBlobPayment signs the payment header and returns the signature +func (s *PaymentSigner) SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) { + header.AccountId = s.GetAccountID() + pm := core.ConvertPaymentHeader(header) + hash, err := pm.Hash() + if err != nil { + return nil, fmt.Errorf("failed to hash payment header: %v", err) + } + + sig, err := crypto.Sign(hash[:], s.PrivateKey) + if err != nil { + return nil, fmt.Errorf("failed to sign hash: %v", err) + } + + return sig, nil +} + +type NoopPaymentSigner struct{} + +func NewNoopPaymentSigner() *NoopPaymentSigner { + return &NoopPaymentSigner{} +} + +func (s *NoopPaymentSigner) SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) { + return nil, fmt.Errorf("noop signer cannot sign blob payment header") +} + +func (s *NoopPaymentSigner) GetAccountID() string { + return "" +} + +// VerifyPaymentSignature verifies the signature against the payment metadata +func VerifyPaymentSignature(paymentHeader *commonpb.PaymentHeader, paymentSignature []byte) bool { + pm := core.ConvertPaymentHeader(paymentHeader) + hash, err := pm.Hash() + if err != nil { + return false + } + + recoveredPubKey, err := crypto.SigToPub(hash[:], paymentSignature) + if err != nil { + log.Printf("Failed to recover public key from signature: %v\n", err) + return false + } + + recoveredAddress := crypto.PubkeyToAddress(*recoveredPubKey) + accountId := common.HexToAddress(paymentHeader.AccountId) + if recoveredAddress != accountId { + log.Printf("Signature address %s does not match account id %s\n", recoveredAddress.Hex(), accountId.Hex()) + return false + } + + return crypto.VerifySignature( + crypto.FromECDSAPub(recoveredPubKey), + hash[:], + paymentSignature[:len(paymentSignature)-1], // Remove recovery ID + ) +} + +// GetAccountID returns the Ethereum address of the signer +func (s *PaymentSigner) GetAccountID() string { + publicKey := crypto.FromECDSAPub(&s.PrivateKey.PublicKey) + hash := crypto.Keccak256(publicKey[1:]) + + return common.BytesToAddress(hash[12:]).Hex() +} diff --git a/core/auth/payment_signer_test.go b/core/auth/payment_signer_test.go new file mode 100644 index 0000000000..4ba7c872a4 --- /dev/null +++ b/core/auth/payment_signer_test.go @@ -0,0 +1,76 @@ +package auth_test + +import ( + "encoding/hex" + "testing" + + commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/Layr-Labs/eigenda/core/auth" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPaymentSigner(t *testing.T) { + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + + privateKeyHex := hex.EncodeToString(crypto.FromECDSA(privateKey)) + signer := auth.NewPaymentSigner(privateKeyHex) + + t.Run("SignBlobPayment", func(t *testing.T) { + header := &commonpb.PaymentHeader{ + BinIndex: 1, + CumulativePayment: []byte{0x01, 0x02, 0x03}, + AccountId: "", + } + + signature, err := signer.SignBlobPayment(header) + require.NoError(t, err) + assert.NotEmpty(t, signature) + + // Verify the signature + isValid := auth.VerifyPaymentSignature(header, signature) + assert.True(t, isValid) + }) + + t.Run("VerifyPaymentSignature_InvalidSignature", func(t *testing.T) { + header := &commonpb.PaymentHeader{ + BinIndex: 1, + CumulativePayment: []byte{0x01, 0x02, 0x03}, + AccountId: "", + } + + // Create an invalid signature + invalidSignature := make([]byte, 65) + isValid := auth.VerifyPaymentSignature(header, invalidSignature) + assert.False(t, isValid) + }) + + t.Run("VerifyPaymentSignature_ModifiedHeader", func(t *testing.T) { + header := &commonpb.PaymentHeader{ + BinIndex: 1, + CumulativePayment: []byte{0x01, 0x02, 0x03}, + AccountId: "", + } + + signature, err := signer.SignBlobPayment(header) + require.NoError(t, err) + + // Modify the header after signing + header.BinIndex = 2 + + isValid := auth.VerifyPaymentSignature(header, signature) + assert.False(t, isValid) + }) +} + +func TestNoopPaymentSigner(t *testing.T) { + signer := auth.NewNoopPaymentSigner() + + t.Run("SignBlobRequest", func(t *testing.T) { + _, err := signer.SignBlobPayment(nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "noop signer cannot sign blob payment header") + }) +} diff --git a/core/data.go b/core/data.go index 7a064f5818..0742f5aa0b 100644 --- a/core/data.go +++ b/core/data.go @@ -7,6 +7,7 @@ import ( "math/big" "strconv" + commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" "github.com/Layr-Labs/eigenda/common" "github.com/Layr-Labs/eigenda/encoding" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" @@ -562,6 +563,15 @@ func (pm *PaymentMetadata) UnmarshalDynamoDBAttributeValue(av types.AttributeVal return nil } +// ConvertPaymentHeader converts a protobuf payment header to a PaymentMetadata +func ConvertPaymentHeader(header *commonpb.PaymentHeader) *PaymentMetadata { + return &PaymentMetadata{ + AccountID: header.AccountId, + BinIndex: header.BinIndex, + CumulativePayment: new(big.Int).SetBytes(header.CumulativePayment), + } +} + // OperatorInfo contains information about an operator which is stored on the blockchain state, // corresponding to a particular quorum type ActiveReservation struct { diff --git a/core/eth/reader.go b/core/eth/reader.go index 41e76bcba2..b5bafca3c6 100644 --- a/core/eth/reader.go +++ b/core/eth/reader.go @@ -599,3 +599,23 @@ func (t *Reader) GetOnDemandPaymentByAccount(ctx context.Context, blockNumber ui // contract is not implemented yet return core.OnDemandPayment{}, nil } + +func (t *Reader) GetGlobalSymbolsPerSecond(ctx context.Context) (uint64, error) { + // contract is not implemented yet + return 0, nil +} + +func (t *Reader) GetMinNumSymbols(ctx context.Context) (uint32, error) { + // contract is not implemented yet + return 0, nil +} + +func (t *Reader) GetPricePerSymbol(ctx context.Context) (uint32, error) { + // contract is not implemented yet + return 0, nil +} + +func (t *Reader) GetReservationWindow(ctx context.Context) (uint32, error) { + // contract is not implemented yet + return 0, nil +} diff --git a/core/meterer/meterer.go b/core/meterer/meterer.go index d0f508c117..53ac0848af 100644 --- a/core/meterer/meterer.go +++ b/core/meterer/meterer.go @@ -12,17 +12,12 @@ import ( // Config contains network parameters that should be published on-chain. We currently configure these params through disperser env vars. type Config struct { - // GlobalSymbolsPerSecond rate limit in symbols per second for on-demand payments - GlobalSymbolsPerSecond uint64 - // MinNumSymbols is the minimum number of symbols charged, round up for all smaller requests (must be in power of 2) - MinNumSymbols uint32 - // PricePerSymbol is the price per symbol in gwei, used for on-demand payments - PricePerSymbol uint32 - // ReservationWindow is the duration of all reservations in seconds, used to calculate bin indices - ReservationWindow uint32 // ChainReadTimeout is the timeout for reading payment state from chain ChainReadTimeout time.Duration + + // UpdateInterval is the interval for refreshing the on-chain state + UpdateInterval time.Duration } // Meterer handles payment accounting across different accounts. Disperser API server receives requests from clients and each request contains a blob header @@ -30,8 +25,8 @@ type Config struct { // payments information is valid. type Meterer struct { Config - // ChainState reads on-chain payment state periodically and cache it in memory - ChainState OnchainPayment + // ChainPaymentState reads on-chain payment state periodically and cache it in memory + ChainPaymentState OnchainPayment // OffchainStore uses DynamoDB to track metering and used to validate requests OffchainStore OffchainStore @@ -47,8 +42,8 @@ func NewMeterer( return &Meterer{ Config: config, - ChainState: paymentChainState, - OffchainStore: offchainStore, + ChainPaymentState: paymentChainState, + OffchainStore: offchainStore, logger: logger.With("component", "Meterer"), } @@ -57,13 +52,13 @@ func NewMeterer( // Start starts to periodically refreshing the on-chain state func (m *Meterer) Start(ctx context.Context) { go func() { - ticker := time.NewTicker(1 * time.Hour) + ticker := time.NewTicker(m.UpdateInterval) defer ticker.Stop() for { select { case <-ticker.C: - if err := m.ChainState.RefreshOnchainPaymentState(ctx, nil); err != nil { + if err := m.ChainPaymentState.RefreshOnchainPaymentState(ctx, nil); err != nil { m.logger.Error("Failed to refresh on-chain state", "error", err) } case <-ctx.Done(): @@ -79,7 +74,7 @@ func (m *Meterer) MeterRequest(ctx context.Context, blob core.Blob, header core. headerQuorums := blob.GetQuorumNumbers() // Validate against the payment method if header.CumulativePayment.Sign() == 0 { - reservation, err := m.ChainState.GetActiveReservationByAccount(ctx, header.AccountID) + reservation, err := m.ChainPaymentState.GetActiveReservationByAccount(ctx, header.AccountID) if err != nil { return fmt.Errorf("failed to get active reservation by account: %w", err) } @@ -87,7 +82,7 @@ func (m *Meterer) MeterRequest(ctx context.Context, blob core.Blob, header core. return fmt.Errorf("invalid reservation: %w", err) } } else { - onDemandPayment, err := m.ChainState.GetOnDemandPaymentByAccount(ctx, header.AccountID) + onDemandPayment, err := m.ChainPaymentState.GetOnDemandPaymentByAccount(ctx, header.AccountID) if err != nil { return fmt.Errorf("failed to get on-demand payment by account: %w", err) } @@ -138,9 +133,10 @@ func (m *Meterer) ValidateQuorum(headerQuorums []uint8, allowedQuorums []uint8) // ValidateBinIndex checks if the provided bin index is valid func (m *Meterer) ValidateBinIndex(header core.PaymentMetadata, reservation *core.ActiveReservation) bool { now := uint64(time.Now().Unix()) - currentBinIndex := GetBinIndex(now, m.ReservationWindow) + reservationWindow := m.ChainPaymentState.GetReservationWindow() + currentBinIndex := GetBinIndex(now, reservationWindow) // Valid bin indexes are either the current bin or the previous bin - if (header.BinIndex != currentBinIndex && header.BinIndex != (currentBinIndex-1)) || (GetBinIndex(reservation.StartTimestamp, m.ReservationWindow) > header.BinIndex || header.BinIndex > GetBinIndex(reservation.EndTimestamp, m.ReservationWindow)) { + if (header.BinIndex != currentBinIndex && header.BinIndex != (currentBinIndex-1)) || (GetBinIndex(reservation.StartTimestamp, reservationWindow) > header.BinIndex || header.BinIndex > GetBinIndex(reservation.EndTimestamp, reservationWindow)) { return false } return true @@ -162,7 +158,7 @@ func (m *Meterer) IncrementBinUsage(ctx context.Context, header core.PaymentMeta // metered usage before updating the size already exceeded the limit return fmt.Errorf("bin has already been filled") } - if newUsage <= 2*usageLimit && header.BinIndex+2 <= GetBinIndex(reservation.EndTimestamp, m.ReservationWindow) { + if newUsage <= 2*usageLimit && header.BinIndex+2 <= GetBinIndex(reservation.EndTimestamp, m.ChainPaymentState.GetReservationWindow()) { _, err := m.OffchainStore.UpdateReservationBin(ctx, header.AccountID, uint64(header.BinIndex+2), newUsage-usageLimit) if err != nil { return err @@ -182,7 +178,7 @@ func GetBinIndex(timestamp uint64, binInterval uint32) uint32 { // On-demand requests doesn't have additional quorum settings and should only be // allowed by ETH and EIGEN quorums func (m *Meterer) ServeOnDemandRequest(ctx context.Context, header core.PaymentMetadata, onDemandPayment *core.OnDemandPayment, blobLength uint, headerQuorums []uint8) error { - quorumNumbers, err := m.ChainState.GetOnDemandQuorumNumbers(ctx) + quorumNumbers, err := m.ChainPaymentState.GetOnDemandQuorumNumbers(ctx) if err != nil { return fmt.Errorf("failed to get on-demand quorum numbers: %w", err) } @@ -246,17 +242,18 @@ func (m *Meterer) ValidatePayment(ctx context.Context, header core.PaymentMetada // PaymentCharged returns the chargeable price for a given data length func (m *Meterer) PaymentCharged(dataLength uint) uint64 { - return uint64(m.SymbolsCharged(dataLength)) * uint64(m.PricePerSymbol) + return uint64(m.SymbolsCharged(dataLength)) * uint64(m.ChainPaymentState.GetPricePerSymbol()) } // SymbolsCharged returns the number of symbols charged for a given data length // being at least MinNumSymbols or the nearest rounded-up multiple of MinNumSymbols. func (m *Meterer) SymbolsCharged(dataLength uint) uint32 { - if dataLength <= uint(m.MinNumSymbols) { - return m.MinNumSymbols + if dataLength <= uint(m.ChainPaymentState.GetMinNumSymbols()) { + return m.ChainPaymentState.GetMinNumSymbols() } // Round up to the nearest multiple of MinNumSymbols - return uint32(core.RoundUpDivide(uint(dataLength), uint(m.MinNumSymbols))) * m.MinNumSymbols + fmt.Println("return ", uint32(core.RoundUpDivide(uint(dataLength), uint(m.ChainPaymentState.GetMinNumSymbols())))*m.ChainPaymentState.GetMinNumSymbols()) + return uint32(core.RoundUpDivide(uint(dataLength), uint(m.ChainPaymentState.GetMinNumSymbols()))) * m.ChainPaymentState.GetMinNumSymbols() } // ValidateBinIndex checks if the provided bin index is valid @@ -278,7 +275,7 @@ func (m *Meterer) IncrementGlobalBinUsage(ctx context.Context, symbolsCharged ui if err != nil { return fmt.Errorf("failed to increment global bin usage: %w", err) } - if newUsage > m.GlobalSymbolsPerSecond { + if newUsage > m.ChainPaymentState.GetGlobalSymbolsPerSecond() { return fmt.Errorf("global bin usage overflows") } return nil @@ -286,5 +283,5 @@ func (m *Meterer) IncrementGlobalBinUsage(ctx context.Context, symbolsCharged ui // GetReservationBinLimit returns the bin limit for a given reservation func (m *Meterer) GetReservationBinLimit(reservation *core.ActiveReservation) uint64 { - return reservation.SymbolsPerSec * uint64(m.ReservationWindow) + return reservation.SymbolsPerSec * uint64(m.ChainPaymentState.GetReservationWindow()) } diff --git a/core/meterer/meterer_test.go b/core/meterer/meterer_test.go index 674ebd2327..8fb2d1b9df 100644 --- a/core/meterer/meterer_test.go +++ b/core/meterer/meterer_test.go @@ -2,7 +2,6 @@ package meterer_test import ( "context" - "errors" "fmt" "math/big" "os" @@ -43,9 +42,9 @@ var ( deployLocalStack bool localStackPort = "4566" paymentChainState = &mock.MockOnchainPaymentState{} - ondemandTableName = "ondemand-meterer-test" - reservationTableName = "reservations-meterer-test" - globalReservationTableName = "global-reservation-meterer-test" + ondemandTableName = "ondemand_meterer" + reservationTableName = "reservations_meterer" + globalReservationTableName = "global_reservation_meterer" ) func TestMain(m *testing.M) { @@ -104,11 +103,8 @@ func setup(_ *testing.M) { logger = logging.NewNoopLogger() config := meterer.Config{ - PricePerSymbol: 2, - MinNumSymbols: 3, - GlobalSymbolsPerSecond: 1009, - ReservationWindow: 1, - ChainReadTimeout: 3 * time.Second, + ChainReadTimeout: 3 * time.Second, + UpdateInterval: 1 * time.Second, } err = meterer.CreateReservationTable(clientConfig, reservationTableName) @@ -148,14 +144,20 @@ func setup(_ *testing.M) { panic("failed to create offchain store") } + paymentChainState.On("RefreshOnchainPaymentState", testifymock.Anything).Return(nil).Maybe() + if err := paymentChainState.RefreshOnchainPaymentState(context.Background(), nil); err != nil { + panic("failed to make initial query to the on-chain state") + } + // add some default sensible configs mt = meterer.NewMeterer( config, paymentChainState, store, - logging.NewNoopLogger(), + logger, // metrics.NewNoopMetrics(), ) + mt.Start(context.Background()) } @@ -167,7 +169,11 @@ func teardown() { func TestMetererReservations(t *testing.T) { ctx := context.Background() - binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), mt.ReservationWindow) + paymentChainState.On("GetReservationWindow", testifymock.Anything).Return(uint32(1), nil) + paymentChainState.On("GetGlobalSymbolsPerSecond", testifymock.Anything).Return(uint64(1009), nil) + paymentChainState.On("GetMinNumSymbols", testifymock.Anything).Return(uint32(3), nil) + + binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), mt.ChainPaymentState.GetReservationWindow()) quoromNumbers := []uint8{0, 1} paymentChainState.On("GetActiveReservationByAccount", testifymock.Anything, testifymock.MatchedBy(func(account string) bool { @@ -176,7 +182,7 @@ func TestMetererReservations(t *testing.T) { paymentChainState.On("GetActiveReservationByAccount", testifymock.Anything, testifymock.MatchedBy(func(account string) bool { return account == accountID2 })).Return(account2Reservations, nil) - paymentChainState.On("GetActiveReservationByAccount", testifymock.Anything, testifymock.Anything).Return(core.ActiveReservation{}, errors.New("reservation not found")) + paymentChainState.On("GetActiveReservationByAccount", testifymock.Anything, testifymock.Anything).Return(core.ActiveReservation{}, fmt.Errorf("reservation not found")) // test invalid quorom ID blob, header := createMetererInput(1, 0, 1000, []uint8{0, 1, 2}, accountID1) @@ -250,6 +256,8 @@ func TestMetererReservations(t *testing.T) { func TestMetererOnDemand(t *testing.T) { ctx := context.Background() quorumNumbers := []uint8{0, 1} + paymentChainState.On("GetPricePerSymbol", testifymock.Anything).Return(uint32(2), nil) + paymentChainState.On("GetMinNumSymbols", testifymock.Anything).Return(uint32(3), nil) binIndex := uint32(0) // this field doesn't matter for on-demand payments wrt global rate limit paymentChainState.On("GetOnDemandPaymentByAccount", testifymock.Anything, testifymock.MatchedBy(func(account string) bool { @@ -258,7 +266,7 @@ func TestMetererOnDemand(t *testing.T) { paymentChainState.On("GetOnDemandPaymentByAccount", testifymock.Anything, testifymock.MatchedBy(func(account string) bool { return account == accountID2 })).Return(account2OnDemandPayments, nil) - paymentChainState.On("GetOnDemandPaymentByAccount", testifymock.Anything, testifymock.Anything).Return(core.OnDemandPayment{}, errors.New("payment not found")) + paymentChainState.On("GetOnDemandPaymentByAccount", testifymock.Anything, testifymock.Anything).Return(core.OnDemandPayment{}, fmt.Errorf("payment not found")) paymentChainState.On("GetOnDemandQuorumNumbers", testifymock.Anything).Return(quorumNumbers, nil) // test unregistered account @@ -291,7 +299,7 @@ func TestMetererOnDemand(t *testing.T) { // test duplicated cumulative payments dataLength := uint(100) priceCharged := mt.PaymentCharged(dataLength) - assert.Equal(t, uint64(102*mt.PricePerSymbol), priceCharged) + assert.Equal(t, uint64(102*mt.ChainPaymentState.GetPricePerSymbol()), priceCharged) blob, header = createMetererInput(binIndex, priceCharged, dataLength, quorumNumbers, accountID2) err = mt.MeterRequest(ctx, *blob, *header) assert.NoError(t, err) @@ -364,9 +372,9 @@ func TestMeterer_paymentCharged(t *testing.T) { { name: "Data length less than min chargeable size", dataLength: 512, - pricePerSymbol: 2, + pricePerSymbol: 1, minNumSymbols: 1024, - expected: 2048, + expected: 1024, }, { name: "Data length greater than min chargeable size", @@ -391,13 +399,13 @@ func TestMeterer_paymentCharged(t *testing.T) { }, } + paymentChainState := &mock.MockOnchainPaymentState{} for _, tt := range tests { + paymentChainState.On("GetPricePerSymbol", testifymock.Anything).Return(uint32(tt.pricePerSymbol), nil) + paymentChainState.On("GetMinNumSymbols", testifymock.Anything).Return(uint32(tt.minNumSymbols), nil) t.Run(tt.name, func(t *testing.T) { m := &meterer.Meterer{ - Config: meterer.Config{ - PricePerSymbol: tt.pricePerSymbol, - MinNumSymbols: tt.minNumSymbols, - }, + ChainPaymentState: paymentChainState, } result := m.PaymentCharged(tt.dataLength) assert.Equal(t, tt.expected, result) @@ -413,19 +421,19 @@ func TestMeterer_symbolsCharged(t *testing.T) { expected uint32 }{ { - name: "Data length equal to min chargeable size", + name: "Data length equal to min number of symobols", dataLength: 1024, minNumSymbols: 1024, expected: 1024, }, { - name: "Data length less than min chargeable size", + name: "Data length less than min number of symbols", dataLength: 512, minNumSymbols: 1024, expected: 1024, }, { - name: "Data length greater than min chargeable size", + name: "Data length greater than min number of symbols", dataLength: 2048, minNumSymbols: 1024, expected: 2048, @@ -444,12 +452,12 @@ func TestMeterer_symbolsCharged(t *testing.T) { }, } + paymentChainState := &mock.MockOnchainPaymentState{} for _, tt := range tests { + paymentChainState.On("GetMinNumSymbols", testifymock.Anything).Return(uint32(tt.minNumSymbols), nil) t.Run(tt.name, func(t *testing.T) { m := &meterer.Meterer{ - Config: meterer.Config{ - MinNumSymbols: tt.minNumSymbols, - }, + ChainPaymentState: paymentChainState, } result := m.SymbolsCharged(tt.dataLength) assert.Equal(t, tt.expected, result) diff --git a/core/meterer/onchain_state.go b/core/meterer/onchain_state.go index bfb9681075..7db5a1bd55 100644 --- a/core/meterer/onchain_state.go +++ b/core/meterer/onchain_state.go @@ -2,7 +2,7 @@ package meterer import ( "context" - "errors" + "fmt" "sync" "github.com/Layr-Labs/eigenda/core" @@ -17,82 +17,106 @@ type OnchainPayment interface { GetActiveReservationByAccount(ctx context.Context, accountID string) (core.ActiveReservation, error) GetOnDemandPaymentByAccount(ctx context.Context, accountID string) (core.OnDemandPayment, error) GetOnDemandQuorumNumbers(ctx context.Context) ([]uint8, error) + GetGlobalSymbolsPerSecond() uint64 + GetMinNumSymbols() uint32 + GetPricePerSymbol() uint32 + GetReservationWindow() uint32 } +var _ OnchainPayment = (*OnchainPaymentState)(nil) + type OnchainPaymentState struct { tx *eth.Reader - ActiveReservations map[string]core.ActiveReservation - OnDemandPayments map[string]core.OnDemandPayment - OnDemandQuorumNumbers []uint8 - ReservationsLock sync.RWMutex - OnDemandLocks sync.RWMutex + ActiveReservations map[string]core.ActiveReservation + OnDemandPayments map[string]core.OnDemandPayment + + ReservationsLock sync.RWMutex + OnDemandLocks sync.RWMutex + + PaymentVaultParams PaymentVaultParams } -func NewOnchainPaymentState(ctx context.Context, tx *eth.Reader) (OnchainPaymentState, error) { - blockNumber, err := tx.GetCurrentBlockNumber(ctx) - if err != nil { - return OnchainPaymentState{}, err - } +type PaymentVaultParams struct { + GlobalSymbolsPerSecond uint64 + MinNumSymbols uint32 + PricePerSymbol uint32 + ReservationWindow uint32 + OnDemandQuorumNumbers []uint8 +} - quorumNumbers, err := tx.GetRequiredQuorumNumbers(ctx, blockNumber) +func NewOnchainPaymentState(ctx context.Context, tx *eth.Reader) (OnchainPaymentState, error) { + paymentVaultParams, err := GetPaymentVaultParams(ctx, tx) if err != nil { return OnchainPaymentState{}, err } return OnchainPaymentState{ - tx: tx, - ActiveReservations: make(map[string]core.ActiveReservation), - OnDemandPayments: make(map[string]core.OnDemandPayment), - OnDemandQuorumNumbers: quorumNumbers, + tx: tx, + ActiveReservations: make(map[string]core.ActiveReservation), + OnDemandPayments: make(map[string]core.OnDemandPayment), + PaymentVaultParams: paymentVaultParams, }, nil } -// RefreshOnchainPaymentState returns the current onchain payment state (TODO: can optimize based on contract interface) -func (pcs *OnchainPaymentState) RefreshOnchainPaymentState(ctx context.Context, tx *eth.Reader) error { +func GetPaymentVaultParams(ctx context.Context, tx *eth.Reader) (PaymentVaultParams, error) { blockNumber, err := tx.GetCurrentBlockNumber(ctx) if err != nil { - return err + return PaymentVaultParams{}, err } - pcs.ReservationsLock.Lock() - accountIDs := make([]string, 0, len(pcs.ActiveReservations)) - for accountID := range pcs.ActiveReservations { - accountIDs = append(accountIDs, accountID) + quorumNumbers, err := tx.GetRequiredQuorumNumbers(ctx, blockNumber) + if err != nil { + return PaymentVaultParams{}, err } - activeReservations, err := tx.GetActiveReservations(ctx, blockNumber, accountIDs) + globalSymbolsPerSecond, err := tx.GetGlobalSymbolsPerSecond(ctx) if err != nil { - return err + return PaymentVaultParams{}, err } - pcs.ActiveReservations = activeReservations - pcs.ReservationsLock.Unlock() - pcs.OnDemandLocks.Lock() - accountIDs = make([]string, 0, len(pcs.OnDemandPayments)) - for accountID := range pcs.OnDemandPayments { - accountIDs = append(accountIDs, accountID) + minNumSymbols, err := tx.GetMinNumSymbols(ctx) + if err != nil { + return PaymentVaultParams{}, err } - onDemandPayments, err := tx.GetOnDemandPayments(ctx, blockNumber, accountIDs) + pricePerSymbol, err := tx.GetPricePerSymbol(ctx) if err != nil { - return err + return PaymentVaultParams{}, err } - pcs.OnDemandPayments = onDemandPayments - pcs.OnDemandLocks.Unlock() + reservationWindow, err := tx.GetReservationWindow(ctx) + if err != nil { + return PaymentVaultParams{}, err + } + + return PaymentVaultParams{ + OnDemandQuorumNumbers: quorumNumbers, + GlobalSymbolsPerSecond: globalSymbolsPerSecond, + MinNumSymbols: minNumSymbols, + PricePerSymbol: pricePerSymbol, + ReservationWindow: reservationWindow, + }, nil +} + +// RefreshOnchainPaymentState returns the current onchain payment state +func (pcs *OnchainPaymentState) RefreshOnchainPaymentState(ctx context.Context, tx *eth.Reader) error { + paymentVaultParams, err := GetPaymentVaultParams(ctx, tx) + if err != nil { + return err + } + pcs.PaymentVaultParams = paymentVaultParams return nil } // GetActiveReservationByAccount returns a pointer to the active reservation for the given account ID; no writes will be made to the reservation -func (pcs *OnchainPaymentState) GetActiveReservationByAccount(ctx context.Context, blockNumber uint32, accountID string) (core.ActiveReservation, error) { +func (pcs *OnchainPaymentState) GetActiveReservationByAccount(ctx context.Context, accountID string) (core.ActiveReservation, error) { if reservation, ok := pcs.ActiveReservations[accountID]; ok { return reservation, nil } - // pulls the chain state - res, err := pcs.tx.GetActiveReservationByAccount(ctx, blockNumber, accountID) + res, err := pcs.GetActiveReservationByAccountOnChain(ctx, accountID) if err != nil { - return core.ActiveReservation{}, errors.New("payment not found") + return core.ActiveReservation{}, err } pcs.ReservationsLock.Lock() @@ -101,15 +125,27 @@ func (pcs *OnchainPaymentState) GetActiveReservationByAccount(ctx context.Contex return res, nil } +// GetActiveReservationByAccountOnChain returns on-chain reservation for the given account ID +func (pcs *OnchainPaymentState) GetActiveReservationByAccountOnChain(ctx context.Context, accountID string) (core.ActiveReservation, error) { + blockNumber, err := pcs.tx.GetCurrentBlockNumber(ctx) + if err != nil { + return core.ActiveReservation{}, err + } + res, err := pcs.tx.GetActiveReservationByAccount(ctx, blockNumber, accountID) + if err != nil { + return core.ActiveReservation{}, fmt.Errorf("reservation account not found on-chain: %w", err) + } + return res, nil +} + // GetOnDemandPaymentByAccount returns a pointer to the on-demand payment for the given account ID; no writes will be made to the payment -func (pcs *OnchainPaymentState) GetOnDemandPaymentByAccount(ctx context.Context, blockNumber uint32, accountID string) (core.OnDemandPayment, error) { +func (pcs *OnchainPaymentState) GetOnDemandPaymentByAccount(ctx context.Context, accountID string) (core.OnDemandPayment, error) { if payment, ok := pcs.OnDemandPayments[accountID]; ok { return payment, nil } - // pulls the chain state - res, err := pcs.tx.GetOnDemandPaymentByAccount(ctx, blockNumber, accountID) + res, err := pcs.GetOnDemandPaymentByAccountOnChain(ctx, accountID) if err != nil { - return core.OnDemandPayment{}, errors.New("payment not found") + return core.OnDemandPayment{}, err } pcs.OnDemandLocks.Lock() @@ -118,6 +154,38 @@ func (pcs *OnchainPaymentState) GetOnDemandPaymentByAccount(ctx context.Context, return res, nil } -func (pcs *OnchainPaymentState) GetOnDemandQuorumNumbers(ctx context.Context, blockNumber uint32) ([]uint8, error) { +func (pcs *OnchainPaymentState) GetOnDemandPaymentByAccountOnChain(ctx context.Context, accountID string) (core.OnDemandPayment, error) { + blockNumber, err := pcs.tx.GetCurrentBlockNumber(ctx) + if err != nil { + return core.OnDemandPayment{}, err + } + res, err := pcs.tx.GetOnDemandPaymentByAccount(ctx, blockNumber, accountID) + if err != nil { + return core.OnDemandPayment{}, fmt.Errorf("on-demand not found on-chain: %w", err) + } + return res, nil +} + +func (pcs *OnchainPaymentState) GetOnDemandQuorumNumbers(ctx context.Context) ([]uint8, error) { + blockNumber, err := pcs.tx.GetCurrentBlockNumber(ctx) + if err != nil { + return nil, err + } return pcs.tx.GetRequiredQuorumNumbers(ctx, blockNumber) } + +func (pcs *OnchainPaymentState) GetGlobalSymbolsPerSecond() uint64 { + return pcs.PaymentVaultParams.GlobalSymbolsPerSecond +} + +func (pcs *OnchainPaymentState) GetMinNumSymbols() uint32 { + return pcs.PaymentVaultParams.MinNumSymbols +} + +func (pcs *OnchainPaymentState) GetPricePerSymbol() uint32 { + return pcs.PaymentVaultParams.PricePerSymbol +} + +func (pcs *OnchainPaymentState) GetReservationWindow() uint32 { + return pcs.PaymentVaultParams.ReservationWindow +} diff --git a/core/mock/payment_state.go b/core/mock/payment_state.go index d69c66e3c6..c5e7885107 100644 --- a/core/mock/payment_state.go +++ b/core/mock/payment_state.go @@ -55,3 +55,23 @@ func (m *MockOnchainPaymentState) GetOnDemandQuorumNumbers(ctx context.Context) } return value, args.Error(1) } + +func (m *MockOnchainPaymentState) GetGlobalSymbolsPerSecond() uint64 { + args := m.Called() + return args.Get(0).(uint64) +} + +func (m *MockOnchainPaymentState) GetMinNumSymbols() uint32 { + args := m.Called() + return args.Get(0).(uint32) +} + +func (m *MockOnchainPaymentState) GetPricePerSymbol() uint32 { + args := m.Called() + return args.Get(0).(uint32) +} + +func (m *MockOnchainPaymentState) GetReservationWindow() uint32 { + args := m.Called() + return args.Get(0).(uint32) +} diff --git a/disperser/apiserver/server.go b/disperser/apiserver/server.go index 78720ee82d..83dbdb9bed 100644 --- a/disperser/apiserver/server.go +++ b/disperser/apiserver/server.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "errors" "fmt" + "math/big" "net" "slices" "strings" @@ -19,6 +20,7 @@ import ( healthcheck "github.com/Layr-Labs/eigenda/common/healthcheck" "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/auth" + "github.com/Layr-Labs/eigenda/core/meterer" "github.com/Layr-Labs/eigenda/disperser" "github.com/Layr-Labs/eigenda/encoding" "github.com/Layr-Labs/eigenda/encoding/rs" @@ -44,6 +46,7 @@ type DispersalServer struct { tx core.Reader quorumConfig QuorumConfig + meterer *meterer.Meterer ratelimiter common.RateLimiter authenticator core.BlobRequestAuthenticator @@ -69,6 +72,7 @@ func NewDispersalServer( tx core.Reader, _logger logging.Logger, metrics *disperser.Metrics, + meterer *meterer.Meterer, ratelimiter common.RateLimiter, rateConfig RateConfig, maxBlobSize int, @@ -90,6 +94,7 @@ func NewDispersalServer( tx: tx, metrics: metrics, logger: logger, + meterer: meterer, ratelimiter: ratelimiter, authenticator: authenticator, mu: &sync.RWMutex{}, @@ -209,7 +214,7 @@ func (s *DispersalServer) DisperseBlobAuthenticated(stream pb.Disperser_Disperse } // Disperse the blob - reply, err := s.disperseBlob(ctx, blob, authenticatedAddress, "DisperseBlobAuthenticated") + reply, err := s.disperseBlob(ctx, blob, authenticatedAddress, "DisperseBlobAuthenticated", nil) if err != nil { // Note the disperseBlob already updated metrics for this error. s.logger.Info("failed to disperse blob", "err", err) @@ -241,7 +246,7 @@ func (s *DispersalServer) DisperseBlob(ctx context.Context, req *pb.DisperseBlob return nil, api.NewErrorInvalidArg(err.Error()) } - reply, err := s.disperseBlob(ctx, blob, "", "DisperseBlob") + reply, err := s.disperseBlob(ctx, blob, "", "DisperseBlob", nil) if err != nil { // Note the disperseBlob already updated metrics for this error. s.logger.Info("failed to disperse blob", "err", err) @@ -251,13 +256,9 @@ func (s *DispersalServer) DisperseBlob(ctx context.Context, req *pb.DisperseBlob return reply, err } -func (s *DispersalServer) DispersePaidBlob(ctx context.Context, req *pb.DispersePaidBlobRequest) (*pb.DisperseBlobReply, error) { - return nil, api.NewErrorInternal("not implemented") -} - // Note: disperseBlob will internally update metrics upon an error; the caller doesn't need // to track the error again. -func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, authenticatedAddress string, apiMethodName string) (*pb.DisperseBlobReply, error) { +func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, authenticatedAddress string, apiMethodName string, paymentHeader *core.PaymentMetadata) (*pb.DisperseBlobReply, error) { timer := prometheus.NewTimer(prometheus.ObserverFunc(func(f float64) { s.metrics.ObserveLatency("DisperseBlob", f*1000) // make milliseconds })) @@ -282,7 +283,13 @@ func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, aut s.logger.Debug("received a new blob dispersal request", "authenticatedAddress", authenticatedAddress, "origin", origin, "blobSizeBytes", blobSize, "securityParams", strings.Join(securityParamsStrings, ", ")) - if s.ratelimiter != nil { + // If paymentHeader is not empty, we use the meterer, otherwise we use the ratelimiter if the ratelimiter is available + if paymentHeader != nil { + err := s.meterer.MeterRequest(ctx, *blob, *paymentHeader) + if err != nil { + return nil, api.NewErrorResourceExhausted(err.Error()) + } + } else if s.ratelimiter != nil { err := s.checkRateLimitsAndAddRatesToHeader(ctx, blob, origin, authenticatedAddress, apiMethodName) if err != nil { // Note checkRateLimitsAndAddRatesToHeader already updated the metrics for this error. @@ -311,6 +318,43 @@ func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, aut }, nil } +func (s *DispersalServer) DispersePaidBlob(ctx context.Context, req *pb.DispersePaidBlobRequest) (*pb.DisperseBlobReply, error) { + // If EnablePaymentMeter is false, meterer gets set to nil at start + // In that case, the function should not continue. (checking ) + if s.meterer == nil { + return nil, api.NewErrorInternal("payment feature is not enabled") + } + blob, err := s.validatePaidRequestAndGetBlob(ctx, req) + binIndex := req.PaymentHeader.BinIndex + cumulativePayment := new(big.Int).SetBytes(req.PaymentHeader.CumulativePayment) + //todo: before disperse blob, validate the signature + signature := req.PaymentSignature + if !auth.VerifyPaymentSignature(req.GetPaymentHeader(), signature) { + return nil, api.NewErrorInvalidArg("payment signature is invalid") + } + if err != nil { + for _, quorumID := range req.QuorumNumbers { + s.metrics.HandleFailedRequest(codes.InvalidArgument.String(), fmt.Sprint(quorumID), len(req.GetData()), "DispersePaidBlob") + } + s.metrics.HandleInvalidArgRpcRequest("DispersePaidBlob") + return nil, api.NewErrorInvalidArg(err.Error()) + } + + paymentHeader := core.PaymentMetadata{ + AccountID: blob.RequestHeader.AccountID, + BinIndex: binIndex, + CumulativePayment: cumulativePayment, + } + reply, err := s.disperseBlob(ctx, blob, "", "DispersePaidBlob", &paymentHeader) + if err != nil { + // Note the DispersePaidBlob already updated metrics for this error. + s.logger.Info("failed to disperse blob", "err", err) + } else { + s.metrics.HandleSuccessfulRpcRequest("DispersePaidBlob") + } + return reply, err +} + func (s *DispersalServer) getAccountRate(origin, authenticatedAddress string, quorumID core.QuorumID) (*PerUserRateInfo, string, error) { unauthRates, ok := s.rateConfig.QuorumRateInfos[quorumID] if !ok { @@ -949,7 +993,7 @@ func (s *DispersalServer) validateRequestAndGetBlob(ctx context.Context, req *pb } if len(req.GetCustomQuorumNumbers()) > 256 { - return nil, errors.New("number of custom_quorum_numbers must not exceed 256") + return nil, fmt.Errorf("number of custom_quorum_numbers must not exceed 256") } // validate every 32 bytes is a valid field element @@ -965,7 +1009,7 @@ func (s *DispersalServer) validateRequestAndGetBlob(ctx context.Context, req *pb } if len(req.GetCustomQuorumNumbers()) > int(quorumConfig.QuorumCount) { - return nil, errors.New("number of custom_quorum_numbers must not exceed number of quorums") + return nil, fmt.Errorf("number of custom_quorum_numbers must not exceed number of quorums") } seenQuorums := make(map[uint8]struct{}) @@ -1033,3 +1077,97 @@ func (s *DispersalServer) validateRequestAndGetBlob(ctx context.Context, req *pb return blob, nil } + +// TODO: refactor checks with validateRequestAndGetBlob; most checks are the same, but paid requests have different quorum requirements +func (s *DispersalServer) validatePaidRequestAndGetBlob(ctx context.Context, req *pb.DispersePaidBlobRequest) (*core.Blob, error) { + + data := req.GetData() + blobSize := len(data) + // The blob size in bytes must be in range [1, maxBlobSize]. + if blobSize > s.maxBlobSize { + return nil, fmt.Errorf("blob size cannot exceed %v Bytes", s.maxBlobSize) + } + if blobSize == 0 { + return nil, fmt.Errorf("blob size must be greater than 0") + } + + if len(req.GetQuorumNumbers()) > 256 { + return nil, errors.New("number of custom_quorum_numbers must not exceed 256") + } + + // validate every 32 bytes is a valid field element + _, err := rs.ToFrArray(data) + if err != nil { + s.logger.Error("failed to convert a 32bytes as a field element", "err", err) + return nil, api.NewErrorInvalidArg(fmt.Sprintf("encountered an error to convert a 32-bytes into a valid field element, please use the correct format where every 32bytes(big-endian) is less than 21888242871839275222246405745257275088548364400416034343698204186575808495617: %v", err)) + } + + quorumConfig, err := s.updateQuorumConfig(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get quorum config: %w", err) + } + + if len(req.GetQuorumNumbers()) > int(quorumConfig.QuorumCount) { + return nil, errors.New("number of custom_quorum_numbers must not exceed number of quorums") + } + + seenQuorums := make(map[uint8]struct{}) + + // TODO: validate payment signature against payment metadata + if !auth.VerifyPaymentSignature(req.GetPaymentHeader(), req.GetPaymentSignature()) { + return nil, fmt.Errorf("payment signature is invalid") + } + // Unlike regular blob dispersal request validation, there's no check with required quorums + // Because Reservation has their specific quorum requirements, and on-demand is only allowed and paid to the required quorums. + // Payment specific validations are done within the meterer library. + for i := range req.GetQuorumNumbers() { + + if req.GetQuorumNumbers()[i] > core.MaxQuorumID { + return nil, fmt.Errorf("custom_quorum_numbers must be in range [0, 254], but found %d", req.GetQuorumNumbers()[i]) + } + + quorumID := uint8(req.GetQuorumNumbers()[i]) + if quorumID >= quorumConfig.QuorumCount { + return nil, fmt.Errorf("custom_quorum_numbers must be in range [0, %d], but found %d", s.quorumConfig.QuorumCount-1, quorumID) + } + + if _, ok := seenQuorums[quorumID]; ok { + return nil, fmt.Errorf("custom_quorum_numbers must not contain duplicates") + } + seenQuorums[quorumID] = struct{}{} + + } + + if len(seenQuorums) == 0 { + return nil, fmt.Errorf("the blob must be sent to at least one quorum") + } + + params := make([]*core.SecurityParam, len(seenQuorums)) + i := 0 + for quorumID := range seenQuorums { + params[i] = &core.SecurityParam{ + QuorumID: core.QuorumID(quorumID), + AdversaryThreshold: quorumConfig.SecurityParams[quorumID].AdversaryThreshold, + ConfirmationThreshold: quorumConfig.SecurityParams[quorumID].ConfirmationThreshold, + } + err = params[i].Validate() + if err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + i++ + } + + header := core.BlobRequestHeader{ + BlobAuthHeader: core.BlobAuthHeader{ + AccountID: req.PaymentHeader.AccountId, + }, + SecurityParams: params, + } + + blob := &core.Blob{ + RequestHeader: header, + Data: data, + } + + return blob, nil +} diff --git a/disperser/apiserver/server_test.go b/disperser/apiserver/server_test.go index c37c60cd9f..32e1e59fea 100644 --- a/disperser/apiserver/server_test.go +++ b/disperser/apiserver/server_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/Layr-Labs/eigenda/core/auth" + "github.com/Layr-Labs/eigenda/core/meterer" "github.com/Layr-Labs/eigenda/core/mock" "github.com/Layr-Labs/eigenda/disperser/apiserver" "github.com/Layr-Labs/eigenda/disperser/common/blobstore" @@ -52,7 +53,7 @@ var ( bucketTableName = fmt.Sprintf("test-BucketStore-%v", UUID) deployLocalStack bool - localStackPort = "4568" + localStackPort = "4569" allowlistFile *os.File testMaxBlobSize = 2 * 1024 * 1024 ) @@ -136,7 +137,7 @@ func TestDisperseBlobWithRequiredQuorums(t *testing.T) { } transactor.On("GetQuorumSecurityParams", tmock.Anything).Return(quorumParams, nil) - dispersalServer := newTestServer(transactor) + dispersalServer := newTestServer(transactor, t.Name()) data := make([]byte, 1024) _, err := rand.Read(data) @@ -192,6 +193,7 @@ func TestDisperseBlobWithRequiredQuorums(t *testing.T) { } func TestDisperseBlobWithInvalidQuorum(t *testing.T) { + data := make([]byte, 1024) _, err := rand.Read(data) assert.NoError(t, err) @@ -632,7 +634,7 @@ func setup() { transactor.On("GetQuorumSecurityParams", tmock.Anything).Return(quorumParams, nil) transactor.On("GetRequiredQuorumNumbers", tmock.Anything).Return([]uint8{}, nil) - dispersalServer = newTestServer(transactor) + dispersalServer = newTestServer(transactor, "setup") dispersalServerV2 = newTestServerV2() } @@ -645,7 +647,7 @@ func teardown() { } } -func newTestServer(transactor core.Writer) *apiserver.DispersalServer { +func newTestServer(transactor core.Writer, testName string) *apiserver.DispersalServer { logger := logging.NewNoopLogger() bucketName := "test-eigenda-blobstore" @@ -674,6 +676,43 @@ func newTestServer(transactor core.Writer) *apiserver.DispersalServer { if err != nil { panic("failed to create bucket store") } + + mockState := &mock.MockOnchainPaymentState{} + mockState.On("RefreshOnchainPaymentState", tmock.Anything).Return(nil).Maybe() + if err := mockState.RefreshOnchainPaymentState(context.Background(), nil); err != nil { + panic("failed to make initial query to the on-chain state") + } + + // append test name to each table name for an unique store + table_names := []string{"reservations_server_" + testName, "ondemand_server_" + testName, "global_server_" + testName} + err = meterer.CreateReservationTable(awsConfig, table_names[0]) + if err != nil { + teardown() + panic("failed to create reservation table") + } + err = meterer.CreateOnDemandTable(awsConfig, table_names[1]) + if err != nil { + teardown() + panic("failed to create ondemand table") + } + err = meterer.CreateGlobalReservationTable(awsConfig, table_names[2]) + if err != nil { + teardown() + panic("failed to create global reservation table") + } + + store, err := meterer.NewOffchainStore( + awsConfig, + table_names[0], + table_names[1], + table_names[2], + logger, + ) + if err != nil { + teardown() + panic("failed to create offchain store") + } + meterer := meterer.NewMeterer(meterer.Config{}, mockState, store, logger) ratelimiter := ratelimit.NewRateLimiter(prometheus.NewRegistry(), globalParams, bucketStore, logger) rateConfig := apiserver.RateConfig{ @@ -730,7 +769,7 @@ func newTestServer(transactor core.Writer) *apiserver.DispersalServer { return apiserver.NewDispersalServer(disperser.ServerConfig{ GrpcPort: "51001", GrpcTimeout: 1 * time.Second, - }, queue, transactor, logger, disperser.NewMetrics(prometheus.NewRegistry(), "9001", logger), ratelimiter, rateConfig, testMaxBlobSize) + }, queue, transactor, logger, disperser.NewMetrics(prometheus.NewRegistry(), "9001", logger), meterer, ratelimiter, rateConfig, testMaxBlobSize) } func disperseBlob(t *testing.T, server *apiserver.DispersalServer, data []byte) (pb.BlobStatus, uint, []byte) { diff --git a/disperser/cmd/apiserver/config.go b/disperser/cmd/apiserver/config.go index 2eed16d49b..86fffa87c0 100644 --- a/disperser/cmd/apiserver/config.go +++ b/disperser/cmd/apiserver/config.go @@ -22,19 +22,25 @@ const ( ) type Config struct { - DisperserVersion DisperserVersion - AwsClientConfig aws.ClientConfig - BlobstoreConfig blobstore.Config - ServerConfig disperser.ServerConfig - LoggerConfig common.LoggerConfig - MetricsConfig disperser.MetricsConfig - RatelimiterConfig ratelimit.Config - RateConfig apiserver.RateConfig - EnableRatelimiter bool - BucketTableName string - BucketStoreSize int - EthClientConfig geth.EthClientConfig - MaxBlobSize int + DisperserVersion DisperserVersion + AwsClientConfig aws.ClientConfig + BlobstoreConfig blobstore.Config + ServerConfig disperser.ServerConfig + LoggerConfig common.LoggerConfig + MetricsConfig disperser.MetricsConfig + RatelimiterConfig ratelimit.Config + RateConfig apiserver.RateConfig + EnableRatelimiter bool + EnablePaymentMeterer bool + UpdateInterval int + ChainReadTimeout int + ReservationsTableName string + OnDemandTableName string + GlobalRateTableName string + BucketTableName string + BucketStoreSize int + EthClientConfig geth.EthClientConfig + MaxBlobSize int BLSOperatorStateRetrieverAddr string EigenDAServiceManagerAddr string @@ -77,13 +83,19 @@ func NewConfig(ctx *cli.Context) (Config, error) { HTTPPort: ctx.GlobalString(flags.MetricsHTTPPort.Name), EnableMetrics: ctx.GlobalBool(flags.EnableMetrics.Name), }, - RatelimiterConfig: ratelimiterConfig, - RateConfig: rateConfig, - EnableRatelimiter: ctx.GlobalBool(flags.EnableRatelimiter.Name), - BucketTableName: ctx.GlobalString(flags.BucketTableName.Name), - BucketStoreSize: ctx.GlobalInt(flags.BucketStoreSize.Name), - EthClientConfig: geth.ReadEthClientConfigRPCOnly(ctx), - MaxBlobSize: ctx.GlobalInt(flags.MaxBlobSize.Name), + RatelimiterConfig: ratelimiterConfig, + RateConfig: rateConfig, + EnableRatelimiter: ctx.GlobalBool(flags.EnableRatelimiter.Name), + EnablePaymentMeterer: ctx.GlobalBool(flags.EnablePaymentMeterer.Name), + ReservationsTableName: ctx.GlobalString(flags.ReservationsTableName.Name), + OnDemandTableName: ctx.GlobalString(flags.OnDemandTableName.Name), + GlobalRateTableName: ctx.GlobalString(flags.GlobalRateTableName.Name), + BucketTableName: ctx.GlobalString(flags.BucketTableName.Name), + BucketStoreSize: ctx.GlobalInt(flags.BucketStoreSize.Name), + UpdateInterval: ctx.GlobalInt(flags.UpdateInterval.Name), + ChainReadTimeout: ctx.GlobalInt(flags.ChainReadTimeout.Name), + EthClientConfig: geth.ReadEthClientConfigRPCOnly(ctx), + MaxBlobSize: ctx.GlobalInt(flags.MaxBlobSize.Name), BLSOperatorStateRetrieverAddr: ctx.GlobalString(flags.BlsOperatorStateRetrieverFlag.Name), EigenDAServiceManagerAddr: ctx.GlobalString(flags.EigenDAServiceManagerFlag.Name), diff --git a/disperser/cmd/apiserver/flags/flags.go b/disperser/cmd/apiserver/flags/flags.go index 375b6596de..4a7cf51b82 100644 --- a/disperser/cmd/apiserver/flags/flags.go +++ b/disperser/cmd/apiserver/flags/flags.go @@ -76,11 +76,48 @@ var ( Required: true, EnvVar: common.PrefixEnvVar(envVarPrefix, "ENABLE_METRICS"), } + EnablePaymentMeterer = cli.BoolFlag{ + Name: common.PrefixFlag(FlagPrefix, "enable-payment-meterer"), + Usage: "enable payment meterer", + EnvVar: common.PrefixEnvVar(envVarPrefix, "ENABLE_PAYMENT_METERER"), + } EnableRatelimiter = cli.BoolFlag{ Name: common.PrefixFlag(FlagPrefix, "enable-ratelimiter"), Usage: "enable rate limiter", EnvVar: common.PrefixEnvVar(envVarPrefix, "ENABLE_RATELIMITER"), } + ReservationsTableName = cli.StringFlag{ + Name: common.PrefixFlag(FlagPrefix, "reservations-table-name"), + Usage: "name of the dynamodb table to store reservation usages", + Value: "reservations", + EnvVar: common.PrefixEnvVar(envVarPrefix, "RESERVATIONS_TABLE_NAME"), + } + OnDemandTableName = cli.StringFlag{ + Name: common.PrefixFlag(FlagPrefix, "on-demand-table-name"), + Usage: "name of the dynamodb table to store on-demand payments", + Value: "on_demand", + EnvVar: common.PrefixEnvVar(envVarPrefix, "ON_DEMAND_TABLE_NAME"), + } + GlobalRateTableName = cli.StringFlag{ + Name: common.PrefixFlag(FlagPrefix, "global-rate-table-name"), + Usage: "name of the dynamodb table to store global rate usage. If not provided, a local store will be used", + Value: "global_rate", + EnvVar: common.PrefixEnvVar(envVarPrefix, "GLOBAL_RATE_TABLE_NAME"), + } + UpdateInterval = cli.DurationFlag{ + Name: common.PrefixFlag(FlagPrefix, "update-interval"), + Usage: "update interval for refreshing the on-chain state", + Value: 1 * time.Second, + EnvVar: common.PrefixEnvVar(envVarPrefix, "UPDATE_INTERVAL"), + Required: false, + } + ChainReadTimeout = cli.UintFlag{ + Name: common.PrefixFlag(FlagPrefix, "chain-read-timeout"), + Usage: "timeout for reading from the chain", + Value: 10, + EnvVar: common.PrefixEnvVar(envVarPrefix, "CHAIN_READ_TIMEOUT"), + Required: false, + } BucketTableName = cli.StringFlag{ Name: common.PrefixFlag(FlagPrefix, "rate-bucket-table-name"), Usage: "name of the dynamodb table to store rate limiter buckets. If not provided, a local store will be used", @@ -117,9 +154,13 @@ var optionalFlags = []cli.Flag{ MetricsHTTPPort, EnableMetrics, EnableRatelimiter, + EnablePaymentMeterer, BucketStoreSize, GrpcTimeoutFlag, MaxBlobSize, + ReservationsTableName, + OnDemandTableName, + GlobalRateTableName, } // Flags contains the list of configuration options available to the binary. diff --git a/disperser/cmd/apiserver/main.go b/disperser/cmd/apiserver/main.go index 3497ac5b17..c55fb90aa7 100644 --- a/disperser/cmd/apiserver/main.go +++ b/disperser/cmd/apiserver/main.go @@ -8,6 +8,7 @@ import ( "time" "github.com/Layr-Labs/eigenda/common" + mt "github.com/Layr-Labs/eigenda/core/meterer" "github.com/Layr-Labs/eigenda/disperser/apiserver" "github.com/Layr-Labs/eigenda/disperser/common/blobstore" "github.com/Layr-Labs/eigenda/encoding/fft" @@ -101,6 +102,41 @@ func RunDisperserServer(ctx *cli.Context) error { reg := prometheus.NewRegistry() + var meterer *mt.Meterer + if config.EnablePaymentMeterer { + mtConfig := mt.Config{ + ChainReadTimeout: time.Duration(config.ChainReadTimeout) * time.Second, + UpdateInterval: time.Duration(config.UpdateInterval) * time.Second, + } + + paymentChainState, err := mt.NewOnchainPaymentState(context.Background(), transactor) + if err != nil { + return fmt.Errorf("failed to create onchain payment state: %w", err) + } + if err := paymentChainState.RefreshOnchainPaymentState(context.Background(), nil); err != nil { + return fmt.Errorf("failed to make initial query to the on-chain state: %w", err) + } + + offchainStore, err := mt.NewOffchainStore( + config.AwsClientConfig, + config.ReservationsTableName, + config.OnDemandTableName, + config.GlobalRateTableName, + logger, + ) + if err != nil { + return fmt.Errorf("failed to create offchain store: %w", err) + } + // add some default sensible configs + meterer = mt.NewMeterer( + mtConfig, + &paymentChainState, + offchainStore, + logger, + // metrics.NewNoopMetrics(), + ) + } + var ratelimiter common.RateLimiter if config.EnableRatelimiter { globalParams := config.RatelimiterConfig.GlobalRateParams @@ -136,6 +172,7 @@ func RunDisperserServer(ctx *cli.Context) error { transactor, logger, metrics, + meterer, ratelimiter, config.RateConfig, config.MaxBlobSize, diff --git a/test/integration_test.go b/test/integration_test.go index aa787a0002..7dde36695b 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" "log" + "math" "math/big" "net" "net/http" @@ -20,9 +22,13 @@ import ( "github.com/Layr-Labs/eigenda/encoding/kzg/prover" "github.com/Layr-Labs/eigenda/encoding/kzg/verifier" "github.com/Layr-Labs/eigenda/encoding/utils/codec" + "github.com/Layr-Labs/eigenda/inabox/deploy" "github.com/consensys/gnark-crypto/ecc/bn254/fp" + "github.com/ory/dockertest/v3" clientsmock "github.com/Layr-Labs/eigenda/api/clients/mock" + commonaws "github.com/Layr-Labs/eigenda/common/aws" + "github.com/Layr-Labs/eigenda/core/meterer" "github.com/Layr-Labs/eigenda/disperser/apiserver" dispatcher "github.com/Layr-Labs/eigenda/disperser/batcher/grpc" "github.com/Layr-Labs/eigenda/disperser/encoder" @@ -52,6 +58,7 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -65,6 +72,13 @@ var ( gettysburgAddressBytes = []byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.") serviceManagerAddress = gethcommon.HexToAddress("0x0000000000000000000000000000000000000000") handleBatchLivenessChan = make(chan time.Time, 1) + + dockertestPool *dockertest.Pool + dockertestResource *dockertest.Resource + clientConfig commonaws.ClientConfig + + deployLocalStack bool + localStackPort = "4565" ) const ( @@ -195,7 +209,92 @@ func mustMakeDisperser(t *testing.T, cst core.IndexedChainState, store disperser tx := &coremock.MockWriter{} tx.On("GetCurrentBlockNumber").Return(uint64(100), nil) tx.On("GetQuorumCount").Return(1, nil) - server := apiserver.NewDispersalServer(serverConfig, store, tx, logger, disperserMetrics, ratelimiter, rateConfig, testMaxBlobSize) + + minimumNumSymbols := uint32(128) + pricePerSymbol := uint32(1) + reservationLimit := uint64(1024) + paymentLimit := big.NewInt(512) + + // this is disperser client's private key used in tests + privateKey, err := crypto.HexToECDSA("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded") // Remove "0x" prefix + if err != nil { + panic("failed to convert hex to ECDSA") + } + publicKey := crypto.PubkeyToAddress(privateKey.PublicKey).Hex() + mockState := &coremock.MockOnchainPaymentState{} + mockState.On("GetActiveReservationByAccount", mock.Anything, mock.MatchedBy(func(account string) bool { + return account == publicKey + })).Return(core.ActiveReservation{SymbolsPerSec: reservationLimit, StartTimestamp: 0, EndTimestamp: math.MaxUint32, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}}, nil) + mockState.On("GetActiveReservationByAccount", mock.Anything, mock.Anything).Return(core.ActiveReservation{}, errors.New("reservation not found")) + + mockState.On("GetOnDemandPaymentByAccount", mock.Anything, mock.MatchedBy(func(account string) bool { + return account == publicKey + })).Return(core.OnDemandPayment{CumulativePayment: paymentLimit}, nil) + mockState.On("GetOnDemandPaymentByAccount", mock.Anything, mock.Anything).Return(core.OnDemandPayment{}, errors.New("payment not found")) + mockState.On("GetOnDemandQuorumNumbers", mock.Anything).Return([]uint8{0, 1}, nil) + mockState.On("GetMinNumSymbols", mock.Anything).Return(minimumNumSymbols, nil) + mockState.On("GetPricePerSymbol", mock.Anything).Return(pricePerSymbol, nil) + mockState.On("GetReservationWindow", mock.Anything).Return(uint32(60), nil) + mockState.On("GetGlobalSymbolsPerSecond", mock.Anything).Return(uint64(1024), nil) + mockState.On("GetOnDemandQuorumNumbers", mock.Anything).Return([]uint8{0, 1}, nil) + + deployLocalStack = !(os.Getenv("DEPLOY_LOCALSTACK") == "false") + if !deployLocalStack { + localStackPort = os.Getenv("LOCALSTACK_PORT") + } + + if deployLocalStack { + var err error + dockertestPool, dockertestResource, err = deploy.StartDockertestWithLocalstackContainer(localStackPort) + if err != nil { + teardown() + panic("failed to start localstack container") + } + } + + clientConfig = commonaws.ClientConfig{ + Region: "us-east-1", + AccessKey: "localstack", + SecretAccessKey: "localstack", + EndpointURL: fmt.Sprintf("http://0.0.0.0:%s", localStackPort), + } + + table_names := []string{"reservations_integration", "ondemand_integration", "global_integration"} + + err = meterer.CreateReservationTable(clientConfig, table_names[0]) + if err != nil { + teardown() + panic("failed to create reservation table") + } + err = meterer.CreateOnDemandTable(clientConfig, table_names[1]) + if err != nil { + teardown() + panic("failed to create ondemand table") + } + err = meterer.CreateGlobalReservationTable(clientConfig, table_names[2]) + if err != nil { + teardown() + panic("failed to create global reservation table") + } + + offchainStore, err := meterer.NewOffchainStore( + clientConfig, + table_names[0], + table_names[1], + table_names[2], + logger, + ) + if err != nil { + panic("failed to create offchain store") + } + + mockState.On("RefreshOnchainPaymentState", mock.Anything).Return(nil).Maybe() + if err := mockState.RefreshOnchainPaymentState(context.Background(), nil); err != nil { + panic("failed to make initial query to the on-chain state") + } + + meterer := meterer.NewMeterer(meterer.Config{}, mockState, offchainStore, logger) + server := apiserver.NewDispersalServer(serverConfig, store, tx, logger, disperserMetrics, meterer, ratelimiter, rateConfig, testMaxBlobSize) return TestDisperser{ batcher: batcher, @@ -349,7 +448,9 @@ func mustMakeRetriever(cst core.IndexedChainState, logger logging.Logger) (*comm } func TestMain(m *testing.M) { - os.Exit(m.Run()) + code := m.Run() + teardown() + os.Exit(code) } func TestDispersalAndRetrieval(t *testing.T) { @@ -582,3 +683,9 @@ func TestDispersalAndRetrieval(t *testing.T) { restored = bytes.TrimRight(restored, "\x00") assert.Equal(t, gettysburgAddressBytes, restored[:len(gettysburgAddressBytes)]) } + +func teardown() { + if deployLocalStack { + deploy.PurgeDockertestResources(dockertestPool, dockertestResource) + } +}