diff --git a/.gotestfmt/travisci/package.gotpl b/.gotestfmt/travisci/package.gotpl deleted file mode 100644 index 3da11fd9..00000000 --- a/.gotestfmt/travisci/package.gotpl +++ /dev/null @@ -1,49 +0,0 @@ -{{- /*gotype: github.com/gotesttools/gotestfmt/v2/parser.Package*/ -}} -{{- /* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ -}} -{{- $settings := .Settings -}} -{{- if and (or (not $settings.HideSuccessfulPackages) (ne .Result "PASS")) (or (not $settings.HideEmptyPackages) (ne .Result "SKIP") (ne (len .TestCases) 0)) -}} - {{- if eq .Result "PASS" -}} - {{ "\033" }}[0;32m - {{- else if eq .Result "SKIP" -}} - {{ "\033" }}[0;33m - {{- else -}} - {{ "\033" }}[0;31m - {{- end -}} - 📦 {{ .Name -}}{{- "\033" }}[0m - {{- with .Coverage -}} - {{- "\033" -}}[0;37m ({{ . }}% coverage){{- "\033" -}}[0m - {{- end -}} - {{- "\n" -}} - {{- with .Reason -}} - {{- " " -}}🛑 {{ . -}}{{- "\n" -}} - {{- end -}} - {{- with .Output -}} - {{- . -}}{{- "\n" -}} - {{- end -}} - {{- with .TestCases -}} - {{- range . -}} - {{- if or (not $settings.HideSuccessfulTests) (ne .Result "PASS") -}} - travis_fold:start:{{ .Name }}{{- "\n" -}} - {{- if eq .Result "PASS" -}} - {{ " \033" }}[0;32m✅ - {{- else if eq .Result "SKIP" -}} - {{ " \033" }}[0;33m🚧 - {{- else -}} - {{ " \033" }}[0;31m❌[FAIL] - {{- end -}} - {{ " " }}{{- .Name -}} - {{- "\033" -}}[0;37m ({{if $settings.ShowTestStatus}}{{.Result}}; {{end}}{{ .Duration -}}){{- "\033" -}}[0m{{- "\n" -}} - {{- with .Output -}} - {{- formatTestOutput . $settings -}} - {{- "\n" -}} - {{- end -}} - travis_fold:end:{{ .Name }}{{- "\n" -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{- "\n" -}} -{{- end -}} diff --git a/Makefile b/Makefile index aeb4ab6b..3c430e30 100644 --- a/Makefile +++ b/Makefile @@ -169,9 +169,9 @@ bench-preparer: FORCE bench-sign: FORCE $(go_cmd) test ./utils/signature/... -bench ".*" -run "^$$" | awk -f scripts/bench-tx-per-sec.awk -# Run verifier benchmarks with added op/sec column. -bench-verify: FORCE - $(go_cmd) test ./service/verifier/... -bench "BenchmarkVerifyForm.*" -run "^$$" | awk -f scripts/bench-tx-per-sec.awk +# Run sidecar benchmarks with added op/sec column. +bench-sidecar: FORCE + $(go_cmd) test ./service/sidecar/... -bench "Benchmark.*" -run "^$$" | awk -f scripts/bench-tx-per-sec.awk ######################### # Generate protos diff --git a/api/protoblocktx/block_tx.pb.go b/api/protoblocktx/block_tx.pb.go index e7d1680c..afa401d9 100644 --- a/api/protoblocktx/block_tx.pb.go +++ b/api/protoblocktx/block_tx.pb.go @@ -25,61 +25,84 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// Status represents the result of transaction validation. +// Except for NOT_VALIDATED, all statuses are recorded in the ledger. +// Some statuses are also stored in the state database which prevent resubmission of the same transaction ID. type Status int32 const ( - Status_COMMITTED Status = 0 - Status_ABORTED_MVCC_CONFLICT Status = 1 - Status_ABORTED_DUPLICATE_TXID Status = 2 - Status_ABORTED_SIGNATURE_INVALID Status = 3 - Status_ABORTED_MISSING_TXID Status = 4 - Status_ABORTED_DUPLICATE_NAMESPACE Status = 5 - Status_ABORTED_NAMESPACE_POLICY_INVALID Status = 6 - Status_ABORTED_NAMESPACE_ID_INVALID Status = 7 - Status_ABORTED_BLIND_WRITES_NOT_ALLOWED Status = 9 - Status_ABORTED_NO_WRITES Status = 10 - Status_ABORTED_UNSUPPORTED_TX_PAYLOAD Status = 11 - Status_ABORTED_NIL_KEY Status = 12 - Status_ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET Status = 13 - Status_ABORTED_EMPTY_NAMESPACES Status = 14 - Status_NOT_VALIDATED Status = 255 + // Should never be persisted or reported. + Status_NOT_VALIDATED Status = 0 // Default. The transaction has not been validated yet. + // Stored in the state database. Prevents submitting a transaction with the same ID. + Status_COMMITTED Status = 1 // Successfully committed and state updated. + Status_ABORTED_SIGNATURE_INVALID Status = 2 // Signature is invalid according to the namespace policy. + Status_ABORTED_MVCC_CONFLICT Status = 3 // Read-write set conflict. + // Cannot be stored in the state database because the TX ID cannot be extracted, + // or the TX ID entry is already occupied. + Status_REJECTED_DUPLICATE_TX_ID Status = 100 // Transaction with the same ID has already been processed. + Status_MALFORMED_BAD_ENVELOPE Status = 101 // Cannot unmarshal envelope. + Status_MALFORMED_MISSING_TX_ID Status = 102 // Envelope is missing transaction ID. + // Stored in the state database. Prevents submitting a transaction with the same ID. + Status_MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD Status = 103 // Unsupported envelope payload type. + Status_MALFORMED_BAD_ENVELOPE_PAYLOAD Status = 104 // Cannot unmarshal envelope's payload. + Status_MALFORMED_TX_ID_CONFLICT Status = 105 // Envelope's TX ID does not match the payload's TX ID. + Status_MALFORMED_EMPTY_NAMESPACES Status = 106 // No namespaces provided. + Status_MALFORMED_DUPLICATE_NAMESPACE Status = 107 // Duplicate namespace detected. + Status_MALFORMED_NAMESPACE_ID_INVALID Status = 108 // Invalid namespace identifier. + Status_MALFORMED_BLIND_WRITES_NOT_ALLOWED Status = 109 // Blind writes are not allowed in a namespace transaction. + Status_MALFORMED_NO_WRITES Status = 110 // No write operations in the transaction. + Status_MALFORMED_EMPTY_KEY Status = 111 // Unset key detected. + Status_MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET Status = 112 // Duplicate key in the read-write set. + Status_MALFORMED_MISSING_SIGNATURE Status = 113 // Number of signatures does not match the number of namespaces. + Status_MALFORMED_NAMESPACE_POLICY_INVALID Status = 114 // Invalid namespace policy. + Status_MALFORMED_CONFIG_TX_INVALID Status = 115 // Invalid configuration transaction. ) // Enum value maps for Status. var ( Status_name = map[int32]string{ - 0: "COMMITTED", - 1: "ABORTED_MVCC_CONFLICT", - 2: "ABORTED_DUPLICATE_TXID", - 3: "ABORTED_SIGNATURE_INVALID", - 4: "ABORTED_MISSING_TXID", - 5: "ABORTED_DUPLICATE_NAMESPACE", - 6: "ABORTED_NAMESPACE_POLICY_INVALID", - 7: "ABORTED_NAMESPACE_ID_INVALID", - 9: "ABORTED_BLIND_WRITES_NOT_ALLOWED", - 10: "ABORTED_NO_WRITES", - 11: "ABORTED_UNSUPPORTED_TX_PAYLOAD", - 12: "ABORTED_NIL_KEY", - 13: "ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET", - 14: "ABORTED_EMPTY_NAMESPACES", - 255: "NOT_VALIDATED", + 0: "NOT_VALIDATED", + 1: "COMMITTED", + 2: "ABORTED_SIGNATURE_INVALID", + 3: "ABORTED_MVCC_CONFLICT", + 100: "REJECTED_DUPLICATE_TX_ID", + 101: "MALFORMED_BAD_ENVELOPE", + 102: "MALFORMED_MISSING_TX_ID", + 103: "MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD", + 104: "MALFORMED_BAD_ENVELOPE_PAYLOAD", + 105: "MALFORMED_TX_ID_CONFLICT", + 106: "MALFORMED_EMPTY_NAMESPACES", + 107: "MALFORMED_DUPLICATE_NAMESPACE", + 108: "MALFORMED_NAMESPACE_ID_INVALID", + 109: "MALFORMED_BLIND_WRITES_NOT_ALLOWED", + 110: "MALFORMED_NO_WRITES", + 111: "MALFORMED_EMPTY_KEY", + 112: "MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET", + 113: "MALFORMED_MISSING_SIGNATURE", + 114: "MALFORMED_NAMESPACE_POLICY_INVALID", + 115: "MALFORMED_CONFIG_TX_INVALID", } Status_value = map[string]int32{ - "COMMITTED": 0, - "ABORTED_MVCC_CONFLICT": 1, - "ABORTED_DUPLICATE_TXID": 2, - "ABORTED_SIGNATURE_INVALID": 3, - "ABORTED_MISSING_TXID": 4, - "ABORTED_DUPLICATE_NAMESPACE": 5, - "ABORTED_NAMESPACE_POLICY_INVALID": 6, - "ABORTED_NAMESPACE_ID_INVALID": 7, - "ABORTED_BLIND_WRITES_NOT_ALLOWED": 9, - "ABORTED_NO_WRITES": 10, - "ABORTED_UNSUPPORTED_TX_PAYLOAD": 11, - "ABORTED_NIL_KEY": 12, - "ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET": 13, - "ABORTED_EMPTY_NAMESPACES": 14, - "NOT_VALIDATED": 255, + "NOT_VALIDATED": 0, + "COMMITTED": 1, + "ABORTED_SIGNATURE_INVALID": 2, + "ABORTED_MVCC_CONFLICT": 3, + "REJECTED_DUPLICATE_TX_ID": 100, + "MALFORMED_BAD_ENVELOPE": 101, + "MALFORMED_MISSING_TX_ID": 102, + "MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD": 103, + "MALFORMED_BAD_ENVELOPE_PAYLOAD": 104, + "MALFORMED_TX_ID_CONFLICT": 105, + "MALFORMED_EMPTY_NAMESPACES": 106, + "MALFORMED_DUPLICATE_NAMESPACE": 107, + "MALFORMED_NAMESPACE_ID_INVALID": 108, + "MALFORMED_BLIND_WRITES_NOT_ALLOWED": 109, + "MALFORMED_NO_WRITES": 110, + "MALFORMED_EMPTY_KEY": 111, + "MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET": 112, + "MALFORMED_MISSING_SIGNATURE": 113, + "MALFORMED_NAMESPACE_POLICY_INVALID": 114, + "MALFORMED_CONFIG_TX_INVALID": 115, } ) @@ -720,7 +743,7 @@ func (x *StatusWithHeight) GetCode() Status { if x != nil { return x.Code } - return Status_COMMITTED + return Status_NOT_VALIDATED } func (x *StatusWithHeight) GetBlockNumber() uint64 { @@ -992,39 +1015,51 @@ var file_api_protoblocktx_block_tx_proto_rawDesc = []byte{ 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x2a, 0xc5, 0x03, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, - 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x41, - 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4d, 0x56, 0x43, 0x43, 0x5f, 0x43, 0x4f, 0x4e, 0x46, - 0x4c, 0x49, 0x43, 0x54, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, - 0x44, 0x5f, 0x44, 0x55, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x58, 0x49, 0x44, - 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x49, - 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, - 0x03, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4d, 0x49, 0x53, - 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x58, 0x49, 0x44, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x41, - 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x45, - 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x50, 0x41, 0x43, 0x45, 0x10, 0x05, 0x12, 0x24, 0x0a, 0x20, - 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x50, 0x41, 0x43, - 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, - 0x10, 0x06, 0x12, 0x20, 0x0a, 0x1c, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4e, 0x41, + 0x6e, 0x2a, 0x83, 0x05, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x11, 0x0a, 0x0d, + 0x4e, 0x4f, 0x54, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x1d, + 0x0a, 0x19, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, + 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x02, 0x12, 0x19, 0x0a, + 0x15, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4d, 0x56, 0x43, 0x43, 0x5f, 0x43, 0x4f, + 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x4a, 0x45, + 0x43, 0x54, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x54, + 0x58, 0x5f, 0x49, 0x44, 0x10, 0x64, 0x12, 0x1a, 0x0a, 0x16, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, + 0x4d, 0x45, 0x44, 0x5f, 0x42, 0x41, 0x44, 0x5f, 0x45, 0x4e, 0x56, 0x45, 0x4c, 0x4f, 0x50, 0x45, + 0x10, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, + 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x58, 0x5f, 0x49, 0x44, 0x10, 0x66, 0x12, + 0x2a, 0x0a, 0x26, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x53, + 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x45, 0x4e, 0x56, 0x45, 0x4c, 0x4f, 0x50, + 0x45, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x67, 0x12, 0x22, 0x0a, 0x1e, 0x4d, + 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x42, 0x41, 0x44, 0x5f, 0x45, 0x4e, 0x56, + 0x45, 0x4c, 0x4f, 0x50, 0x45, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x68, 0x12, + 0x1c, 0x0a, 0x18, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x5f, + 0x49, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x10, 0x69, 0x12, 0x1e, 0x0a, + 0x1a, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, + 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x50, 0x41, 0x43, 0x45, 0x53, 0x10, 0x6a, 0x12, 0x21, 0x0a, + 0x1d, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x50, 0x4c, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x50, 0x41, 0x43, 0x45, 0x10, 0x6b, + 0x12, 0x22, 0x0a, 0x1e, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x50, 0x41, 0x43, 0x45, 0x5f, 0x49, 0x44, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, - 0x49, 0x44, 0x10, 0x07, 0x12, 0x24, 0x0a, 0x20, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, - 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x53, 0x5f, 0x4e, 0x4f, 0x54, - 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x41, 0x42, - 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x53, 0x10, - 0x0a, 0x12, 0x22, 0x0a, 0x1e, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x53, - 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x5f, 0x50, 0x41, 0x59, 0x4c, - 0x4f, 0x41, 0x44, 0x10, 0x0b, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, - 0x5f, 0x4e, 0x49, 0x4c, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0c, 0x12, 0x2b, 0x0a, 0x27, 0x41, 0x42, - 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, - 0x4b, 0x45, 0x59, 0x5f, 0x49, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x57, 0x52, 0x49, 0x54, - 0x45, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x0d, 0x12, 0x1c, 0x0a, 0x18, 0x41, 0x42, 0x4f, 0x52, 0x54, - 0x45, 0x44, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x53, 0x50, 0x41, - 0x43, 0x45, 0x53, 0x10, 0x0e, 0x12, 0x12, 0x0a, 0x0d, 0x4e, 0x4f, 0x54, 0x5f, 0x56, 0x41, 0x4c, - 0x49, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0xff, 0x01, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x79, 0x70, 0x65, 0x72, 0x6c, 0x65, 0x64, - 0x67, 0x65, 0x72, 0x2f, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x2d, 0x78, 0x2d, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x49, 0x44, 0x10, 0x6c, 0x12, 0x26, 0x0a, 0x22, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, + 0x44, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x53, 0x5f, 0x4e, + 0x4f, 0x54, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x10, 0x6d, 0x12, 0x17, 0x0a, 0x13, + 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x57, 0x52, 0x49, + 0x54, 0x45, 0x53, 0x10, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, + 0x45, 0x44, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x6f, 0x12, 0x2d, + 0x0a, 0x29, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x50, 0x4c, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x49, 0x4e, 0x5f, 0x52, 0x45, 0x41, + 0x44, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x70, 0x12, 0x1f, 0x0a, + 0x1b, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, + 0x4e, 0x47, 0x5f, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x10, 0x71, 0x12, 0x26, + 0x0a, 0x22, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4e, 0x41, 0x4d, 0x45, + 0x53, 0x50, 0x41, 0x43, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x49, 0x4e, 0x56, + 0x41, 0x4c, 0x49, 0x44, 0x10, 0x72, 0x12, 0x1f, 0x0a, 0x1b, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, + 0x4d, 0x45, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x58, 0x5f, 0x49, 0x4e, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x73, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x79, 0x70, 0x65, 0x72, 0x6c, 0x65, 0x64, 0x67, 0x65, + 0x72, 0x2f, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x2d, 0x78, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x74, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/protoblocktx/block_tx.proto b/api/protoblocktx/block_tx.proto index 14f8c224..7d3a8e40 100644 --- a/api/protoblocktx/block_tx.proto +++ b/api/protoblocktx/block_tx.proto @@ -88,20 +88,36 @@ message ConfigTransaction { uint64 version = 2; } +// Status represents the result of transaction validation. +// Except for NOT_VALIDATED, all statuses are recorded in the ledger. +// Some statuses are also stored in the state database which prevent resubmission of the same transaction ID. enum Status { - COMMITTED = 0; - ABORTED_MVCC_CONFLICT = 1; - ABORTED_DUPLICATE_TXID = 2; - ABORTED_SIGNATURE_INVALID = 3; - ABORTED_MISSING_TXID = 4; - ABORTED_DUPLICATE_NAMESPACE = 5; - ABORTED_NAMESPACE_POLICY_INVALID = 6; - ABORTED_NAMESPACE_ID_INVALID = 7; - ABORTED_BLIND_WRITES_NOT_ALLOWED = 9; - ABORTED_NO_WRITES = 10; - ABORTED_UNSUPPORTED_TX_PAYLOAD = 11; - ABORTED_NIL_KEY = 12; - ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET = 13; - ABORTED_EMPTY_NAMESPACES = 14; - NOT_VALIDATED = 255; + // Should never be persisted or reported. + NOT_VALIDATED = 0; // Default. The transaction has not been validated yet. + + // Stored in the state database. Prevents submitting a transaction with the same ID. + COMMITTED = 1; // Successfully committed and state updated. + ABORTED_SIGNATURE_INVALID = 2; // Signature is invalid according to the namespace policy. + ABORTED_MVCC_CONFLICT = 3; // Read-write set conflict. + + // Cannot be stored in the state database because the TX ID cannot be extracted, + // or the TX ID entry is already occupied. + REJECTED_DUPLICATE_TX_ID = 100; // Transaction with the same ID has already been processed. + MALFORMED_BAD_ENVELOPE = 101; // Cannot unmarshal envelope. + MALFORMED_MISSING_TX_ID = 102; // Envelope is missing transaction ID. + + // Stored in the state database. Prevents submitting a transaction with the same ID. + MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD = 103; // Unsupported envelope payload type. + MALFORMED_BAD_ENVELOPE_PAYLOAD = 104; // Cannot unmarshal envelope's payload. + MALFORMED_TX_ID_CONFLICT = 105; // Envelope's TX ID does not match the payload's TX ID. + MALFORMED_EMPTY_NAMESPACES = 106; // No namespaces provided. + MALFORMED_DUPLICATE_NAMESPACE = 107; // Duplicate namespace detected. + MALFORMED_NAMESPACE_ID_INVALID = 108; // Invalid namespace identifier. + MALFORMED_BLIND_WRITES_NOT_ALLOWED = 109; // Blind writes are not allowed in a namespace transaction. + MALFORMED_NO_WRITES = 110; // No write operations in the transaction. + MALFORMED_EMPTY_KEY = 111; // Unset key detected. + MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET = 112; // Duplicate key in the read-write set. + MALFORMED_MISSING_SIGNATURE = 113; // Number of signatures does not match the number of namespaces. + MALFORMED_NAMESPACE_POLICY_INVALID = 114; // Invalid namespace policy. + MALFORMED_CONFIG_TX_INVALID = 115; // Invalid configuration transaction. } diff --git a/api/protocoordinatorservice/coordinator.pb.go b/api/protocoordinatorservice/coordinator.pb.go index f9f865e9..92389dee 100644 --- a/api/protocoordinatorservice/coordinator.pb.go +++ b/api/protocoordinatorservice/coordinator.pb.go @@ -32,9 +32,10 @@ type Block struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Number uint64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"` // The block number. - Txs []*protoblocktx.Tx `protobuf:"bytes,2,rep,name=txs,proto3" json:"txs,omitempty"` // List of transactions within the block. - TxsNum []uint32 `protobuf:"varint,3,rep,packed,name=txs_num,json=txsNum,proto3" json:"txs_num,omitempty"` // Transaction number within the block generated by the orderer. + Number uint64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"` // The block number. + Txs []*protoblocktx.Tx `protobuf:"bytes,2,rep,name=txs,proto3" json:"txs,omitempty"` // List of transactions within the block. + TxsNum []uint32 `protobuf:"varint,3,rep,packed,name=txs_num,json=txsNum,proto3" json:"txs_num,omitempty"` // Transaction number within the block generated by the orderer. + Rejected []*TxStatusInfo `protobuf:"bytes,4,rep,name=rejected,proto3" json:"rejected,omitempty"` // Rejected transactions. } func (x *Block) Reset() { @@ -90,6 +91,76 @@ func (x *Block) GetTxsNum() []uint32 { return nil } +func (x *Block) GetRejected() []*TxStatusInfo { + if x != nil { + return x.Rejected + } + return nil +} + +type TxStatusInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TxNum uint32 `protobuf:"varint,1,opt,name=tx_num,json=txNum,proto3" json:"tx_num,omitempty"` // Transaction number within the block generated by the orderer. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // Transaction ID. + Status protoblocktx.Status `protobuf:"varint,3,opt,name=status,proto3,enum=protoblocktx.Status" json:"status,omitempty"` // The reject reason. +} + +func (x *TxStatusInfo) Reset() { + *x = TxStatusInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TxStatusInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxStatusInfo) ProtoMessage() {} + +func (x *TxStatusInfo) ProtoReflect() protoreflect.Message { + mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxStatusInfo.ProtoReflect.Descriptor instead. +func (*TxStatusInfo) Descriptor() ([]byte, []int) { + return file_api_protocoordinatorservice_coordinator_proto_rawDescGZIP(), []int{1} +} + +func (x *TxStatusInfo) GetTxNum() uint32 { + if x != nil { + return x.TxNum + } + return 0 +} + +func (x *TxStatusInfo) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TxStatusInfo) GetStatus() protoblocktx.Status { + if x != nil { + return x.Status + } + return protoblocktx.Status(0) +} + type WaitingTransactions struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -101,7 +172,7 @@ type WaitingTransactions struct { func (x *WaitingTransactions) Reset() { *x = WaitingTransactions{} if protoimpl.UnsafeEnabled { - mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[1] + mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -114,7 +185,7 @@ func (x *WaitingTransactions) String() string { func (*WaitingTransactions) ProtoMessage() {} func (x *WaitingTransactions) ProtoReflect() protoreflect.Message { - mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[1] + mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -127,7 +198,7 @@ func (x *WaitingTransactions) ProtoReflect() protoreflect.Message { // Deprecated: Use WaitingTransactions.ProtoReflect.Descriptor instead. func (*WaitingTransactions) Descriptor() ([]byte, []int) { - return file_api_protocoordinatorservice_coordinator_proto_rawDescGZIP(), []int{1} + return file_api_protocoordinatorservice_coordinator_proto_rawDescGZIP(), []int{2} } func (x *WaitingTransactions) GetCount() int32 { @@ -146,7 +217,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { - mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[2] + mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -159,7 +230,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[2] + mi := &file_api_protocoordinatorservice_coordinator_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -172,7 +243,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_api_protocoordinatorservice_coordinator_proto_rawDescGZIP(), []int{2} + return file_api_protocoordinatorservice_coordinator_proto_rawDescGZIP(), []int{3} } var File_api_protocoordinatorservice_coordinator_proto protoreflect.FileDescriptor @@ -184,64 +255,74 @@ var file_api_protocoordinatorservice_coordinator_proto_rawDesc = []byte{ 0x17, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x1f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x74, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5c, 0x0a, 0x05, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x03, 0x74, 0x78, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x54, 0x78, 0x52, 0x03, 0x74, 0x78, 0x73, 0x12, 0x17, - 0x0a, 0x07, 0x74, 0x78, 0x73, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52, - 0x06, 0x74, 0x78, 0x73, 0x4e, 0x75, 0x6d, 0x22, 0x2b, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x69, - 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, - 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0xa3, 0x05, - 0x0a, 0x0b, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x57, 0x0a, - 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, - 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, - 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x28, 0x01, 0x30, 0x01, 0x12, 0x58, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x73, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, - 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0x1e, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, - 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, - 0x12, 0x61, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, - 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, - 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x4c, - 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x78, 0x74, 0x45, 0x78, - 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, - 0x72, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, - 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, - 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x15, - 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x59, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, + 0x5f, 0x74, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f, 0x01, 0x0a, 0x05, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x03, 0x74, + 0x78, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x54, 0x78, 0x52, 0x03, 0x74, 0x78, 0x73, 0x12, + 0x17, 0x0a, 0x07, 0x74, 0x78, 0x73, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, + 0x52, 0x06, 0x74, 0x78, 0x73, 0x4e, 0x75, 0x6d, 0x12, 0x41, 0x0a, 0x08, 0x72, 0x65, 0x6a, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x74, 0x0a, - 0x24, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x46, 0x6f, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, - 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, - 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, - 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x68, 0x79, 0x70, 0x65, 0x72, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x2f, 0x66, 0x61, - 0x62, 0x72, 0x69, 0x63, 0x2d, 0x78, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, - 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x08, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x63, 0x0a, 0x0c, 0x54, + 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x0a, 0x06, 0x74, + 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x74, 0x78, 0x4e, + 0x75, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x2c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, + 0x78, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x2b, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x07, 0x0a, + 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0xa3, 0x05, 0x0a, 0x0b, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x57, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x50, + 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x28, 0x01, 0x30, 0x01, 0x12, + 0x58, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x17, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x61, 0x0a, 0x1b, 0x47, 0x65, 0x74, + 0x4c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x4c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x1a, + 0x47, 0x65, 0x74, 0x4e, 0x65, 0x78, 0x74, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, + 0x6e, 0x66, 0x6f, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x74, 0x78, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x59, 0x0a, 0x14, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, + 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x74, 0x78, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x24, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x4f, 0x66, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, + 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2c, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, + 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x47, 0x5a, 0x45, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x79, 0x70, 0x65, 0x72, + 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x2f, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x2d, 0x78, 0x2d, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -256,39 +337,43 @@ func file_api_protocoordinatorservice_coordinator_proto_rawDescGZIP() []byte { return file_api_protocoordinatorservice_coordinator_proto_rawDescData } -var file_api_protocoordinatorservice_coordinator_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_api_protocoordinatorservice_coordinator_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_api_protocoordinatorservice_coordinator_proto_goTypes = []interface{}{ (*Block)(nil), // 0: protocoordinatorservice.Block - (*WaitingTransactions)(nil), // 1: protocoordinatorservice.WaitingTransactions - (*Empty)(nil), // 2: protocoordinatorservice.Empty - (*protoblocktx.Tx)(nil), // 3: protoblocktx.Tx - (*protoblocktx.BlockInfo)(nil), // 4: protoblocktx.BlockInfo - (*protoblocktx.QueryStatus)(nil), // 5: protoblocktx.QueryStatus - (*protoblocktx.TransactionsStatus)(nil), // 6: protoblocktx.TransactionsStatus - (*protoblocktx.LastCommittedBlock)(nil), // 7: protoblocktx.LastCommittedBlock - (*protoblocktx.ConfigTransaction)(nil), // 8: protoblocktx.ConfigTransaction + (*TxStatusInfo)(nil), // 1: protocoordinatorservice.TxStatusInfo + (*WaitingTransactions)(nil), // 2: protocoordinatorservice.WaitingTransactions + (*Empty)(nil), // 3: protocoordinatorservice.Empty + (*protoblocktx.Tx)(nil), // 4: protoblocktx.Tx + (protoblocktx.Status)(0), // 5: protoblocktx.Status + (*protoblocktx.BlockInfo)(nil), // 6: protoblocktx.BlockInfo + (*protoblocktx.QueryStatus)(nil), // 7: protoblocktx.QueryStatus + (*protoblocktx.TransactionsStatus)(nil), // 8: protoblocktx.TransactionsStatus + (*protoblocktx.LastCommittedBlock)(nil), // 9: protoblocktx.LastCommittedBlock + (*protoblocktx.ConfigTransaction)(nil), // 10: protoblocktx.ConfigTransaction } var file_api_protocoordinatorservice_coordinator_proto_depIdxs = []int32{ - 3, // 0: protocoordinatorservice.Block.txs:type_name -> protoblocktx.Tx - 0, // 1: protocoordinatorservice.Coordinator.BlockProcessing:input_type -> protocoordinatorservice.Block - 4, // 2: protocoordinatorservice.Coordinator.SetLastCommittedBlockNumber:input_type -> protoblocktx.BlockInfo - 2, // 3: protocoordinatorservice.Coordinator.GetLastCommittedBlockNumber:input_type -> protocoordinatorservice.Empty - 2, // 4: protocoordinatorservice.Coordinator.GetNextExpectedBlockNumber:input_type -> protocoordinatorservice.Empty - 5, // 5: protocoordinatorservice.Coordinator.GetTransactionsStatus:input_type -> protoblocktx.QueryStatus - 2, // 6: protocoordinatorservice.Coordinator.GetConfigTransaction:input_type -> protocoordinatorservice.Empty - 2, // 7: protocoordinatorservice.Coordinator.NumberOfWaitingTransactionsForStatus:input_type -> protocoordinatorservice.Empty - 6, // 8: protocoordinatorservice.Coordinator.BlockProcessing:output_type -> protoblocktx.TransactionsStatus - 2, // 9: protocoordinatorservice.Coordinator.SetLastCommittedBlockNumber:output_type -> protocoordinatorservice.Empty - 7, // 10: protocoordinatorservice.Coordinator.GetLastCommittedBlockNumber:output_type -> protoblocktx.LastCommittedBlock - 4, // 11: protocoordinatorservice.Coordinator.GetNextExpectedBlockNumber:output_type -> protoblocktx.BlockInfo - 6, // 12: protocoordinatorservice.Coordinator.GetTransactionsStatus:output_type -> protoblocktx.TransactionsStatus - 8, // 13: protocoordinatorservice.Coordinator.GetConfigTransaction:output_type -> protoblocktx.ConfigTransaction - 1, // 14: protocoordinatorservice.Coordinator.NumberOfWaitingTransactionsForStatus:output_type -> protocoordinatorservice.WaitingTransactions - 8, // [8:15] is the sub-list for method output_type - 1, // [1:8] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 4, // 0: protocoordinatorservice.Block.txs:type_name -> protoblocktx.Tx + 1, // 1: protocoordinatorservice.Block.rejected:type_name -> protocoordinatorservice.TxStatusInfo + 5, // 2: protocoordinatorservice.TxStatusInfo.status:type_name -> protoblocktx.Status + 0, // 3: protocoordinatorservice.Coordinator.BlockProcessing:input_type -> protocoordinatorservice.Block + 6, // 4: protocoordinatorservice.Coordinator.SetLastCommittedBlockNumber:input_type -> protoblocktx.BlockInfo + 3, // 5: protocoordinatorservice.Coordinator.GetLastCommittedBlockNumber:input_type -> protocoordinatorservice.Empty + 3, // 6: protocoordinatorservice.Coordinator.GetNextExpectedBlockNumber:input_type -> protocoordinatorservice.Empty + 7, // 7: protocoordinatorservice.Coordinator.GetTransactionsStatus:input_type -> protoblocktx.QueryStatus + 3, // 8: protocoordinatorservice.Coordinator.GetConfigTransaction:input_type -> protocoordinatorservice.Empty + 3, // 9: protocoordinatorservice.Coordinator.NumberOfWaitingTransactionsForStatus:input_type -> protocoordinatorservice.Empty + 8, // 10: protocoordinatorservice.Coordinator.BlockProcessing:output_type -> protoblocktx.TransactionsStatus + 3, // 11: protocoordinatorservice.Coordinator.SetLastCommittedBlockNumber:output_type -> protocoordinatorservice.Empty + 9, // 12: protocoordinatorservice.Coordinator.GetLastCommittedBlockNumber:output_type -> protoblocktx.LastCommittedBlock + 6, // 13: protocoordinatorservice.Coordinator.GetNextExpectedBlockNumber:output_type -> protoblocktx.BlockInfo + 8, // 14: protocoordinatorservice.Coordinator.GetTransactionsStatus:output_type -> protoblocktx.TransactionsStatus + 10, // 15: protocoordinatorservice.Coordinator.GetConfigTransaction:output_type -> protoblocktx.ConfigTransaction + 2, // 16: protocoordinatorservice.Coordinator.NumberOfWaitingTransactionsForStatus:output_type -> protocoordinatorservice.WaitingTransactions + 10, // [10:17] is the sub-list for method output_type + 3, // [3:10] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_api_protocoordinatorservice_coordinator_proto_init() } @@ -310,7 +395,7 @@ func file_api_protocoordinatorservice_coordinator_proto_init() { } } file_api_protocoordinatorservice_coordinator_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WaitingTransactions); i { + switch v := v.(*TxStatusInfo); i { case 0: return &v.state case 1: @@ -322,6 +407,18 @@ func file_api_protocoordinatorservice_coordinator_proto_init() { } } file_api_protocoordinatorservice_coordinator_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WaitingTransactions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_protocoordinatorservice_coordinator_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Empty); i { case 0: return &v.state @@ -340,7 +437,7 @@ func file_api_protocoordinatorservice_coordinator_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_protocoordinatorservice_coordinator_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, diff --git a/api/protocoordinatorservice/coordinator.proto b/api/protocoordinatorservice/coordinator.proto index dc237945..acd27db0 100644 --- a/api/protocoordinatorservice/coordinator.proto +++ b/api/protocoordinatorservice/coordinator.proto @@ -24,9 +24,16 @@ service Coordinator { // A committer's representation of a block in the blockchain. message Block { - uint64 number = 1; // The block number. - repeated protoblocktx.Tx txs = 2; // List of transactions within the block. - repeated uint32 txs_num = 3; // Transaction number within the block generated by the orderer. + uint64 number = 1; // The block number. + repeated protoblocktx.Tx txs = 2; // List of transactions within the block. + repeated uint32 txs_num = 3; // Transaction number within the block generated by the orderer. + repeated TxStatusInfo rejected = 4; // Rejected transactions. +} + +message TxStatusInfo { + uint32 tx_num = 1; // Transaction number within the block generated by the orderer. + string id = 2; // Transaction ID. + protoblocktx.Status status = 3; // The reject reason. } message WaitingTransactions { diff --git a/integration/runner/runtime.go b/integration/runner/runtime.go index b1798726..da2f18be 100644 --- a/integration/runner/runtime.go +++ b/integration/runner/runtime.go @@ -27,6 +27,7 @@ import ( "github.com/hyperledger/fabric-x-committer/api/types" "github.com/hyperledger/fabric-x-committer/cmd/config" "github.com/hyperledger/fabric-x-committer/loadgen/workload" + "github.com/hyperledger/fabric-x-committer/service/sidecar" "github.com/hyperledger/fabric-x-committer/service/sidecar/sidecarclient" "github.com/hyperledger/fabric-x-committer/service/vc" "github.com/hyperledger/fabric-x-committer/service/vc/dbtest" @@ -533,27 +534,32 @@ func (c *CommitterRuntime) ValidateExpectedResultsInCommittedBlock(t *testing.T, c.ensureLastCommittedBlockNumber(t, blk.Header.Number) + var persistedTxIDs []string + persistedTxIDsStatus := make(map[string]*protoblocktx.StatusWithHeight) nonDuplicateTxIDsStatus := make(map[string]*protoblocktx.StatusWithHeight) - var nonDupTxIDs []string duplicateTxIDsStatus := make(map[string]*protoblocktx.StatusWithHeight) for i, tID := range expected.TxIDs { s := types.CreateStatusWithHeight(expected.Statuses[i], blk.Header.Number, i) - if expected.Statuses[i] != protoblocktx.Status_ABORTED_DUPLICATE_TXID { + if s.Code == protoblocktx.Status_REJECTED_DUPLICATE_TX_ID { + duplicateTxIDsStatus[tID] = s + } else { nonDuplicateTxIDsStatus[tID] = s - nonDupTxIDs = append(nonDupTxIDs, tID) - continue } - duplicateTxIDsStatus[tID] = s + + if sidecar.IsStatusStoredInDB(s.Code) { + persistedTxIDsStatus[tID] = s + persistedTxIDs = append(persistedTxIDs, tID) + } } - c.dbEnv.StatusExistsForNonDuplicateTxID(t, nonDuplicateTxIDsStatus) + c.dbEnv.StatusExistsForNonDuplicateTxID(t, persistedTxIDsStatus) // For the duplicate txID, neither the status nor the height would match the entry in the // transaction status table. c.dbEnv.StatusExistsWithDifferentHeightForDuplicateTxID(t, duplicateTxIDsStatus) ctx, cancel := context.WithTimeout(t.Context(), 1*time.Minute) defer cancel() - test.EnsurePersistedTxStatus(ctx, t, c.CoordinatorClient, nonDupTxIDs, nonDuplicateTxIDsStatus) + test.EnsurePersistedTxStatus(ctx, t, c.CoordinatorClient, persistedTxIDs, persistedTxIDsStatus) } // CountStatus returns the number of transactions with a given tx status. diff --git a/integration/test/badly_formed_txs_test.go b/integration/test/badly_formed_txs_test.go index 3952ffc1..4e8578cc 100644 --- a/integration/test/badly_formed_txs_test.go +++ b/integration/test/badly_formed_txs_test.go @@ -14,30 +14,38 @@ import ( "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/integration/runner" - "github.com/hyperledger/fabric-x-committer/service/verifier" - "github.com/hyperledger/fabric-x-committer/utils/signature" + "github.com/hyperledger/fabric-x-committer/service/sidecar" ) func TestBadlyFormedTxs(t *testing.T) { t.Parallel() gomega.RegisterTestingT(t) + + testSize := len(sidecar.MalformedTxTestCases) c := runner.NewRuntime(t, &runner.Config{ NumVerifiers: 2, NumVCService: 2, - BlockSize: 5, + BlockSize: uint64(testSize), BlockTimeout: 1 * time.Second, }) c.Start(t, runner.FullTxPath) + c.CreateNamespacesAndCommit(t, "1") - c.CreateCryptoForNs(t, "1", signature.Ecdsa) - - //nolint:paralleltest - for _, tt := range verifier.BadTxFormatTestCases { - t.Run(tt.Tx.Id, func(t *testing.T) { - c.SendTransactionsToOrderer(t, []*protoblocktx.Tx{tt.Tx}) - c.ValidateExpectedResultsInCommittedBlock(t, &runner.ExpectedStatusInBlock{ - TxIDs: []string{tt.Tx.Id}, Statuses: []protoblocktx.Status{tt.ExpectedStatus}, - }) - }) + txs := make([]*protoblocktx.Tx, testSize) + expected := &runner.ExpectedStatusInBlock{ + TxIDs: make([]string, testSize), + Statuses: make([]protoblocktx.Status, testSize), + } + for i, tt := range sidecar.MalformedTxTestCases { + txs[i] = tt.Tx + expected.TxIDs[i] = tt.Tx.Id + if tt.ExpectedStatus != protoblocktx.Status_NOT_VALIDATED { + expected.Statuses[i] = tt.ExpectedStatus + } else { + // We use invalid signature as the default because the TXs are not properly signed. + expected.Statuses[i] = protoblocktx.Status_ABORTED_SIGNATURE_INVALID + } } + c.SendTransactionsToOrderer(t, txs) + c.ValidateExpectedResultsInCommittedBlock(t, expected) } diff --git a/integration/test/service_crash_test.go b/integration/test/service_crash_test.go index 9b0b2a6c..54e4462b 100644 --- a/integration/test/service_crash_test.go +++ b/integration/test/service_crash_test.go @@ -99,7 +99,7 @@ func TestCrashWhenIdle(t *testing.T) { expectedResults = &runner.ExpectedStatusInBlock{ TxIDs: []string{"tx1"}, Statuses: []protoblocktx.Status{ - protoblocktx.Status_ABORTED_DUPLICATE_TXID, + protoblocktx.Status_REJECTED_DUPLICATE_TX_ID, }, } c.ValidateExpectedResultsInCommittedBlock(t, expectedResults) diff --git a/loadgen/adapters/sidecar_receiver.go b/loadgen/adapters/sidecar_receiver.go index 9d8449f2..ee95d7fa 100644 --- a/loadgen/adapters/sidecar_receiver.go +++ b/loadgen/adapters/sidecar_receiver.go @@ -18,6 +18,7 @@ import ( "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/loadgen/metrics" "github.com/hyperledger/fabric-x-committer/service/sidecar/sidecarclient" + "github.com/hyperledger/fabric-x-committer/utils" "github.com/hyperledger/fabric-x-committer/utils/broadcastdeliver" "github.com/hyperledger/fabric-x-committer/utils/channel" "github.com/hyperledger/fabric-x-committer/utils/connection" @@ -149,10 +150,7 @@ func mapToStatusBatch(block *common.Block) []metrics.TxStatus { // recapStatusCodes recaps of the status codes of a block. func recapStatusCodes(statusCodes []byte) string { - codes := make(map[byte]uint64) - for _, code := range statusCodes { - codes[code]++ - } + codes := utils.CountAppearances(statusCodes) items := make([]string, 0, len(codes)) for code, count := range codes { items = append( diff --git a/loadgen/workload/stream_test.go b/loadgen/workload/stream_test.go index 7edb3660..b49b086e 100644 --- a/loadgen/workload/stream_test.go +++ b/loadgen/workload/stream_test.go @@ -365,7 +365,7 @@ func TestReadWriteWithValue(t *testing.T) { func TestGenTxWithRateLimit(t *testing.T) { t.Parallel() p := DefaultProfile(8) - limit := 100 + limit := 1000 expectedSeconds := 5 producedTotal := expectedSeconds * limit diff --git a/mock/coordinator.go b/mock/coordinator.go index 97823e94..77f4c76d 100644 --- a/mock/coordinator.go +++ b/mock/coordinator.go @@ -195,15 +195,22 @@ func (c *Coordinator) sendTxsValidationStatus( } } - txs := scBlock.Txs - txNums := scBlock.TxsNum - for len(txs) > 0 { - chunkSize := rand.Intn(len(txs)) + 1 - if err := c.sendTxsStatusChunk(stream, txs[:chunkSize], txNums[:chunkSize], scBlock.Number); err != nil { + info := scBlock.Rejected + for i, tx := range scBlock.Txs { + info = append(info, &protocoordinatorservice.TxStatusInfo{ + TxNum: scBlock.TxsNum[i], + Id: tx.Id, + Status: protoblocktx.Status_COMMITTED, + }) + } + rand.Shuffle(len(info), func(i, j int) { info[i], info[j] = info[j], info[i] }) + + for len(info) > 0 { + chunkSize := rand.Intn(len(info)) + 1 + if err := c.sendTxsStatusChunk(stream, info[:chunkSize], scBlock.Number); err != nil { return errors.Wrap(err, "submit chunk failed") } - txs = txs[chunkSize:] - txNums = txNums[chunkSize:] + info = info[chunkSize:] } } return errors.Wrap(ctx.Err(), "context cancelled") @@ -211,18 +218,18 @@ func (c *Coordinator) sendTxsValidationStatus( func (c *Coordinator) sendTxsStatusChunk( stream protocoordinatorservice.Coordinator_BlockProcessingServer, - txs []*protoblocktx.Tx, - txNum []uint32, blockNum uint64, + txs []*protocoordinatorservice.TxStatusInfo, + blockNum uint64, ) error { b := &protoblocktx.TransactionsStatus{ Status: make(map[string]*protoblocktx.StatusWithHeight, len(txs)), } c.txsStatusMu.Lock() defer c.txsStatusMu.Unlock() - for i, tx := range txs { - s := types.CreateStatusWithHeight(protoblocktx.Status_COMMITTED, blockNum, int(txNum[i])) - b.Status[tx.Id] = s - c.txsStatus.addIfNotExist(tx.Id, s) + for _, info := range txs { + s := types.CreateStatusWithHeight(info.Status, blockNum, int(info.TxNum)) + b.Status[info.Id] = s + c.txsStatus.addIfNotExist(info.Id, s) } if err := stream.Send(b); err != nil { return errors.Wrap(err, "failed to send status") diff --git a/service/coordinator/coordinator.go b/service/coordinator/coordinator.go index 3c6498bc..622e1000 100644 --- a/service/coordinator/coordinator.go +++ b/service/coordinator/coordinator.go @@ -9,6 +9,8 @@ package coordinator import ( "context" "fmt" + "maps" + "slices" "sync" "sync/atomic" "time" @@ -21,6 +23,7 @@ import ( "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/api/protocoordinatorservice" + "github.com/hyperledger/fabric-x-committer/api/protovcservice" "github.com/hyperledger/fabric-x-committer/service/coordinator/dependencygraph" "github.com/hyperledger/fabric-x-committer/utils" "github.com/hyperledger/fabric-x-committer/utils/channel" @@ -366,6 +369,7 @@ func (c *Service) receiveAndProcessBlock( stream protocoordinatorservice.Coordinator_BlockProcessingServer, ) error { txsBatchForDependencyGraph := channel.NewWriter(ctx, c.queues.coordinatorToDepGraphTxs) + txBatchForVcService := channel.NewWriter(ctx, c.queues.sigVerifierToVCServiceValidatedTxs) for ctx.Err() == nil { blk, err := stream.Recv() @@ -389,25 +393,39 @@ func (c *Service) receiveAndProcessBlock( } logger.Debugf("Coordinator received block [%d] with %d TXs", blk.Number, len(blk.Txs)) - promutil.AddToCounter(c.metrics.transactionReceivedTotal, len(blk.Txs)) + promutil.AddToCounter(c.metrics.transactionReceivedTotal, len(blk.Txs)+len(blk.Rejected)) c.numWaitingTxsForStatus.Add(int32(len(blk.Txs))) //nolint:gosec - if len(blk.Txs) == 0 { - continue - } - - // TODO: make it configurable. - chunkSizeForDepGraph := min(c.config.DependencyGraphConfig.WaitingTxsLimit, 500) - for i := 0; i < len(blk.Txs); i += chunkSizeForDepGraph { - end := min(i+chunkSizeForDepGraph, len(blk.Txs)) - txsBatchForDependencyGraph.Write( - &dependencygraph.TransactionBatch{ + if len(blk.Txs) > 0 { + // TODO: make it configurable. + chunkSizeForDepGraph := min(c.config.DependencyGraphConfig.WaitingTxsLimit, 500) + for i := 0; i < len(blk.Txs); i += chunkSizeForDepGraph { + end := min(i+chunkSizeForDepGraph, len(blk.Txs)) + txsBatchForDependencyGraph.Write(&dependencygraph.TransactionBatch{ ID: c.txBatchIDToDepGraph, BlockNumber: blk.Number, Txs: blk.Txs[i:end], TxsNum: blk.TxsNum[i:end], }) - c.txBatchIDToDepGraph++ + c.txBatchIDToDepGraph++ + } + } + + if len(blk.Rejected) > 0 { + rejected := make(dependencygraph.TxNodeBatch, len(blk.Rejected)) + for i, tx := range blk.Rejected { + rejected[i] = &dependencygraph.TransactionNode{ + Tx: &protovcservice.Transaction{ + ID: tx.Id, + PrelimInvalidTxStatus: &protovcservice.InvalidTxStatus{ + Code: tx.Status, + }, + BlockNumber: blk.Number, + TxNum: tx.TxNum, + }, + } + } + txBatchForVcService.Write(rejected) } } @@ -432,19 +450,10 @@ func (c *Service) sendTxStatus( logger.Debugf("Batch with %d TX statuses forwarded to output stream.", len(txStatus.Status)) - // TODO: introduce metrics to record all sent statuses. Issue #436. + statusCount := utils.CountAppearances(slices.Collect(maps.Values(txStatus.Status))) m := c.metrics - for _, status := range txStatus.Status { - switch status.Code { - case protoblocktx.Status_COMMITTED: - promutil.AddToCounter(m.transactionCommittedStatusSentTotal, 1) - case protoblocktx.Status_ABORTED_MVCC_CONFLICT: - promutil.AddToCounter(m.transactionMVCCConflictStatusSentTotal, 1) - case protoblocktx.Status_ABORTED_DUPLICATE_TXID: - promutil.AddToCounter(m.transactionDuplicateTxStatusSentTotal, 1) - case protoblocktx.Status_ABORTED_SIGNATURE_INVALID: - promutil.AddToCounter(m.transactionInvalidSignatureStatusSentTotal, 1) - } + for code, count := range statusCount { + promutil.AddToCounter(m.transactionCommittedTotal.WithLabelValues(code.Code.String()), count) } } } diff --git a/service/coordinator/coordinator_test.go b/service/coordinator/coordinator_test.go index 317373f0..78d5717b 100644 --- a/service/coordinator/coordinator_test.go +++ b/service/coordinator/coordinator_test.go @@ -276,7 +276,56 @@ func TestCoordinatorServiceValidTx(t *testing.T) { "tx1": {Code: protoblocktx.Status_COMMITTED, BlockNumber: 1}, }, nil) - test.RequireIntMetricValue(t, preMetricsValue+1, env.coordinator.metrics.transactionCommittedStatusSentTotal) + test.RequireIntMetricValue(t, preMetricsValue+1, env.coordinator.metrics.transactionCommittedTotal.WithLabelValues( + protoblocktx.Status_COMMITTED.String(), + )) + + _, err = env.coordinator.SetLastCommittedBlockNumber(ctx, &protoblocktx.BlockInfo{Number: 1}) + require.NoError(t, err) + + lastCommittedBlock, err := env.coordinator.GetLastCommittedBlockNumber(ctx, nil) + require.NoError(t, err) + require.NotNil(t, lastCommittedBlock.Block) + require.Equal(t, uint64(1), lastCommittedBlock.Block.Number) +} + +func TestCoordinatorServiceRejectedTx(t *testing.T) { + t.Parallel() + env := newCoordinatorTestEnv(t, &testConfig{numSigService: 2, numVcService: 2, mockVcService: false}) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Minute) + t.Cleanup(cancel) + env.start(ctx, t) + + env.createNamespaces(t, 0, "1") + + preMetricsValue := test.GetIntMetricValue(t, env.coordinator.metrics.transactionReceivedTotal) + + err := env.csStream.Send(&protocoordinatorservice.Block{ + Number: 1, + Rejected: []*protocoordinatorservice.TxStatusInfo{ + { + TxNum: 0, + Id: "rejected", + Status: protoblocktx.Status_MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD, + }, + }, + }) + require.NoError(t, err) + test.EventuallyIntMetric( + t, preMetricsValue+1, env.coordinator.metrics.transactionReceivedTotal, + 1*time.Second, 100*time.Millisecond, + ) + + env.requireStatus(ctx, t, map[string]*protoblocktx.StatusWithHeight{ + "rejected": {Code: protoblocktx.Status_MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD, BlockNumber: 1}, + }, nil) + + test.RequireIntMetricValue(t, 1, env.coordinator.metrics.transactionCommittedTotal.WithLabelValues( + protoblocktx.Status_MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD.String(), + )) + test.RequireIntMetricValue(t, preMetricsValue, env.coordinator.metrics.transactionCommittedTotal.WithLabelValues( + protoblocktx.Status_COMMITTED.String(), + )) _, err = env.coordinator.SetLastCommittedBlockNumber(ctx, &protoblocktx.BlockInfo{Number: 1}) require.NoError(t, err) @@ -417,7 +466,9 @@ func TestCoordinatorServiceDependentOrderedTxs(t *testing.T) { for txID, txStatus := range status { require.Equal(t, protoblocktx.Status_COMMITTED, txStatus.Code, txID) } - test.RequireIntMetricValue(t, expectedReceived, env.coordinator.metrics.transactionCommittedStatusSentTotal) + test.RequireIntMetricValue(t, expectedReceived, env.coordinator.metrics.transactionCommittedTotal.WithLabelValues( + protoblocktx.Status_COMMITTED.String(), + )) res := env.dbEnv.FetchKeys(t, utNsID, [][]byte{mainKey, subKey}) mainValue, ok := res[string(mainKey)] @@ -583,7 +634,7 @@ func TestCoordinatorRecovery(t *testing.T) { expectedTxStatus := map[string]*protoblocktx.StatusWithHeight{ "tx2": types.CreateStatusWithHeight(protoblocktx.Status_COMMITTED, 2, 0), "mvcc conflict": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_MVCC_CONFLICT, 2, 2), - "tx1": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_DUPLICATE_TXID, 2, 5), + "tx1": types.CreateStatusWithHeight(protoblocktx.Status_REJECTED_DUPLICATE_TX_ID, 2, 5), } env.requireStatus(ctx, t, expectedTxStatus, map[string]*protoblocktx.StatusWithHeight{ "tx1": types.CreateStatusWithHeight(protoblocktx.Status_COMMITTED, 1, 0), @@ -722,7 +773,7 @@ func TestCoordinatorRecovery(t *testing.T) { "tx3": types.CreateStatusWithHeight(protoblocktx.Status_COMMITTED, 2, 1), "mvcc conflict": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_MVCC_CONFLICT, 2, 2), "duplicate namespace": types.CreateStatusWithHeight(protoblocktx.Status_COMMITTED, 2, 4), - "tx1": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_DUPLICATE_TXID, 2, 5), + "tx1": types.CreateStatusWithHeight(protoblocktx.Status_REJECTED_DUPLICATE_TX_ID, 2, 5), }, map[string]*protoblocktx.StatusWithHeight{ "tx1": types.CreateStatusWithHeight(protoblocktx.Status_COMMITTED, 1, 0), }) @@ -852,7 +903,9 @@ func TestChunkSizeSentForDepGraph(t *testing.T) { } require.Equal(t, expectedTxsStatus, actualTxsStatus) - test.RequireIntMetricValue(t, txPerBlock, env.coordinator.metrics.transactionCommittedStatusSentTotal) + test.RequireIntMetricValue(t, txPerBlock, env.coordinator.metrics.transactionCommittedTotal.WithLabelValues( + protoblocktx.Status_COMMITTED.String(), + )) } func TestWaitingTxsCount(t *testing.T) { @@ -904,7 +957,9 @@ func TestWaitingTxsCount(t *testing.T) { } require.Equal(t, expectedTxsStatus, actualTxsStatus) - test.RequireIntMetricValue(t, txPerBlock, env.coordinator.metrics.transactionCommittedStatusSentTotal) + test.RequireIntMetricValue(t, txPerBlock, env.coordinator.metrics.transactionCommittedTotal.WithLabelValues( + protoblocktx.Status_COMMITTED.String(), + )) env.streamCancel() require.Eventually(t, func() bool { diff --git a/service/coordinator/dependencygraph/dependency_detector.go b/service/coordinator/dependencygraph/dependency_detector.go index ab6535ee..a724712f 100644 --- a/service/coordinator/dependencygraph/dependency_detector.go +++ b/service/coordinator/dependencygraph/dependency_detector.go @@ -105,6 +105,9 @@ func (d *dependencyDetector) mergeWaitingTx(depDetector *dependencyDetector) { // This method is not thread-safe. func (d *dependencyDetector) removeWaitingTx(txsNode TxNodeBatch) { for _, txNode := range txsNode { + if txNode.rwKeys == nil { + return + } d.readOnlyKeyToWaitingTxs.remove(txNode.rwKeys.readsOnly, txNode) d.writeOnlyKeyToWaitingTxs.remove(txNode.rwKeys.writesOnly, txNode) d.readWriteKeyToWaitingTxs.remove(txNode.rwKeys.readsAndWrites, txNode) diff --git a/service/coordinator/dependencygraph/transaction_node.go b/service/coordinator/dependencygraph/transaction_node.go index 2a1175f6..f9df861d 100644 --- a/service/coordinator/dependencygraph/transaction_node.go +++ b/service/coordinator/dependencygraph/transaction_node.go @@ -20,7 +20,9 @@ import ( type ( // TransactionNode is a node in the dependency graph. TransactionNode struct { - Tx *protovcservice.Transaction + Tx *protovcservice.Transaction + Signatures [][]byte + // dependsOnTxs is a set of transactions that this transaction depends on. // A transaction is eligible for validation once all the transactions // in dependsOnTxs set are validated. @@ -50,7 +52,6 @@ type ( // from each dependent transaction. dependentTxs utils.SyncMap[*TransactionNode, any] rwKeys *readWriteKeys - Signatures [][]byte // Used by the simple dependency graph. waitForKeysCount uint64 @@ -76,8 +77,8 @@ func newTransactionNode(blockNum uint64, txNum uint32, tx *protoblocktx.Tx) *Tra BlockNumber: blockNum, TxNum: txNum, }, - rwKeys: readAndWriteKeys(tx.Namespaces), Signatures: tx.Signatures, + rwKeys: readAndWriteKeys(tx.Namespaces), } } diff --git a/service/coordinator/metrics.go b/service/coordinator/metrics.go index c4fc37c8..67ffa20c 100644 --- a/service/coordinator/metrics.go +++ b/service/coordinator/metrics.go @@ -16,11 +16,8 @@ type perfMetrics struct { *monitoring.Provider // received and processed transactions - transactionReceivedTotal prometheus.Counter - transactionCommittedStatusSentTotal prometheus.Counter - transactionMVCCConflictStatusSentTotal prometheus.Counter - transactionInvalidSignatureStatusSentTotal prometheus.Counter - transactionDuplicateTxStatusSentTotal prometheus.Counter + transactionReceivedTotal prometheus.Counter + transactionCommittedTotal *prometheus.CounterVec // queue sizes sigverifierInputTxBatchQueueSize prometheus.Gauge @@ -50,33 +47,12 @@ func newPerformanceMetrics() *perfMetrics { Name: "received_transaction_total", Help: "Total number of transactions received by the coordinator service from the client.", }), - transactionCommittedStatusSentTotal: p.NewCounter(prometheus.CounterOpts{ + transactionCommittedTotal: p.NewCounterVec(prometheus.CounterOpts{ Namespace: "coordinator", Subsystem: "grpc", - Name: "sent_transaction_committed_status_total", + Name: "committed_transaction_total", Help: "Total number of transactions committed status sent by the coordinator service to the client.", - }), - transactionMVCCConflictStatusSentTotal: p.NewCounter(prometheus.CounterOpts{ - Namespace: "coordinator", - Subsystem: "grpc", - Name: "sent_transaction_mvcc_conflict_status_total", - Help: "Total number of transactions mvcc conflict status sent by" + - " the coordinator service to the client.", - }), - transactionInvalidSignatureStatusSentTotal: p.NewCounter(prometheus.CounterOpts{ - Namespace: "coordinator", - Subsystem: "grpc", - Name: "sent_transaction_invalid_signature_status_total", - Help: "Total number of transactions invalid signature status sent by" + - " the coordinator service to the client.", - }), - transactionDuplicateTxStatusSentTotal: p.NewCounter(prometheus.CounterOpts{ - Namespace: "coordinator", - Subsystem: "grpc", - Name: "sent_transaction_duplicate_tx_status_total", - Help: "Total number of transactions duplicate tx status sent by the" + - " coordinator service to the client.", - }), + }, []string{"status"}), sigverifierInputTxBatchQueueSize: p.NewGauge(prometheus.GaugeOpts{ Namespace: "coordinator", Subsystem: "sigverifier", diff --git a/service/sidecar/ledger_service_test.go b/service/sidecar/ledger_service_test.go index 1a1db1ab..b27bdd50 100644 --- a/service/sidecar/ledger_service_test.go +++ b/service/sidecar/ledger_service_test.go @@ -14,6 +14,7 @@ import ( "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/hyperledger/fabric/protoutil" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -50,7 +51,7 @@ func TestLedgerService(t *testing.T) { // NOTE: if we start the deliver client without even the 0'th block, it would // result in an error. This is due to the iterator implementation in the // fabric ledger. - blk0 := createBlockForTest(0, nil, [3]string{"0", "1", "2"}) + blk0 := createBlockForTest(t, 0, nil, [3]string{"0", "1", "2"}) valid := byte(protoblocktx.Status_COMMITTED) metadata := &common.BlockMetadata{ Metadata: [][]byte{nil, nil, {valid, valid, valid}}, @@ -68,9 +69,9 @@ func TestLedgerService(t *testing.T) { Endpoint: &config.Endpoint, }, 0) - blk1 := createBlockForTest(1, protoutil.BlockHeaderHash(blk0.Header), [3]string{"3", "4", "5"}) + blk1 := createBlockForTest(t, 1, protoutil.BlockHeaderHash(blk0.Header), [3]string{"3", "4", "5"}) blk1.Metadata = metadata - blk2 := createBlockForTest(2, protoutil.BlockHeaderHash(blk1.Header), [3]string{"6", "7", "8"}) + blk2 := createBlockForTest(t, 2, protoutil.BlockHeaderHash(blk1.Header), [3]string{"6", "7", "8"}) blk2.Metadata = metadata inputBlock <- blk1 inputBlock <- blk2 @@ -91,7 +92,7 @@ func TestLedgerService(t *testing.T) { // ensureAtLeastHeight checks if the ledger is at or above the specified height. func ensureAtLeastHeight(t *testing.T, s *LedgerService, height uint64) { t.Helper() - require.Eventually(t, func() bool { - return s.GetBlockHeight() >= height + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.GreaterOrEqual(ct, s.GetBlockHeight(), height) }, 15*time.Second, 10*time.Millisecond) } diff --git a/service/sidecar/mapping.go b/service/sidecar/mapping.go index 26fe70f3..93131a9f 100644 --- a/service/sidecar/mapping.go +++ b/service/sidecar/mapping.go @@ -8,31 +8,51 @@ package sidecar import ( "fmt" + "unicode/utf8" + "github.com/cockroachdb/errors" "github.com/hyperledger/fabric-protos-go-apiv2/common" "go.uber.org/zap/zapcore" "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/api/protocoordinatorservice" "github.com/hyperledger/fabric-x-committer/api/types" + "github.com/hyperledger/fabric-x-committer/service/verifier/policy" + "github.com/hyperledger/fabric-x-committer/utils" "github.com/hyperledger/fabric-x-committer/utils/serialization" ) type ( - validationCode = byte scBlockWithStatus struct { block *protocoordinatorservice.Block withStatus *blockWithStatus isConfig bool + // txIDToHeight is a reference to the relay map. + txIDToHeight *utils.SyncMap[string, types.Height] + } + + blockWithStatus struct { + block *common.Block + txStatus []protoblocktx.Status + pendingCount int } ) const ( - excludedStatus = validationCode(protoblocktx.Status_ABORTED_UNSUPPORTED_TX_PAYLOAD) - notYetValidated = validationCode(protoblocktx.Status_NOT_VALIDATED) + statusNotYetValidated = protoblocktx.Status_NOT_VALIDATED + statusIdx = int(common.BlockMetadataIndex_TRANSACTIONS_FILTER) ) -func mapBlock(block *common.Block) *scBlockWithStatus { +func mapBlock(block *common.Block, txIDToHeight *utils.SyncMap[string, types.Height]) (*scBlockWithStatus, error) { + // Prepare block's metadata. + if block.Metadata == nil { + block.Metadata = &common.BlockMetadata{} + } + metadataSize := len(block.Metadata.Metadata) + if metadataSize <= statusIdx { + block.Metadata.Metadata = append(block.Metadata.Metadata, make([][]byte, statusIdx+1-metadataSize)...) + } + if block.Data == nil { logger.Warnf("Received a block [%d] without data", block.Header.Number) return &scBlockWithStatus{ @@ -40,66 +60,148 @@ func mapBlock(block *common.Block) *scBlockWithStatus { Number: block.Header.Number, }, withStatus: &blockWithStatus{ - block: block, - txIDToTxIndex: make(map[string]int), + block: block, }, - } + txIDToHeight: txIDToHeight, + }, nil } + txCount := len(block.Data.Data) mappedBlock := &scBlockWithStatus{ block: &protocoordinatorservice.Block{ - Number: block.Header.Number, - Txs: make([]*protoblocktx.Tx, 0, txCount), - TxsNum: make([]uint32, 0, txCount), + Number: block.Header.Number, + Txs: make([]*protoblocktx.Tx, 0, txCount), + TxsNum: make([]uint32, 0, txCount), + Rejected: make([]*protocoordinatorservice.TxStatusInfo, 0, txCount), }, withStatus: &blockWithStatus{ - block: block, - txStatus: newValidationCodes(txCount), - txIDToTxIndex: make(map[string]int, txCount), - pendingCount: txCount, + block: block, + txStatus: make([]protoblocktx.Status, txCount), + pendingCount: txCount, }, + txIDToHeight: txIDToHeight, } - - for txNum, msg := range block.Data.Data { - logger.Debugf("Mapping transaction [blk,tx] = [%d,%d]", block.Header.Number, txNum) - data, hdr, err := serialization.UnwrapEnvelope(msg) + for msgIndex, msg := range block.Data.Data { + logger.Debugf("Mapping transaction [blk,tx] = [%d,%d]", mappedBlock.block.Number, msgIndex) + err := mappedBlock.mapMessage(uint32(msgIndex), msg) //nolint:gosec // int -> uint32. if err != nil { - mappedBlock.excludeTx(txNum, hdr, err.Error()) - continue + // This can never occur unless there is a bug in the relay. + return nil, err } + } + return mappedBlock, nil +} - switch hdr.Type { - case int32(common.HeaderType_CONFIG): - mappedBlock.appendTx(txNum, hdr, configTx(hdr.TxId, msg)) - mappedBlock.isConfig = true - case int32(common.HeaderType_MESSAGE): - tx, err := serialization.UnmarshalTx(data) - if err != nil { - mappedBlock.excludeTx(txNum, hdr, err.Error()) - continue - } - if isApplicationConfigTx(tx) { - mappedBlock.excludeTx(txNum, hdr, "application's config tx") - continue - } - mappedBlock.appendTx(txNum, hdr, tx) - default: - mappedBlock.excludeTx(txNum, hdr, "unsupported message type") +func (b *scBlockWithStatus) mapMessage(msgIndex uint32, msg []byte) error { + data, hdr, envErr := serialization.UnwrapEnvelope(msg) + if envErr != nil { + return b.rejectNonDBStatusTx(msgIndex, hdr, protoblocktx.Status_MALFORMED_BAD_ENVELOPE, envErr.Error()) + } + if hdr.TxId == "" { + return b.rejectNonDBStatusTx(msgIndex, hdr, protoblocktx.Status_MALFORMED_MISSING_TX_ID, "no TX ID") + } + + switch common.HeaderType(hdr.Type) { + default: + return b.rejectTx(msgIndex, hdr, protoblocktx.Status_MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD, "message type") + case common.HeaderType_CONFIG: + _, err := policy.ParsePolicyFromConfigTx(msg) + if err != nil { + return b.rejectTx(msgIndex, hdr, protoblocktx.Status_MALFORMED_CONFIG_TX_INVALID, err.Error()) + } + b.isConfig = true + return b.appendTx(msgIndex, hdr, configTx(hdr.TxId, msg)) + case common.HeaderType_MESSAGE: + tx, err := serialization.UnmarshalTx(data) + if err != nil { + return b.rejectTx(msgIndex, hdr, protoblocktx.Status_MALFORMED_BAD_ENVELOPE_PAYLOAD, err.Error()) } + if status := verifyTxForm(tx, hdr); status != statusNotYetValidated { + return b.rejectTx(msgIndex, hdr, status, "malformed tx") + } + return b.appendTx(msgIndex, hdr, tx) } - return mappedBlock } -func (b *scBlockWithStatus) appendTx(txNum int, channelHdr *common.ChannelHeader, tx *protoblocktx.Tx) { - b.block.TxsNum = append(b.block.TxsNum, uint32(txNum)) //nolint:gosec +func (b *scBlockWithStatus) appendTx(txNum uint32, hdr *common.ChannelHeader, tx *protoblocktx.Tx) error { + if idAlreadyExists, err := b.addTxIDMapping(txNum, hdr); idAlreadyExists || err != nil { + return err + } + b.block.TxsNum = append(b.block.TxsNum, txNum) b.block.Txs = append(b.block.Txs, tx) - debugTx(channelHdr, "including [==> %s]", tx.Id) + debugTx(hdr, "included: %s", hdr.TxId) + return nil } -func (b *scBlockWithStatus) excludeTx(txNum int, channelHdr *common.ChannelHeader, reason string) { - b.withStatus.txStatus[txNum] = excludedStatus - b.withStatus.pendingCount-- - debugTx(channelHdr, "excluding due to %s", reason) +func (b *scBlockWithStatus) rejectTx( + txNum uint32, hdr *common.ChannelHeader, status protoblocktx.Status, reason string, +) error { + if !IsStatusStoredInDB(status) { + return b.rejectNonDBStatusTx(txNum, hdr, status, reason) + } + if idAlreadyExists, err := b.addTxIDMapping(txNum, hdr); idAlreadyExists || err != nil { + return err + } + b.block.Rejected = append(b.block.Rejected, &protocoordinatorservice.TxStatusInfo{ + TxNum: txNum, + Id: hdr.TxId, + Status: status, + }) + debugTx(hdr, "rejected: %s (%s)", &status, reason) + return nil +} + +func (b *scBlockWithStatus) rejectNonDBStatusTx( + txNum uint32, hdr *common.ChannelHeader, status protoblocktx.Status, reason string, +) error { + if IsStatusStoredInDB(status) { + // This can never occur unless there is a bug in the relay. + return errors.Newf("[BUG] status should be stored [blk:%d,num:%d]: %s", b.block.Number, txNum, &status) + } + err := b.withStatus.setFinalStatus(txNum, status) + debugTx(hdr, "excluded: %s (%s)", &status, reason) + return err +} + +func (b *scBlockWithStatus) addTxIDMapping(txNum uint32, hdr *common.ChannelHeader) (idAlreadyExists bool, err error) { + _, idAlreadyExists = b.txIDToHeight.LoadOrStore(hdr.TxId, types.Height{ + BlockNum: b.block.Number, + TxNum: txNum, + }) + if idAlreadyExists { + err = b.rejectNonDBStatusTx(txNum, hdr, protoblocktx.Status_REJECTED_DUPLICATE_TX_ID, "duplicate tx") + } + return idAlreadyExists, err +} + +func (b *blockWithStatus) setFinalStatus(txNum uint32, status protoblocktx.Status) error { + if b.txStatus[txNum] != statusNotYetValidated { + // This can never occur unless there is a bug in the relay or the coordinator. + return errors.Newf("two results for a TX [blockNum: %d, txNum: %d]", b.block.Header.Number, txNum) + } + b.txStatus[txNum] = status + b.pendingCount-- + return nil +} + +func (b *blockWithStatus) setStatusMetadataInBlock() { + statusMetadata := make([]byte, len(b.txStatus)) + for i, s := range b.txStatus { + statusMetadata[i] = byte(s) + } + b.block.Metadata.Metadata[statusIdx] = statusMetadata +} + +// IsStatusStoredInDB returns true if the given status code can be stored in the state DB. +func IsStatusStoredInDB(status protoblocktx.Status) bool { + switch status { + case protoblocktx.Status_MALFORMED_BAD_ENVELOPE, + protoblocktx.Status_MALFORMED_MISSING_TX_ID, + protoblocktx.Status_REJECTED_DUPLICATE_TX_ID: + return false + default: + return true + } } func debugTx(channelHdr *common.ChannelHeader, format string, a ...any) { @@ -115,14 +217,6 @@ func debugTx(channelHdr *common.ChannelHeader, format string, a ...any) { logger.Debugf("TX type [%s] ID [%s]: %s", hdr, txID, fmt.Sprintf(format, a...)) } -func newValidationCodes(expected int) []validationCode { - codes := make([]validationCode, expected) - for i := range codes { - codes[i] = notYetValidated - } - return codes -} - func configTx(id string, value []byte) *protoblocktx.Tx { return &protoblocktx.Tx{ Id: id, @@ -134,17 +228,122 @@ func configTx(id string, value []byte) *protoblocktx.Tx { Value: value, }}, }}, - // A valid TX must have a signature per namespace. - Signatures: make([][]byte, 1), } } -// isApplicationConfigTx checks the application does not submit a config TX. -func isApplicationConfigTx(tx *protoblocktx.Tx) bool { +// verifyTxForm verifies that a TX is not malformed. +// It returns status MALFORMED_ if it is malformed, or not-validated otherwise. +func verifyTxForm(tx *protoblocktx.Tx, hdr *common.ChannelHeader) protoblocktx.Status { + if status := validateTxID(tx, hdr); status != statusNotYetValidated { + return status + } + + if len(tx.Namespaces) == 0 { + return protoblocktx.Status_MALFORMED_EMPTY_NAMESPACES + } + if len(tx.Namespaces) != len(tx.Signatures) { + return protoblocktx.Status_MALFORMED_MISSING_SIGNATURE + } + + nsIDs := make(map[string]any, len(tx.Namespaces)) for _, ns := range tx.Namespaces { - if ns.NsId == types.ConfigNamespaceID { - return true + // Checks that the application does not submit a config TX. + if ns.NsId == types.ConfigNamespaceID || policy.ValidateNamespaceID(ns.NsId) != nil { + return protoblocktx.Status_MALFORMED_NAMESPACE_ID_INVALID + } + if _, ok := nsIDs[ns.NsId]; ok { + return protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE + } + + for _, check := range []func(ns *protoblocktx.TxNamespace) protoblocktx.Status{ + checkNamespaceFormation, checkMetaNamespace, + } { + if status := check(ns); status != statusNotYetValidated { + return status + } } + nsIDs[ns.NsId] = nil + } + return statusNotYetValidated +} + +func validateTxID(tx *protoblocktx.Tx, hdr *common.ChannelHeader) protoblocktx.Status { + if hdr.TxId != tx.Id { + // This is a temporary workaround. Later TX versions will not include the TX ID. + return protoblocktx.Status_MALFORMED_TX_ID_CONFLICT + } + + if tx.Id == "" || !utf8.ValidString(tx.Id) { + // ASN.1. Marshalling only supports valid UTF8 strings. + // This case is unlikely as the message received via protobuf message which also only support + // valid UTF8 strings. + // Thus, we do not create a designated status for such error. + return protoblocktx.Status_MALFORMED_MISSING_TX_ID + } + return statusNotYetValidated +} + +func checkNamespaceFormation(ns *protoblocktx.TxNamespace) protoblocktx.Status { + if len(ns.ReadWrites) == 0 && len(ns.BlindWrites) == 0 { + return protoblocktx.Status_MALFORMED_NO_WRITES + } + + keys := make([][]byte, 0, len(ns.ReadsOnly)+len(ns.ReadWrites)+len(ns.BlindWrites)) + for _, r := range ns.ReadsOnly { + keys = append(keys, r.Key) + } + for _, r := range ns.ReadWrites { + keys = append(keys, r.Key) + } + for _, r := range ns.BlindWrites { + keys = append(keys, r.Key) + } + return checkKeys(keys) +} + +func checkMetaNamespace(txNs *protoblocktx.TxNamespace) protoblocktx.Status { + if txNs.NsId != types.MetaNamespaceID { + return statusNotYetValidated + } + if len(txNs.BlindWrites) > 0 { + return protoblocktx.Status_MALFORMED_BLIND_WRITES_NOT_ALLOWED + } + + nsUpdate := make(map[string]any) + u := policy.GetUpdatesFromNamespace(txNs) + if u == nil { + return statusNotYetValidated + } + for _, pd := range u.NamespacePolicies.Policies { + _, err := policy.ParseNamespacePolicyItem(pd) + if err != nil { + if errors.Is(err, policy.ErrInvalidNamespaceID) { + return protoblocktx.Status_MALFORMED_NAMESPACE_ID_INVALID + } + return protoblocktx.Status_MALFORMED_NAMESPACE_POLICY_INVALID + } + if pd.Namespace == types.MetaNamespaceID { + return protoblocktx.Status_MALFORMED_NAMESPACE_POLICY_INVALID + } + if _, ok := nsUpdate[pd.Namespace]; ok { + return protoblocktx.Status_MALFORMED_NAMESPACE_POLICY_INVALID + } + nsUpdate[pd.Namespace] = nil + } + return statusNotYetValidated +} + +// checkKeys verifies there are no duplicate keys and no nil keys. +func checkKeys(keys [][]byte) protoblocktx.Status { + uniqueKeys := make(map[string]any, len(keys)) + for _, k := range keys { + if len(k) == 0 { + return protoblocktx.Status_MALFORMED_EMPTY_KEY + } + uniqueKeys[string(k)] = nil + } + if len(uniqueKeys) != len(keys) { + return protoblocktx.Status_MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET } - return false + return statusNotYetValidated } diff --git a/service/sidecar/mapping_test.go b/service/sidecar/mapping_test.go new file mode 100644 index 00000000..3999368a --- /dev/null +++ b/service/sidecar/mapping_test.go @@ -0,0 +1,105 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package sidecar + +import ( + "testing" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/hyperledger/fabric-x-committer/api/protoblocktx" + "github.com/hyperledger/fabric-x-committer/api/types" + "github.com/hyperledger/fabric-x-committer/loadgen/workload" + "github.com/hyperledger/fabric-x-committer/utils" + "github.com/hyperledger/fabric-x-committer/utils/logging" + "github.com/hyperledger/fabric-x-committer/utils/serialization" +) + +func BenchmarkMapBlock(b *testing.B) { + logging.SetupWithConfig(&logging.Config{Enabled: false}) + txs := workload.GenerateTransactions(b, workload.DefaultProfile(8), b.N) + block := makeBlock(b, txs) + + var txIDToHeight utils.SyncMap[string, types.Height] + b.ResetTimer() + mappedBlock, err := mapBlock(block, &txIDToHeight) + b.StopTimer() + require.NoError(b, err, "This can never occur unless there is a bug in the relay.") + require.NotNil(b, mappedBlock) +} + +func TestBlockMapping(t *testing.T) { + t.Parallel() + txs := make([]*protoblocktx.Tx, len(MalformedTxTestCases)+1) + expectedStatus := make([]protoblocktx.Status, len(MalformedTxTestCases)+1) + expectedBlockSize := 0 + expectedRejected := 0 + for i, tt := range MalformedTxTestCases { + txs[i] = tt.Tx + + if !IsStatusStoredInDB(tt.ExpectedStatus) { + expectedStatus[i] = tt.ExpectedStatus + continue + } + expectedBlockSize++ + if tt.ExpectedStatus != statusNotYetValidated { + expectedRejected++ + } + } + duplicatedID := "global duplicated ID" + txs[len(txs)-1] = &protoblocktx.Tx{ + Id: duplicatedID, + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: []byte("k1")}, + }, + }, + }, + Signatures: make([][]byte, 1), + } + expectedStatus[len(txs)-1] = protoblocktx.Status_REJECTED_DUPLICATE_TX_ID + var txIDToHeight utils.SyncMap[string, types.Height] + txIDToHeight.Store(duplicatedID, types.Height{}) + + block := makeBlock(t, txs) + mappedBlock, err := mapBlock(block, &txIDToHeight) + require.NoError(t, err, "This can never occur unless there is a bug in the relay.") + + require.NotNil(t, mappedBlock) + require.NotNil(t, mappedBlock.block) + require.NotNil(t, mappedBlock.withStatus) + + require.Equal(t, block, mappedBlock.withStatus.block) + require.Equal(t, block.Header.Number, mappedBlock.block.Number) + require.Equal(t, expectedStatus, mappedBlock.withStatus.txStatus) + + require.Equal(t, expectedBlockSize+1, txIDToHeight.Count()) + require.Len(t, mappedBlock.block.Txs, expectedBlockSize-expectedRejected) + require.Len(t, mappedBlock.block.TxsNum, expectedBlockSize-expectedRejected) + require.Len(t, mappedBlock.block.Rejected, expectedRejected) + require.Equal(t, expectedBlockSize, mappedBlock.withStatus.pendingCount) +} + +func makeBlock(tb testing.TB, txs []*protoblocktx.Tx) *common.Block { + tb.Helper() + data := make([][]byte, len(txs)) + for i, tx := range txs { + env, _, err := serialization.CreateEnvelope("channel", nil, tx) + require.NoError(tb, err) + data[i], err = proto.Marshal(env) + require.NoError(tb, err) + } + return &common.Block{ + Header: &common.BlockHeader{Number: 1}, + Data: &common.BlockData{Data: data}, + } +} diff --git a/service/sidecar/relay.go b/service/sidecar/relay.go index c2c8ba96..51620e8f 100644 --- a/service/sidecar/relay.go +++ b/service/sidecar/relay.go @@ -8,7 +8,6 @@ package sidecar import ( "context" - "slices" "sync/atomic" "time" @@ -18,6 +17,7 @@ import ( "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/api/protocoordinatorservice" + "github.com/hyperledger/fabric-x-committer/api/types" "github.com/hyperledger/fabric-x-committer/utils" "github.com/hyperledger/fabric-x-committer/utils/channel" "github.com/hyperledger/fabric-x-committer/utils/monitoring/promutil" @@ -30,19 +30,12 @@ type ( nextBlockNumberToBeCommitted atomic.Uint64 activeBlocksCount atomic.Int32 blkNumToBlkWithStatus utils.SyncMap[uint64, *blockWithStatus] - txIDToBlkNum utils.SyncMap[string, uint64] + txIDToHeight utils.SyncMap[string, types.Height] lastCommittedBlockSetInterval time.Duration waitingTxsSlots *utils.Slots metrics *perfMetrics } - blockWithStatus struct { - block *common.Block - txStatus []validationCode - txIDToTxIndex map[string]int - pendingCount int - } - relayRunConfig struct { coordClient protocoordinatorservice.CoordinatorClient nextExpectedBlockByCoordinator uint64 @@ -75,134 +68,120 @@ func (r *relay) run(ctx context.Context, config *relayRunConfig) error { //nolin r.incomingBlockToBeCommitted = config.incomingBlockToBeCommitted r.outgoingCommittedBlock = config.outgoingCommittedBlock r.blkNumToBlkWithStatus.Clear() - r.txIDToBlkNum.Clear() + r.txIDToHeight.Clear() r.waitingTxsSlots = utils.NewSlots(int64(config.waitingTxsLimit)) + // Using the errgroup context for the stream ensures that we cancel the stream once one of the tasks fails. + // And we use the stream's context to ensure that if the stream is closed, we stop all the tasks. + // Finally, we use `rCtx` to ensure that even if all tasks stops without an error, the stream will be cancelled. rCtx, rCancel := context.WithCancel(ctx) defer rCancel() - - stream, err := config.coordClient.BlockProcessing(rCtx) + g, gCtx := errgroup.WithContext(rCtx) + stream, err := config.coordClient.BlockProcessing(gCtx) if err != nil { return errors.Wrap(err, "failed to open stream for block processing") } + sCtx := stream.Context() logger.Infof("Starting coordinator sender and receiver") expectedNextBlockToBeCommitted := r.nextBlockNumberToBeCommitted.Load() - g, gCtx := errgroup.WithContext(stream.Context()) + mappedBlockQueue := make(chan *scBlockWithStatus, cap(r.incomingBlockToBeCommitted)) g.Go(func() error { - return r.preProcessBlockAndSendToCoordinator(gCtx, stream, config.configUpdater) + return r.preProcessBlock(sCtx, mappedBlockQueue, config.configUpdater) }) - - statusBatch := make(chan *protoblocktx.TransactionsStatus, 1000) g.Go(func() error { - return receiveStatusFromCoordinator(gCtx, stream, statusBatch) + return r.sendBlocksToCoordinator(sCtx, mappedBlockQueue, stream) }) + statusBatch := make(chan *protoblocktx.TransactionsStatus, cap(r.outgoingCommittedBlock)) + g.Go(func() error { + return receiveStatusFromCoordinator(sCtx, stream, statusBatch) + }) g.Go(func() error { - return r.processStatusBatch(gCtx, statusBatch) + return r.processStatusBatch(sCtx, statusBatch) }) g.Go(func() error { - return r.setLastCommittedBlockNumber(gCtx, config.coordClient, expectedNextBlockToBeCommitted) + return r.setLastCommittedBlockNumber(sCtx, config.coordClient, expectedNextBlockToBeCommitted) }) return utils.ProcessErr(g.Wait(), "stream with the coordinator has ended") } -func (r *relay) preProcessBlockAndSendToCoordinator( //nolint:gocognit +func (r *relay) preProcessBlock( ctx context.Context, - stream protocoordinatorservice.Coordinator_BlockProcessingClient, + mappedBlockQueue chan<- *scBlockWithStatus, configUpdater func(*common.Block), ) error { - g, gCtx := errgroup.WithContext(ctx) - - incomingBlockToBeCommitted := channel.NewReader(gCtx, r.incomingBlockToBeCommitted) - mappedBlockQueue := channel.Make[*scBlockWithStatus](gCtx, len(r.incomingBlockToBeCommitted)) - outgoingCommittedBlock := channel.NewWriter(gCtx, r.outgoingCommittedBlock) + incomingBlockToBeCommitted := channel.NewReader(ctx, r.incomingBlockToBeCommitted) + queue := channel.NewWriter(ctx, mappedBlockQueue) - done := context.AfterFunc(gCtx, r.waitingTxsSlots.Broadcast) + done := context.AfterFunc(ctx, r.waitingTxsSlots.Broadcast) defer done() - g.Go(func() error { - for { - block, ok := incomingBlockToBeCommitted.Read() - if !ok { - return errors.Wrap(gCtx.Err(), "context ended") - } - if block.Header == nil { - logger.Warn("Received a block without header") - continue - } - - logger.Debugf("Block %d arrived in the relay", block.Header.Number) + for { + block, ok := incomingBlockToBeCommitted.Read() + if !ok { + return errors.Wrap(ctx.Err(), "context ended") + } + if block.Header == nil { + logger.Warn("Received a block without header") + continue + } - start := time.Now() - mappedBlock := mapBlock(block) - promutil.Observe(r.metrics.blockMappingInRelaySeconds, time.Since(start)) - if mappedBlock.isConfig { - configUpdater(block) - } + logger.Debugf("Block %d arrived in the relay", block.Header.Number) - txsCount := len(mappedBlock.block.Txs) - r.waitingTxsSlots.Acquire(gCtx, int64(txsCount)) - promutil.AddToGauge(r.metrics.waitingTransactionsQueueSize, txsCount) - mappedBlockQueue.Write(mappedBlock) + start := time.Now() + mappedBlock, err := mapBlock(block, &r.txIDToHeight) + if err != nil { + // This can never occur unless there is a bug in the relay. + return err + } + promutil.Observe(r.metrics.blockMappingInRelaySeconds, time.Since(start)) + if mappedBlock.isConfig { + configUpdater(block) } - }) - - g.Go(func() error { - for { - mappedBlock, ok := mappedBlockQueue.Read() - if !ok { - return errors.Wrap(gCtx.Err(), "context ended") - } - startTime := time.Now() - blockNum := mappedBlock.block.Number - r.blkNumToBlkWithStatus.Store(blockNum, mappedBlock.withStatus) - - dupIdx := make([]int, 0, len(mappedBlock.block.Txs)) - for txIndex, tx := range mappedBlock.block.Txs { - if _, loaded := r.txIDToBlkNum.LoadOrStore(tx.Id, blockNum); !loaded { - logger.Debugf("Adding txID [%s] to in progress list", tx.GetId()) - mappedBlock.withStatus.txIDToTxIndex[tx.Id] = txIndex - continue - } - logger.Debugf("txID [%s] is duplicate", tx.GetId()) - promutil.AddToCounterVec(r.metrics.transactionsStatusReceivedTotal, []string{ - protoblocktx.Status_ABORTED_DUPLICATE_TXID.String(), - }, 1) - mappedBlock.withStatus.pendingCount-- - mappedBlock.withStatus.txStatus[txIndex] = validationCode(protoblocktx.Status_ABORTED_DUPLICATE_TXID) - dupIdx = append(dupIdx, txIndex) - } + txsCount := len(mappedBlock.block.Txs) + r.waitingTxsSlots.Acquire(ctx, int64(txsCount)) + promutil.AddToGauge(r.metrics.waitingTransactionsQueueSize, txsCount) + queue.Write(mappedBlock) + } +} - // Iterate over the indices in reverse order. Note that the dupIdx is sorted by default. - for _, index := range slices.Backward(dupIdx) { - mappedBlock.block.Txs = slices.Delete(mappedBlock.block.Txs, index, index+1) - mappedBlock.block.TxsNum = slices.Delete(mappedBlock.block.TxsNum, index, index+1) - } +func (r *relay) sendBlocksToCoordinator( + ctx context.Context, + mappedBlockQueue <-chan *scBlockWithStatus, + stream protocoordinatorservice.Coordinator_BlockProcessingClient, +) error { + queue := channel.NewReader(ctx, mappedBlockQueue) + outgoingCommittedBlock := channel.NewWriter(ctx, r.outgoingCommittedBlock) - r.activeBlocksCount.Add(1) + for { + mappedBlock, ok := queue.Read() + if !ok { + return errors.Wrap(ctx.Err(), "context ended") + } - txsCount := len(mappedBlock.block.Txs) - if txsCount == 0 && mappedBlock.withStatus.pendingCount == 0 { - r.processCommittedBlocksInOrder(gCtx, outgoingCommittedBlock) - } + startTime := time.Now() + r.blkNumToBlkWithStatus.Store(mappedBlock.block.Number, mappedBlock.withStatus) + r.activeBlocksCount.Add(1) - if err := stream.Send(mappedBlock.block); err != nil { - return errors.Wrap(err, "failed to send a block to the coordinator") - } - promutil.AddToCounter(r.metrics.transactionsSentTotal, txsCount) - logger.Debugf("Sent SC block %d with %d transactions to Coordinator", - mappedBlock.block.Number, txsCount) - promutil.Observe(r.metrics.mappedBlockProcessingInRelaySeconds, time.Since(startTime)) + if mappedBlock.withStatus.pendingCount == 0 { + r.processCommittedBlocksInOrder(ctx, outgoingCommittedBlock) } - }) - return utils.ProcessErr(g.Wait(), "pre-processing of blocks and sending to coordinator operation has ended") + if err := stream.Send(mappedBlock.block); err != nil { + return errors.Wrap(err, "failed to send a block to the coordinator") + } + txsCount := len(mappedBlock.block.Txs) + promutil.AddToCounter(r.metrics.transactionsSentTotal, txsCount) + logger.Debugf("Sent SC block %d with %d transactions to Coordinator", + mappedBlock.block.Number, txsCount) + promutil.Observe(r.metrics.mappedBlockProcessingInRelaySeconds, time.Since(startTime)) + } } func receiveStatusFromCoordinator( @@ -236,8 +215,10 @@ func (r *relay) processStatusBatch( txStatusProcessedCount := int64(0) startTime := time.Now() - for txID, txStatus := range tStatus.GetStatus() { - if blockNum, ok := r.txIDToBlkNum.Load(txID); !ok || txStatus.BlockNumber != blockNum { + for txID, txStatus := range tStatus.Status { + // We cannot use LoadAndDelete(txID) because it may not match the received statues. + height, ok := r.txIDToHeight.Load(txID) + if !ok || txStatus.BlockNumber != height.BlockNum { // - Case 1: Block not found. // Consider a scenario where the connection between the sidecar and the coordinator fails due // to a network issue—not because the coordinator restarts. Assume the relay has already submitted @@ -266,20 +247,12 @@ func (r *relay) processStatusBatch( // This can never occur unless there is a bug in the relay. return errors.Newf("block %d has never been submitted", txStatus.BlockNumber) } - - txIndex := blkWithStatus.txIDToTxIndex[txID] - - if blkWithStatus.txStatus[txIndex] != notYetValidated { - return errors.Newf("two results for the same TX (txID=%v). blockNum: %d, txNum: %d", - txID, txStatus.BlockNumber, txIndex) + err := blkWithStatus.setFinalStatus(height.TxNum, txStatus.Code) + if err != nil { + // This can never occur unless there is a bug in the relay or the coordinator. + return err } - - blkWithStatus.txStatus[txIndex] = byte(txStatus.GetCode()) - promutil.AddToCounterVec(r.metrics.transactionsStatusReceivedTotal, - []string{txStatus.GetCode().String()}, 1) - - r.txIDToBlkNum.Delete(txID) - blkWithStatus.pendingCount-- + r.txIDToHeight.Delete(txID) txStatusProcessedCount++ } @@ -310,9 +283,14 @@ func (r *relay) processCommittedBlocksInOrder( r.nextBlockNumberToBeCommitted.Add(1) r.activeBlocksCount.Add(-1) - blkWithStatus.block.Metadata = &common.BlockMetadata{ - Metadata: [][]byte{nil, nil, blkWithStatus.txStatus}, + statusCount := utils.CountAppearances(blkWithStatus.txStatus) + for status, count := range statusCount { + promutil.AddToCounter(r.metrics.transactionsStatusReceivedTotal.WithLabelValues( + status.String(), + ), count) } + + blkWithStatus.setStatusMetadataInBlock() outgoingCommittedBlock.Write(blkWithStatus.block) } } diff --git a/service/sidecar/relay_test.go b/service/sidecar/relay_test.go index 239538d5..3b0197e3 100644 --- a/service/sidecar/relay_test.go +++ b/service/sidecar/relay_test.go @@ -18,7 +18,9 @@ import ( "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/api/protocoordinatorservice" + "github.com/hyperledger/fabric-x-committer/loadgen/workload" "github.com/hyperledger/fabric-x-committer/mock" + "github.com/hyperledger/fabric-x-committer/utils/channel" "github.com/hyperledger/fabric-x-committer/utils/connection" "github.com/hyperledger/fabric-x-committer/utils/test" ) @@ -35,7 +37,7 @@ type relayTestEnv struct { const ( valid = byte(protoblocktx.Status_COMMITTED) - duplicate = byte(protoblocktx.Status_ABORTED_DUPLICATE_TXID) + duplicate = byte(protoblocktx.Status_REJECTED_DUPLICATE_TX_ID) ) func newRelayTestEnv(t *testing.T) *relayTestEnv { @@ -85,7 +87,7 @@ func TestRelayNormalBlock(t *testing.T) { relayEnv := newRelayTestEnv(t) relayEnv.coordinator.SetDelay(10 * time.Second) - blk0 := createBlockForTest(0, nil, [3]string{"tx1", "tx2", "tx3"}) + blk0 := createBlockForTest(t, 0, nil, [3]string{"tx1", "tx2", "tx3"}) require.Nil(t, blk0.Metadata) relayEnv.incomingBlockToBeCommitted <- blk0 @@ -112,7 +114,7 @@ func TestRelayNormalBlock(t *testing.T) { require.Greater(t, test.GetMetricValue(t, m.transactionStatusesProcessingInRelaySeconds), float64(0)) relayEnv.relay.waitingTxsSlots.Store(t, int64(0)) - blk1 := createBlockForTest(1, nil, [3]string{"tx1", "tx2", "tx3"}) + blk1 := createBlockForTest(t, 1, nil, [3]string{"tx1", "tx2", "tx3"}) relayEnv.incomingBlockToBeCommitted <- blk1 require.Never(t, func() bool { return test.GetMetricValue(t, m.transactionsSentTotal) > 3 @@ -125,20 +127,34 @@ func TestRelayNormalBlock(t *testing.T) { func TestBlockWithDuplicateTransactions(t *testing.T) { t.Parallel() relayEnv := newRelayTestEnv(t) - blk0 := createBlockForTest(0, nil, [3]string{"tx1", "tx1", "tx1"}) + + ctx, cancel := context.WithTimeout(t.Context(), 15*time.Second) + t.Cleanup(cancel) + incoming := channel.NewWriter(ctx, relayEnv.incomingBlockToBeCommitted) + committed := channel.NewReader(ctx, relayEnv.committedBlock) + + t.Log("Submitting block 0") + blk0 := createBlockForTest(t, 0, nil, [3]string{"tx1", "tx1", "tx1"}) require.Nil(t, blk0.Metadata) - relayEnv.incomingBlockToBeCommitted <- blk0 - committedBlock0 := <-relayEnv.committedBlock + incoming.Write(blk0) + + t.Log("Waiting for block 0 result") + committedBlock0, ok := committed.Read() + require.True(t, ok) expectedMetadata := &common.BlockMetadata{ Metadata: [][]byte{nil, nil, {valid, duplicate, duplicate}}, } require.Equal(t, expectedMetadata, committedBlock0.Metadata) require.Equal(t, blk0, committedBlock0) - blk1 := createBlockForTest(1, nil, [3]string{"tx2", "tx3", "tx2"}) + t.Log("Submitting block 1") + blk1 := createBlockForTest(t, 1, nil, [3]string{"tx2", "tx3", "tx2"}) require.Nil(t, blk1.Metadata) - relayEnv.incomingBlockToBeCommitted <- blk1 - committedBlock1 := <-relayEnv.committedBlock + incoming.Write(blk1) + + t.Log("Waiting for block 1 result") + committedBlock1, ok := committed.Read() + require.True(t, ok) expectedMetadata = &common.BlockMetadata{ Metadata: [][]byte{nil, nil, {valid, valid, duplicate}}, } @@ -149,38 +165,27 @@ func TestBlockWithDuplicateTransactions(t *testing.T) { func TestRelayConfigBlock(t *testing.T) { t.Parallel() relayEnv := newRelayTestEnv(t) - configBlk := createConfigBlockForTest(nil, 0) + configBlk := createConfigBlockForTest(t) relayEnv.incomingBlockToBeCommitted <- configBlk committedBlock := <-relayEnv.committedBlock require.Equal(t, configBlk, committedBlock) - require.Equal(t, &common.BlockMetadata{ - Metadata: [][]byte{nil, nil, {valid}}, - }, committedBlock.Metadata) + require.NotNil(t, committedBlock.Metadata) + require.Greater(t, len(committedBlock.Metadata.Metadata), statusIdx) + require.Equal(t, []byte{valid}, committedBlock.Metadata.Metadata[statusIdx]) require.Len(t, relayEnv.configBlocks, 1) require.Equal(t, configBlk, relayEnv.configBlocks[0]) } -func createConfigBlockForTest(_ *testing.T, number uint64) *common.Block { - data := protoutil.MarshalOrPanic(&common.Envelope{ - Payload: protoutil.MarshalOrPanic(&common.Payload{ - Header: &common.Header{ - ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ - Type: int32(common.HeaderType_CONFIG), - }), - }, - }, - ), - }, - ) - - return &common.Block{ - Header: &common.BlockHeader{Number: number}, - Data: &common.BlockData{Data: [][]byte{data}}, - } +func createConfigBlockForTest(t *testing.T) *common.Block { + t.Helper() + block, err := workload.CreateConfigBlock(&workload.PolicyProfile{}) + require.NoError(t, err) + return block } // createBlockForTest creates sample block with three txIDs. -func createBlockForTest(number uint64, preBlockHash []byte, txIDs [3]string) *common.Block { +func createBlockForTest(t *testing.T, number uint64, preBlockHash []byte, txIDs [3]string) *common.Block { + t.Helper() return &common.Block{ Header: &common.BlockHeader{ Number: number, @@ -188,15 +193,16 @@ func createBlockForTest(number uint64, preBlockHash []byte, txIDs [3]string) *co }, Data: &common.BlockData{ Data: [][]byte{ - createEnvelopeBytesForTest(txIDs[0]), - createEnvelopeBytesForTest(txIDs[1]), - createEnvelopeBytesForTest(txIDs[2]), + createEnvelopeBytesForTest(t, txIDs[0]), + createEnvelopeBytesForTest(t, txIDs[1]), + createEnvelopeBytesForTest(t, txIDs[2]), }, }, } } -func createEnvelopeBytesForTest(txID string) []byte { +func createEnvelopeBytesForTest(t *testing.T, txID string) []byte { + t.Helper() header := &common.Header{ ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ ChannelId: "ch1", @@ -207,7 +213,7 @@ func createEnvelopeBytesForTest(txID string) []byte { return protoutil.MarshalOrPanic(&common.Envelope{ Payload: protoutil.MarshalOrPanic(&common.Payload{ Header: header, - Data: protoutil.MarshalOrPanic(&protoblocktx.Tx{Id: txID}), + Data: protoutil.MarshalOrPanic(makeValidTx(t, txID)), }), }) } diff --git a/service/sidecar/sidecar.go b/service/sidecar/sidecar.go index cfc9b370..8fb40dd8 100644 --- a/service/sidecar/sidecar.go +++ b/service/sidecar/sidecar.go @@ -324,7 +324,12 @@ func appendMissingBlock( blk *common.Block, committedBlocks channel.Writer[*common.Block], ) error { - mappedBlock := mapBlock(blk) + var txIDToHeight utils.SyncMap[string, types.Height] + mappedBlock, err := mapBlock(blk, &txIDToHeight) + if err != nil { + // This can never occur unless there is a bug in the relay. + return err + } txIDs := make([]string, len(mappedBlock.block.Txs)) expectedHeight := make(map[string]*types.Height) for i, tx := range mappedBlock.block.Txs { @@ -341,11 +346,9 @@ func appendMissingBlock( return err } - blk.Metadata = &common.BlockMetadata{ - Metadata: [][]byte{nil, nil, mappedBlock.withStatus.txStatus}, - } + mappedBlock.withStatus.setStatusMetadataInBlock() - if !committedBlocks.Write(blk) { + if !committedBlocks.Write(mappedBlock.withStatus.block) { return errors.New("context ended") } return nil @@ -391,7 +394,7 @@ func waitForIdleCoordinator(ctx context.Context, client protocoordinatorservice. } func fillStatuses( - finalStatuses []validationCode, + finalStatuses []protoblocktx.Status, statuses map[string]*protoblocktx.StatusWithHeight, expectedHeight map[string]*types.Height, ) error { @@ -401,11 +404,10 @@ func fillStatuses( return errors.Newf("committer should have the status of txID [%s] but it does not", txID) } if types.AreSame(height, types.NewHeight(s.BlockNumber, s.TxNumber)) { - finalStatuses[height.TxNum] = byte(s.Code) + finalStatuses[height.TxNum] = s.Code continue } - finalStatuses[height.TxNum] = byte(protoblocktx.Status_ABORTED_DUPLICATE_TXID) + finalStatuses[height.TxNum] = protoblocktx.Status_REJECTED_DUPLICATE_TX_ID } - return nil } diff --git a/service/sidecar/sidecar_test.go b/service/sidecar/sidecar_test.go index 31d2ac95..f59018b1 100644 --- a/service/sidecar/sidecar_test.go +++ b/service/sidecar/sidecar_test.go @@ -8,6 +8,7 @@ package sidecar import ( "context" + "crypto/rand" _ "embed" "fmt" "path/filepath" @@ -18,7 +19,6 @@ import ( "github.com/google/uuid" "github.com/hyperledger/fabric-protos-go-apiv2/common" - "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/hyperledger/fabric-x-common/internaltools/configtxgen" "github.com/hyperledger/fabric/common/ledger/blkstorage" "github.com/hyperledger/fabric/protoutil" @@ -402,7 +402,7 @@ func TestSidecarRecoveryAfterCoordinatorFailure(t *testing.T) { ) t.Log("3. Send transactions to ordering service to create block 11 after stopping the coordinator") - txs := env.sendTransactions(ctx, t) + txs := env.sendTransactionsForBlock(ctx, t) t.Log("4. Restart the coordinator and validate processing block 11") env.coordinatorServer = mock.StartMockCoordinatorServiceFromListWithConfig(t, env.coordinator, @@ -454,6 +454,36 @@ func TestSidecarStartWithoutCoordinator(t *testing.T) { } } +func TestSidecarVerifyBadTxForm(t *testing.T) { + t.Parallel() + env := newSidecarTestEnv(t, sidecarTestConfig{WithConfigBlock: true}) + ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute) + t.Cleanup(cancel) + env.start(ctx, t, 0) + env.requireBlock(ctx, t, 0) + testSize := len(MalformedTxTestCases) + txs := make([]*protoblocktx.Tx, testSize) + status := make([]protoblocktx.Status, testSize) + t.Logf("sending %d malformed txs", testSize) + for i, tt := range MalformedTxTestCases { + txs[i] = tt.Tx + if tt.ExpectedStatus != protoblocktx.Status_NOT_VALIDATED { + status[i] = tt.ExpectedStatus + } else { + status[i] = protoblocktx.Status_COMMITTED + } + _, err := env.ordererEnv.Orderer.SubmitPayload(ctx, env.ordererEnv.TestConfig.ChanID, tt.Tx) + require.NoErrorf(t, err, "tx %s", tt.Tx.Id) + } + t.Logf("sending %d good txs", blockSize-testSize) + extraTxs := env.sendTransactions(ctx, t, blockSize-testSize) + txs = append(txs, extraTxs...) + for range extraTxs { + status = append(status, protoblocktx.Status_COMMITTED) + } + env.requireBlockWithTXsAndStatus(ctx, t, 1, txs, status) +} + func (env *sidecarTestEnv) getCoordinatorLabel(t *testing.T) string { t.Helper() conn, err := connection.Connect(connection.NewInsecureDialConfig(&env.config.Committer.Endpoint)) @@ -468,23 +498,30 @@ func (env *sidecarTestEnv) sendTransactionsAndEnsureCommitted( expectedBlockNumber uint64, ) { t.Helper() - txs := env.sendTransactions(ctx, t) + txs := env.sendTransactionsForBlock(ctx, t) env.requireBlockWithTXs(ctx, t, expectedBlockNumber, txs) } +func (env *sidecarTestEnv) sendTransactionsForBlock( + ctx context.Context, + t *testing.T, +) []*protoblocktx.Tx { + t.Helper() + // mock-orderer expects txs to create the next block. + return env.sendTransactions(ctx, t, blockSize) +} + func (env *sidecarTestEnv) sendTransactions( ctx context.Context, t *testing.T, + count int, ) []*protoblocktx.Tx { t.Helper() txIDPrefix := uuid.New().String() // mock-orderer expects txs to create the next block. - txs := make([]*protoblocktx.Tx, blockSize) + txs := make([]*protoblocktx.Tx, count) for i := range txs { - txs[i] = &protoblocktx.Tx{ - Id: txIDPrefix + strconv.Itoa(i), - Namespaces: []*protoblocktx.TxNamespace{{NsId: strconv.Itoa(i)}}, - } + txs[i] = makeValidTx(t, txIDPrefix+"."+strconv.Itoa(i)) _, err := env.ordererEnv.Orderer.SubmitPayload(ctx, env.ordererEnv.TestConfig.ChanID, txs[i]) require.NoErrorf(t, err, "tx %d", i) } @@ -498,6 +535,23 @@ func (env *sidecarTestEnv) requireBlockWithTXs( txs []*protoblocktx.Tx, ) { t.Helper() + allValid := make([]protoblocktx.Status, len(txs)) + for i := range allValid { + allValid[i] = protoblocktx.Status_COMMITTED + } + env.requireBlockWithTXsAndStatus(ctx, t, expectedBlockNumber, txs, allValid) +} + +//nolint:revive // 5 arguments. +func (env *sidecarTestEnv) requireBlockWithTXsAndStatus( + ctx context.Context, + t *testing.T, + expectedBlockNumber uint64, + txs []*protoblocktx.Tx, + status []protoblocktx.Status, +) { + t.Helper() + require.Len(t, status, len(txs)) block := env.requireBlock(ctx, t, expectedBlockNumber) require.NotNil(t, block.Data) require.Len(t, block.Data.Data, blockSize) @@ -513,8 +567,8 @@ func (env *sidecarTestEnv) requireBlockWithTXs( require.NotNil(t, block.Metadata) require.Len(t, block.Metadata.Metadata, 3) require.Len(t, block.Metadata.Metadata[2], blockSize) - for _, status := range block.Metadata.Metadata[2] { - require.Equal(t, valid, status) + for i, actualStatus := range block.Metadata.Metadata[2] { + require.Equal(t, byte(status[i]), actualStatus) } } @@ -549,7 +603,7 @@ func TestConstructStatuses(t *testing.T) { TxNumber: 3, }, "tx3": { - Code: protoblocktx.Status_ABORTED_BLIND_WRITES_NOT_ALLOWED, + Code: protoblocktx.Status_MALFORMED_BLIND_WRITES_NOT_ALLOWED, BlockNumber: 2, TxNumber: 3, }, @@ -566,18 +620,18 @@ func TestConstructStatuses(t *testing.T) { "tx4": types.NewHeight(1, 6), } - expectedFinalStatuses := []byte{ - byte(peer.TxValidationCode_VALID), - byte(protoblocktx.Status_COMMITTED), - byte(peer.TxValidationCode_VALID), - byte(protoblocktx.Status_ABORTED_SIGNATURE_INVALID), - byte(peer.TxValidationCode_VALID), - byte(protoblocktx.Status_ABORTED_DUPLICATE_TXID), - byte(protoblocktx.Status_COMMITTED), + expectedFinalStatuses := []protoblocktx.Status{ + protoblocktx.Status_COMMITTED, + protoblocktx.Status_COMMITTED, + protoblocktx.Status_COMMITTED, + protoblocktx.Status_ABORTED_SIGNATURE_INVALID, + protoblocktx.Status_COMMITTED, + protoblocktx.Status_REJECTED_DUPLICATE_TX_ID, + protoblocktx.Status_COMMITTED, } - actualFinalStatuses := newValidationCodes(7) + actualFinalStatuses := make([]protoblocktx.Status, 7) for _, skippedIdx := range []int{0, 2, 4} { - actualFinalStatuses[skippedIdx] = byte(peer.TxValidationCode_VALID) + actualFinalStatuses[skippedIdx] = protoblocktx.Status_COMMITTED } require.NoError(t, fillStatuses(actualFinalStatuses, statuses, expectedHeight)) require.Equal(t, expectedFinalStatuses, actualFinalStatuses) @@ -597,3 +651,19 @@ func checkLastCommittedBlock( require.Equal(ct, expectedBlockNumber, lastCommittedBlock.Block.Number) }, expectedProcessingTime, 50*time.Millisecond) } + +func makeValidTx(t *testing.T, txID string) *protoblocktx.Tx { + t.Helper() + key := make([]byte, 32) + _, err := rand.Read(key) + require.NoError(t, err) + return &protoblocktx.Tx{ + Id: txID, + Namespaces: []*protoblocktx.TxNamespace{{ + NsId: strings.ReplaceAll(uuid.New().String(), "-", "")[:32], + NsVersion: 0, + BlindWrites: []*protoblocktx.Write{{Key: key}}, + }}, + Signatures: make([][]byte, 1), + } +} diff --git a/service/sidecar/test_exports.go b/service/sidecar/test_exports.go new file mode 100644 index 00000000..11b477bf --- /dev/null +++ b/service/sidecar/test_exports.go @@ -0,0 +1,411 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package sidecar + +import ( + "google.golang.org/protobuf/proto" + + "github.com/hyperledger/fabric-x-committer/api/protoblocktx" + "github.com/hyperledger/fabric-x-committer/api/types" + "github.com/hyperledger/fabric-x-committer/utils/signature" + "github.com/hyperledger/fabric-x-committer/utils/signature/sigtest" +) + +// MalformedTxTestCases are valid and invalid TXs due to malformed. +var MalformedTxTestCases = []struct { + Tx *protoblocktx.Tx + ExpectedStatus protoblocktx.Status +}{ + { + Tx: &protoblocktx.Tx{ + Id: "valid TX", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: []byte("k1")}, + }, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_NOT_VALIDATED, + }, + { + Tx: &protoblocktx.Tx{}, + ExpectedStatus: protoblocktx.Status_MALFORMED_MISSING_TX_ID, + }, + { + Tx: &protoblocktx.Tx{ + Id: "empty namespaces", + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_EMPTY_NAMESPACES, + }, + { + Tx: &protoblocktx.Tx{ + Id: "missing signature", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, + }, + }, + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_MISSING_SIGNATURE, + }, + { + Tx: &protoblocktx.Tx{ + Id: "not enough signatures", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, + }, + }, + Signatures: make([][]byte, 0), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_MISSING_SIGNATURE, + }, + { + Tx: &protoblocktx.Tx{ + Id: "too much signatures", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, + }, + }, + Signatures: make([][]byte, 2), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_MISSING_SIGNATURE, + }, + { + Tx: &protoblocktx.Tx{ + Id: "namespace id is invalid in tx", + Namespaces: []*protoblocktx.TxNamespace{ + {NsId: "//"}, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_NAMESPACE_ID_INVALID, + }, + { + Tx: &protoblocktx.Tx{ + Id: "no writes", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadsOnly: []*protoblocktx.Read{ + {Key: []byte("k1")}, + }, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_NO_WRITES, + }, + { + Tx: &protoblocktx.Tx{ + Id: "namespace id is invalid in metaNs tx", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: []byte("key")}, + }, + }, + { + NsId: types.MetaNamespaceID, + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: []byte("/\\")}, + }, + }, + }, + Signatures: make([][]byte, 2), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_NAMESPACE_ID_INVALID, + }, + { + Tx: &protoblocktx.Tx{ + Id: "namespace policy is invalid in metaNs tx", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: []byte("key")}, + }, + }, + { + NsId: types.MetaNamespaceID, + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + { + Key: []byte("2"), + Value: []byte("value"), + }, + }, + }, + }, + Signatures: make([][]byte, 2), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_NAMESPACE_POLICY_INVALID, + }, + { + Tx: &protoblocktx.Tx{ + Id: "duplicate namespace", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: []byte("key")}, + }, + }, + { + NsId: "1", + NsVersion: 0, + }, + }, + Signatures: make([][]byte, 2), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, + }, + { + Tx: &protoblocktx.Tx{ + Id: "valid namespace TX", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: types.MetaNamespaceID, + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + { + Key: []byte("2"), + Value: defaultNsValidPolicy(), + }, + }, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_NOT_VALIDATED, + }, + { + Tx: &protoblocktx.Tx{ + Id: "valid namespace TX with regular TX", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: []byte("key")}, + }, + }, + { + NsId: types.MetaNamespaceID, + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + { + Key: []byte("2"), + Value: defaultNsValidPolicy(), + }, + }, + }, + }, + Signatures: make([][]byte, 2), + }, + ExpectedStatus: protoblocktx.Status_NOT_VALIDATED, + }, + { + Tx: &protoblocktx.Tx{ + Id: "invalid policy", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: types.MetaNamespaceID, + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + { + Key: []byte("2"), + Value: defaultNsInvalidPolicy(), + }, + }, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_NAMESPACE_POLICY_INVALID, + }, + { + Tx: &protoblocktx.Tx{ + Id: "blind writes not allowed in metaNs tx", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + { + Key: []byte("key"), + }, + }, + }, + { + NsId: types.MetaNamespaceID, + NsVersion: 0, + BlindWrites: []*protoblocktx.Write{ + { + Key: []byte("2"), + Value: defaultNsInvalidPolicy(), + }, + }, + }, + }, + Signatures: make([][]byte, 2), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_BLIND_WRITES_NOT_ALLOWED, + }, + { + Tx: &protoblocktx.Tx{ + Id: "nil key in ReadOnly", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadsOnly: []*protoblocktx.Read{{Key: nil}}, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_EMPTY_KEY, + }, + { + Tx: &protoblocktx.Tx{ + Id: "nil key in ReadWrites", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{{Key: nil}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_EMPTY_KEY, + }, + { + Tx: &protoblocktx.Tx{ + Id: "nil key in BlindWrites", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + BlindWrites: []*protoblocktx.Write{{Key: nil}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_EMPTY_KEY, + }, + { + Tx: &protoblocktx.Tx{ + Id: "duplicate key within ReadOnly", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadsOnly: []*protoblocktx.Read{{Key: []byte("key1")}, {Key: []byte("key1")}}, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET, + }, + { + Tx: &protoblocktx.Tx{ + Id: "duplicate key within ReadWrite", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("key1")}, {Key: []byte("key1")}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET, + }, + { + Tx: &protoblocktx.Tx{ + Id: "duplicate key within BlindWrites", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + BlindWrites: []*protoblocktx.Write{{Key: []byte("key1")}, {Key: []byte("key1")}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET, + }, + { + Tx: &protoblocktx.Tx{ + Id: "duplicate key across ReadOnly and ReadWrite", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadsOnly: []*protoblocktx.Read{{Key: []byte("key1")}}, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("key1")}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET, + }, + { + Tx: &protoblocktx.Tx{ + Id: "duplicate key across ReadWrite and BlindWrites", + Namespaces: []*protoblocktx.TxNamespace{ + { + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("key1")}}, + BlindWrites: []*protoblocktx.Write{{Key: []byte("key1")}}, + }, + }, + Signatures: make([][]byte, 1), + }, + ExpectedStatus: protoblocktx.Status_MALFORMED_DUPLICATE_KEY_IN_READ_WRITE_SET, + }, +} + +func defaultNsInvalidPolicy() []byte { + nsPolicy, _ := proto.Marshal(&protoblocktx.NamespacePolicy{ + Scheme: "ECDSA", + PublicKey: []byte("publicKey"), + }) + return nsPolicy +} + +func defaultNsValidPolicy() []byte { + factory := sigtest.NewSignatureFactory(signature.Ecdsa) + _, verificationKey := factory.NewKeys() + nsPolicy, _ := proto.Marshal(&protoblocktx.NamespacePolicy{ + Scheme: "ECDSA", + PublicKey: verificationKey, + }) + return nsPolicy +} diff --git a/service/vc/committer.go b/service/vc/committer.go index f8f78b06..070ab164 100644 --- a/service/vc/committer.go +++ b/service/vc/committer.go @@ -165,7 +165,7 @@ func (c *transactionCommitter) commitTransactions( if err := vTx.invalidateTxsOnReadConflicts(conflicts); err != nil { return nil, fmt.Errorf("failed to invalidate transactions on read conflicts: %w", err) } - vTx.updateInvalidTxs(duplicates, protoblocktx.Status_ABORTED_DUPLICATE_TXID) + vTx.updateInvalidTxs(duplicates, protoblocktx.Status_REJECTED_DUPLICATE_TX_ID) } return nil, errors.Newf("[BUG] commit failed after %d retries", maxRetriesToRemoveAllInvalidTxs) @@ -241,7 +241,7 @@ func (c *transactionCommitter) setCorrectStatusForDuplicateTxID( ) error { var dupTxIDs [][]byte for id, s := range txsStatus.Status { - if s.Code == protoblocktx.Status_ABORTED_DUPLICATE_TXID { + if s.Code == protoblocktx.Status_REJECTED_DUPLICATE_TX_ID { dupTxIDs = append(dupTxIDs, []byte(id)) } } diff --git a/service/vc/committer_test.go b/service/vc/committer_test.go index 0538cf2f..b75db707 100644 --- a/service/vc/committer_test.go +++ b/service/vc/committer_test.go @@ -381,7 +381,7 @@ func TestCommit(t *testing.T) { //nolint:maintidx // cannot improve. }, }, expectedTxStatuses: map[string]*protoblocktx.StatusWithHeight{ - "tx1": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_DUPLICATE_TXID, 1, 5), + "tx1": types.CreateStatusWithHeight(protoblocktx.Status_REJECTED_DUPLICATE_TX_ID, 1, 5), "tx-conflict-10": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_MVCC_CONFLICT, 1, 1), "tx-conflict-11": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_MVCC_CONFLICT, 4, 2), "tx-conflict-12": types.CreateStatusWithHeight(protoblocktx.Status_ABORTED_MVCC_CONFLICT, 66000, 3), diff --git a/service/vc/database.go b/service/vc/database.go index 0ad8b72d..efbf2058 100644 --- a/service/vc/database.go +++ b/service/vc/database.go @@ -290,7 +290,7 @@ func (db *database) insertTxStatus( heights := make([][]byte, 0, numEntries) for tID, status := range states.batchStatus.Status { // We cannot insert a "duplicate ID" status since we already have a status entry with this ID. - if status.Code == protoblocktx.Status_ABORTED_DUPLICATE_TXID { + if status.Code == protoblocktx.Status_REJECTED_DUPLICATE_TX_ID { continue } ids = append(ids, []byte(tID)) diff --git a/service/vc/preparer_test.go b/service/vc/preparer_test.go index 4925740b..a1768993 100644 --- a/service/vc/preparer_test.go +++ b/service/vc/preparer_test.go @@ -508,7 +508,7 @@ func TestPrepareTx(t *testing.T) { //nolint:maintidx // cannot improve. { ID: "tx3", PrelimInvalidTxStatus: &protovcservice.InvalidTxStatus{ - Code: protoblocktx.Status_ABORTED_NO_WRITES, + Code: protoblocktx.Status_MALFORMED_NO_WRITES, }, BlockNumber: 6, TxNum: 2, @@ -516,7 +516,7 @@ func TestPrepareTx(t *testing.T) { //nolint:maintidx // cannot improve. { ID: "tx4", PrelimInvalidTxStatus: &protovcservice.InvalidTxStatus{ - Code: protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, + Code: protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, }, BlockNumber: 5, TxNum: 2, @@ -663,8 +663,8 @@ func TestPrepareTx(t *testing.T) { //nolint:maintidx // cannot improve. }, }, invalidTxIDStatus: map[TxID]protoblocktx.Status{ - "tx3": protoblocktx.Status_ABORTED_NO_WRITES, - "tx4": protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, + "tx3": protoblocktx.Status_MALFORMED_NO_WRITES, + "tx4": protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, }, txIDToHeight: transactionIDToHeight{ "tx1": types.NewHeight(8, 0), diff --git a/service/vc/test_exports.go b/service/vc/test_exports.go index c18e56c9..5d64072d 100644 --- a/service/vc/test_exports.go +++ b/service/vc/test_exports.go @@ -197,21 +197,21 @@ func (env *DatabaseTestEnv) StatusExistsForNonDuplicateTxID( expectedStatuses map[string]*protoblocktx.StatusWithHeight, ) { t.Helper() - var nonDupTxIDs [][]byte + var persistedTxIDs [][]byte for id, s := range expectedStatuses { - if s.Code != protoblocktx.Status_ABORTED_DUPLICATE_TXID { - nonDupTxIDs = append(nonDupTxIDs, []byte(id)) + if s.Code < protoblocktx.Status_REJECTED_DUPLICATE_TX_ID { + persistedTxIDs = append(persistedTxIDs, []byte(id)) } } ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute) defer cancel() - actualRows, err := env.DB.readStatusWithHeight(ctx, nonDupTxIDs) + actualRows, err := env.DB.readStatusWithHeight(ctx, persistedTxIDs) require.NoError(t, err) - require.Len(t, actualRows, len(nonDupTxIDs)) - for _, tID := range nonDupTxIDs { - require.Equal(t, expectedStatuses[string(tID)], actualRows[string(tID)]) + require.Len(t, actualRows, len(persistedTxIDs)) + for _, tID := range persistedTxIDs { + require.EqualExportedValues(t, expectedStatuses[string(tID)], actualRows[string(tID)]) } } @@ -223,20 +223,18 @@ func (env *DatabaseTestEnv) StatusExistsWithDifferentHeightForDuplicateTxID( expectedStatuses map[string]*protoblocktx.StatusWithHeight, ) { t.Helper() - var dupTxIDs [][]byte - for id, s := range expectedStatuses { - if s.Code == protoblocktx.Status_ABORTED_DUPLICATE_TXID { - dupTxIDs = append(dupTxIDs, []byte(id)) - } + txIDs := make([][]byte, 0, len(expectedStatuses)) + for id := range expectedStatuses { + txIDs = append(txIDs, []byte(id)) } ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute) defer cancel() - actualRows, err := env.DB.readStatusWithHeight(ctx, dupTxIDs) + actualRows, err := env.DB.readStatusWithHeight(ctx, txIDs) require.NoError(t, err) - require.Len(t, actualRows, len(dupTxIDs)) - for _, tID := range dupTxIDs { + require.Len(t, actualRows, len(txIDs)) + for _, tID := range txIDs { // For the duplicate txID, neither the status nor the height would match the entry in the // transaction status table. txID := string(tID) //nolint:staticcheck // false positive. diff --git a/service/vc/validator_committer_service.go b/service/vc/validator_committer_service.go index f067c4ad..a9838287 100644 --- a/service/vc/validator_committer_service.go +++ b/service/vc/validator_committer_service.go @@ -375,7 +375,7 @@ func (vc *ValidatorCommitterService) sendTransactionStatus( committed++ case protoblocktx.Status_ABORTED_MVCC_CONFLICT: mvcc++ - case protoblocktx.Status_ABORTED_DUPLICATE_TXID: + case protoblocktx.Status_REJECTED_DUPLICATE_TX_ID: dup++ } } diff --git a/service/vc/validator_committer_service_test.go b/service/vc/validator_committer_service_test.go index c74dbba6..995b1d96 100644 --- a/service/vc/validator_committer_service_test.go +++ b/service/vc/validator_committer_service_test.go @@ -380,7 +380,7 @@ func TestValidatorAndCommitterService(t *testing.T) { { ID: "prelim invalid tx", PrelimInvalidTxStatus: &protovcservice.InvalidTxStatus{ - Code: protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, + Code: protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, }, BlockNumber: 5, TxNum: 2, @@ -403,6 +403,14 @@ func TestValidatorAndCommitterService(t *testing.T) { BlockNumber: 2, TxNum: 6, }, + { + ID: "Rejected TX", + BlockNumber: 2, + TxNum: 7, + PrelimInvalidTxStatus: &protovcservice.InvalidTxStatus{ + Code: protoblocktx.Status_MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD, + }, + }, }, } @@ -412,8 +420,9 @@ func TestValidatorAndCommitterService(t *testing.T) { expectedStatus := []protoblocktx.Status{ protoblocktx.Status_ABORTED_MVCC_CONFLICT, - protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, + protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, protoblocktx.Status_ABORTED_MVCC_CONFLICT, + protoblocktx.Status_MALFORMED_UNSUPPORTED_ENVELOPE_PAYLOAD, } expectedTxStatus := make(map[string]*protoblocktx.StatusWithHeight, len(txBatch.Transactions)) @@ -628,12 +637,12 @@ func TestTransactionResubmission(t *testing.T) { tx: &protovcservice.Transaction{ ID: "duplicate namespace", PrelimInvalidTxStatus: &protovcservice.InvalidTxStatus{ - Code: protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, + Code: protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, }, BlockNumber: 3, TxNum: 7, }, - expectedStatus: protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, + expectedStatus: protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, }, { tx: &protovcservice.Transaction{ diff --git a/service/vc/validator_test.go b/service/vc/validator_test.go index a22d3d56..164239a1 100644 --- a/service/vc/validator_test.go +++ b/service/vc/validator_test.go @@ -280,8 +280,8 @@ func TestValidate(t *testing.T) { //nolint:maintidx // cannot improve. "tx3": tx3BlindWrites, }, invalidTxIDStatus: map[TxID]protoblocktx.Status{ - "tx5": protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, - "tx6": protoblocktx.Status_ABORTED_BLIND_WRITES_NOT_ALLOWED, + "tx5": protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, + "tx6": protoblocktx.Status_MALFORMED_BLIND_WRITES_NOT_ALLOWED, }, txIDToHeight: transactionIDToHeight{ "tx1": types.NewHeight(1, 1), @@ -301,8 +301,8 @@ func TestValidate(t *testing.T) { //nolint:maintidx // cannot improve. "tx1": protoblocktx.Status_ABORTED_MVCC_CONFLICT, "tx3": protoblocktx.Status_ABORTED_MVCC_CONFLICT, "tx4": protoblocktx.Status_ABORTED_MVCC_CONFLICT, - "tx5": protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, - "tx6": protoblocktx.Status_ABORTED_BLIND_WRITES_NOT_ALLOWED, + "tx5": protoblocktx.Status_MALFORMED_DUPLICATE_NAMESPACE, + "tx6": protoblocktx.Status_MALFORMED_BLIND_WRITES_NOT_ALLOWED, }, txIDToHeight: transactionIDToHeight{ "tx1": types.NewHeight(1, 1), diff --git a/service/verifier/policy/policy.go b/service/verifier/policy/policy.go index 50c233bb..a039755b 100644 --- a/service/verifier/policy/policy.go +++ b/service/verifier/policy/policy.go @@ -18,6 +18,7 @@ import ( "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/api/protosigverifierservice" "github.com/hyperledger/fabric-x-committer/api/types" + "github.com/hyperledger/fabric-x-committer/utils/signature" ) // KeyValue represents any key/value implementation. @@ -75,7 +76,7 @@ func GetUpdatesFromNamespace(nsTx *protoblocktx.TxNamespace) *protosigverifierse } // ParseNamespacePolicyItem parses policy item to a namespace policy. -func ParseNamespacePolicyItem(pd *protoblocktx.PolicyItem) (*protoblocktx.NamespacePolicy, error) { +func ParseNamespacePolicyItem(pd *protoblocktx.PolicyItem) (*signature.NsVerifier, error) { if err := validateNamespaceIDInPolicy(pd.Namespace); err != nil { return nil, err } @@ -84,7 +85,7 @@ func ParseNamespacePolicyItem(pd *protoblocktx.PolicyItem) (*protoblocktx.Namesp if err != nil { return nil, errors.Wrap(err, "failed to unmarshal namespace policy") } - return p, nil + return signature.NewNsVerifier(p.Scheme, p.PublicKey) } // validateNamespaceIDInPolicy checks that a given namespace fulfills namespace naming conventions. @@ -113,7 +114,7 @@ func ValidateNamespaceID(nsID string) error { } // ParsePolicyFromConfigTx parses the meta namespace policy from a config transaction. -func ParsePolicyFromConfigTx(value []byte) (*protoblocktx.NamespacePolicy, error) { +func ParsePolicyFromConfigTx(value []byte) (*signature.NsVerifier, error) { envelope, err := protoutil.UnmarshalEnvelope(value) if err != nil { return nil, errors.Wrap(err, "error unmarshalling envelope") @@ -131,11 +132,8 @@ func ParsePolicyFromConfigTx(value []byte) (*protoblocktx.NamespacePolicy, error return nil, errors.New("application configuration of incorrect type") } key := acx.MetaNamespaceVerificationKey() - return &protoblocktx.NamespacePolicy{ - PublicKey: key.KeyMaterial, - // We use existing proto here to avoid introducing new ones. - // So we encode the key schema as the identifier. - // This will be replaced in the future with a generic policy mechanism. - Scheme: key.KeyIdentifier, - }, nil + // We use existing proto here to avoid introducing new ones. + // So we encode the key schema as the identifier. + // This will be replaced in the future with a generic policy mechanism. + return signature.NewNsVerifier(key.KeyIdentifier, key.KeyMaterial) } diff --git a/service/verifier/test_exports.go b/service/verifier/test_exports.go deleted file mode 100644 index f9718baf..00000000 --- a/service/verifier/test_exports.go +++ /dev/null @@ -1,335 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package verifier - -import ( - "google.golang.org/protobuf/proto" - - "github.com/hyperledger/fabric-x-committer/api/protoblocktx" - "github.com/hyperledger/fabric-x-committer/api/types" -) - -var ns1 = "1" - -// BadTxFormatTestCases holds the test cases needed to test bad transaction formats. -var BadTxFormatTestCases = []struct { - Tx *protoblocktx.Tx - ExpectedStatus protoblocktx.Status -}{ - { - Tx: &protoblocktx.Tx{ - Id: "", - }, - ExpectedStatus: protoblocktx.Status_ABORTED_MISSING_TXID, - }, - { - Tx: &protoblocktx.Tx{ - Id: "empty namespaces", - }, - ExpectedStatus: protoblocktx.Status_ABORTED_EMPTY_NAMESPACES, - }, - { - Tx: &protoblocktx.Tx{ - Id: "invalid signature", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_SIGNATURE_INVALID, - }, - { - Tx: &protoblocktx.Tx{ - Id: "namespace id is invalid in tx", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: "//", - }, - }, - Signatures: [][]byte{ - []byte("dummy"), - }, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_NAMESPACE_ID_INVALID, - }, - { - Tx: &protoblocktx.Tx{ - Id: "no writes", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadsOnly: []*protoblocktx.Read{ - { - Key: []byte("k1"), - }, - }, - }, - }, - Signatures: [][]byte{ - []byte("dummy"), - }, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_NO_WRITES, - }, - { - Tx: &protoblocktx.Tx{ - Id: "namespace id is invalid in metaNs tx", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{ - { - Key: []byte("key"), - }, - }, - }, - { - NsId: types.MetaNamespaceID, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{ - { - Key: []byte("/\\"), - }, - }, - }, - }, - Signatures: [][]byte{ - []byte("dummy"), - []byte("dummy"), - }, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_NAMESPACE_ID_INVALID, - }, - { - Tx: &protoblocktx.Tx{ - Id: "namespace policy is invalid in metaNs tx", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{ - { - Key: []byte("key"), - }, - }, - }, - { - NsId: types.MetaNamespaceID, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{ - { - Key: []byte("2"), - Value: []byte("value"), - }, - }, - }, - }, - Signatures: [][]byte{ - []byte("dummy"), - []byte("dummy"), - }, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_NAMESPACE_POLICY_INVALID, - }, - { - Tx: &protoblocktx.Tx{ - Id: "duplicate namespace", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{ - { - Key: []byte("key"), - }, - }, - }, - { - NsId: types.MetaNamespaceID, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{ - { - Key: []byte("2"), - Value: defaultNsPolicy(), - }, - }, - }, - { - NsId: ns1, - NsVersion: 0, - }, - }, - Signatures: [][]byte{ - []byte("dummy"), - []byte("dummy"), - []byte("dummy"), - }, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE, - }, - { - Tx: &protoblocktx.Tx{ - Id: "blind writes not allowed in metaNs tx", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{ - { - Key: []byte("key"), - }, - }, - }, - { - NsId: types.MetaNamespaceID, - NsVersion: 0, - BlindWrites: []*protoblocktx.Write{ - { - Key: []byte("2"), - Value: defaultNsPolicy(), - }, - }, - }, - }, - Signatures: [][]byte{ - []byte("dummy"), - []byte("dummy"), - }, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_BLIND_WRITES_NOT_ALLOWED, - }, - { - Tx: &protoblocktx.Tx{ - Id: "nil key in ReadOnly", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadsOnly: []*protoblocktx.Read{{Key: nil}}, - ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_NIL_KEY, - }, - { - Tx: &protoblocktx.Tx{ - Id: "nil key in ReadWrites", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{{Key: nil}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_NIL_KEY, - }, - { - Tx: &protoblocktx.Tx{ - Id: "nil key in BlindWrites", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - BlindWrites: []*protoblocktx.Write{{Key: nil}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_NIL_KEY, - }, - { - Tx: &protoblocktx.Tx{ - Id: "duplicate key within ReadOnly", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadsOnly: []*protoblocktx.Read{{Key: []byte("key1")}, {Key: []byte("key1")}}, - ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("1")}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET, - }, - { - Tx: &protoblocktx.Tx{ - Id: "duplicate key within ReadWrite", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("key1")}, {Key: []byte("key1")}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET, - }, - { - Tx: &protoblocktx.Tx{ - Id: "duplicate key within BlindWrites", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - BlindWrites: []*protoblocktx.Write{{Key: []byte("key1")}, {Key: []byte("key1")}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET, - }, - { - Tx: &protoblocktx.Tx{ - Id: "duplicate key across ReadOnly and ReadWrite", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadsOnly: []*protoblocktx.Read{{Key: []byte("key1")}}, - ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("key1")}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET, - }, - { - Tx: &protoblocktx.Tx{ - Id: "duplicate key across ReadWrite and BlindWrites", - Namespaces: []*protoblocktx.TxNamespace{ - { - NsId: ns1, - NsVersion: 0, - ReadWrites: []*protoblocktx.ReadWrite{{Key: []byte("key1")}}, - BlindWrites: []*protoblocktx.Write{{Key: []byte("key1")}}, - }, - }, - Signatures: [][]byte{[]byte("dummy")}, - }, - ExpectedStatus: protoblocktx.Status_ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET, - }, -} - -func defaultNsPolicy() []byte { - nsPolicy, _ := proto.Marshal(&protoblocktx.NamespacePolicy{ - Scheme: "ECDSA", - PublicKey: []byte("publicKey"), - }) - return nsPolicy -} diff --git a/service/verifier/verifier_server.go b/service/verifier/verifier_server.go index cd22a4dd..1b46dd69 100644 --- a/service/verifier/verifier_server.go +++ b/service/verifier/verifier_server.go @@ -15,7 +15,6 @@ import ( "google.golang.org/grpc/health" healthgrpc "google.golang.org/grpc/health/grpc_health_v1" - "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/api/protosigverifierservice" "github.com/hyperledger/fabric-x-committer/utils/channel" "github.com/hyperledger/fabric-x-committer/utils/connection" @@ -32,8 +31,6 @@ type Server struct { healthcheck *health.Server } -const retValid = protoblocktx.Status_COMMITTED - var ( logger = logging.New("verifier") @@ -117,12 +114,16 @@ func (s *Server) handleInputs( return errors.Wrap(rpcErr, "stream ended") } logger.Debugf("Received input from client with %v requests", len(batch.Requests)) + + // Update policies if included in the batch. err := executor.verifier.updatePolicies(batch.Update) if err != nil { return errors.Join(ErrUpdatePolicies, err) } promutil.AddToCounter(s.metrics.VerifierServerInTxs, len(batch.Requests)) promutil.AddToGauge(s.metrics.ActiveRequests, len(batch.Requests)) + + // Pass verification requests for processing. for _, r := range batch.Requests { if ok := input.Write(r); !ok { return errors.Wrap(stream.Context().Err(), "context ended") diff --git a/service/verifier/verifier_server_test.go b/service/verifier/verifier_server_test.go index 85b20509..e34acb61 100644 --- a/service/verifier/verifier_server_test.go +++ b/service/verifier/verifier_server_test.go @@ -119,31 +119,33 @@ func TestMinimalInput(t *testing.T) { require.Len(t, ret, 3) } -func TestBadTxFormat(t *testing.T) { +func TestBadSignature(t *testing.T) { t.Parallel() - test.FailHandler(t) c := newTestState(t, defaultConfigQuickCutoff()) - ctx, cancel := context.WithTimeout(t.Context(), testTimeout) - t.Cleanup(cancel) - stream, _ := c.Client.StartStream(ctx) + stream, err := c.Client.StartStream(t.Context()) + require.NoError(t, err) update, _ := defaultUpdate(t) - err := stream.Send(&protosigverifierservice.RequestBatch{Update: update}) + err = stream.Send(&protosigverifierservice.RequestBatch{Update: update}) require.NoError(t, err) - blockNumber := uint64(1) - for _, tt := range BadTxFormatTestCases { //nolint:paralleltest - t.Run(tt.Tx.Id, func(t *testing.T) { - requireTestCase(t, stream, &testCase{ - blkNum: blockNumber, - txNum: 0, - tx: tt.Tx, - expectedStatus: tt.ExpectedStatus, - }) - blockNumber++ - }) - } + requireTestCase(t, stream, &testCase{ + blkNum: 1, + txNum: 0, + tx: &protoblocktx.Tx{ + Id: "1", + Namespaces: []*protoblocktx.TxNamespace{{ + NsId: "1", + NsVersion: 0, + ReadWrites: []*protoblocktx.ReadWrite{ + {Key: make([]byte, 0)}, + }, + }}, + Signatures: [][]byte{{0, 1, 2}}, + }, + expectedStatus: protoblocktx.Status_ABORTED_SIGNATURE_INVALID, + }) } func TestUpdatePolicies(t *testing.T) { diff --git a/service/verifier/verify.go b/service/verifier/verify.go index 42213d5f..dfbc8fca 100644 --- a/service/verifier/verify.go +++ b/service/verifier/verify.go @@ -7,19 +7,17 @@ SPDX-License-Identifier: Apache-2.0 package verifier import ( - "encoding/json" "maps" "slices" "sync/atomic" - "unicode/utf8" "github.com/cockroachdb/errors" - "go.uber.org/zap/zapcore" "github.com/hyperledger/fabric-x-committer/api/protoblocktx" "github.com/hyperledger/fabric-x-committer/api/protosigverifierservice" "github.com/hyperledger/fabric-x-committer/api/types" "github.com/hyperledger/fabric-x-committer/service/verifier/policy" + "github.com/hyperledger/fabric-x-committer/utils" "github.com/hyperledger/fabric-x-committer/utils/signature" ) @@ -47,73 +45,58 @@ func (v *verifier) updatePolicies( // While it is unlikely that policy parsing would fail at this stage, it could happen // if the stored policy in the database is corrupted or maliciously altered, or if there is a // bug in the committer that modifies the policy bytes. - newPolicies, err := parsePolicies(update) + newVerifiers, err := parsePolicies(update) if err != nil { - return err + return errors.Join(ErrUpdatePolicies, err) } - newVerifiers := make(map[string]*signature.NsVerifier, len(newPolicies)) - for ns, key := range newPolicies { - nsVerifier, err := signature.NewNsVerifier(key.GetScheme(), key.GetPublicKey()) - if err != nil { - return errors.Join(ErrUpdatePolicies, err) - } - newVerifiers[ns] = nsVerifier - } defer logger.Infof("New verification policies for namespaces %v", slices.Collect(maps.Keys(newVerifiers))) - for k, v := range *v.verifiers.Load() { + for k, nsVerifier := range *v.verifiers.Load() { if _, ok := newVerifiers[k]; !ok { - newVerifiers[k] = v + newVerifiers[k] = nsVerifier } } v.verifiers.Store(&newVerifiers) return nil } -func parsePolicies(update *protosigverifierservice.Update) (map[string]*protoblocktx.NamespacePolicy, error) { - newPolicies := make(map[string]*protoblocktx.NamespacePolicy) +func parsePolicies(update *protosigverifierservice.Update) (map[string]*signature.NsVerifier, error) { + newPolicies := make(map[string]*signature.NsVerifier) if update.Config != nil { - key, err := policy.ParsePolicyFromConfigTx(update.Config.Envelope) + nsVerifier, err := policy.ParsePolicyFromConfigTx(update.Config.Envelope) if err != nil { return nil, errors.Join(ErrUpdatePolicies, err) } - newPolicies[types.MetaNamespaceID] = key + newPolicies[types.MetaNamespaceID] = nsVerifier } if update.NamespacePolicies != nil { for _, pd := range update.NamespacePolicies.Policies { - key, err := policy.ParseNamespacePolicyItem(pd) + nsVerifier, err := policy.ParseNamespacePolicyItem(pd) if err != nil { return nil, errors.Join(ErrUpdatePolicies, err) } - newPolicies[pd.Namespace] = key + newPolicies[pd.Namespace] = nsVerifier } } return newPolicies, nil } func (v *verifier) verifyRequest(request *protosigverifierservice.Request) *protosigverifierservice.Response { - debug(request) - return &protosigverifierservice.Response{ + logger.Debugf("Validating TX [%d:%d]: %s", request.BlockNum, request.TxNum, &utils.LazyJSON{O: request.Tx}) + response := &protosigverifierservice.Response{ TxId: request.Tx.Id, BlockNum: request.BlockNum, TxNum: request.TxNum, - Status: v.verifyTX(request.Tx), - } -} - -func (v *verifier) verifyTX(tx *protoblocktx.Tx) protoblocktx.Status { - if status := verifyTxForm(tx); status != retValid { - return status + Status: protoblocktx.Status_COMMITTED, } - // The verifiers might temporarily retain the old map while updatePolicies has already set a new one. // This is acceptable, provided the coordinator sends the validation status to the dependency graph // after updating the policies in the verifier. // This ensures that dependent data transactions on these updated namespaces always use the map // containing the latest policy. verifiers := *v.verifiers.Load() - for nsIndex, ns := range tx.Namespaces { + for nsIndex, ns := range request.Tx.Namespaces { if ns.NsId == types.ConfigNamespaceID { // Configuration TX is not signed in the same manner as application TX. // Its signatures are verified by the ordering service. @@ -121,7 +104,9 @@ func (v *verifier) verifyTX(tx *protoblocktx.Tx) protoblocktx.Status { } nsVerifier, ok := verifiers[ns.NsId] if !ok { - return protoblocktx.Status_ABORTED_SIGNATURE_INVALID + logger.Debugf("No verifier for namespace: '%v'", ns.NsId) + response.Status = protoblocktx.Status_ABORTED_SIGNATURE_INVALID + return response } // NOTE: We do not compare the namespace version in the transaction @@ -133,158 +118,11 @@ func (v *verifier) verifyTX(tx *protoblocktx.Tx) protoblocktx.Status { // the signatures are valid, the validator-committer service would // still mark the transaction as invalid due to an MVCC conflict on the // namespace version, which would reflect the correct validation status. - if err := nsVerifier.VerifyNs(tx, nsIndex); err != nil { - logger.Debugf("Invalid signature found: %v, namespace id: %v", tx.GetId(), ns.NsId) - return protoblocktx.Status_ABORTED_SIGNATURE_INVALID - } - } - - return retValid -} - -func verifyTxForm(tx *protoblocktx.Tx) protoblocktx.Status { - if tx.Id == "" { - return protoblocktx.Status_ABORTED_MISSING_TXID - } - - if !utf8.ValidString(tx.Id) { - // ASN.1. Marshalling only supports valid UTF8 strings. - // This case is unlikely as the message received via protobuf message which also only support - // valid UTF8 strings. - // Thus, we do not create a designated status for such error. - return protoblocktx.Status_ABORTED_MISSING_TXID - } - - if len(tx.Namespaces) == 0 { - return protoblocktx.Status_ABORTED_EMPTY_NAMESPACES - } - - if len(tx.Namespaces) != len(tx.Signatures) { - return protoblocktx.Status_ABORTED_SIGNATURE_INVALID - } - - nsIDs := make(map[string]any) - for _, ns := range tx.Namespaces { - if policy.ValidateNamespaceID(ns.NsId) != nil { - return protoblocktx.Status_ABORTED_NAMESPACE_ID_INVALID - } - if _, ok := nsIDs[ns.NsId]; ok { - return protoblocktx.Status_ABORTED_DUPLICATE_NAMESPACE - } - - for _, check := range []func(ns *protoblocktx.TxNamespace) protoblocktx.Status{ - checkNamespaceFormation, checkMetaNamespace, checkConfigNamespace, - } { - if status := check(ns); status != retValid { - return status - } - } - nsIDs[ns.NsId] = nil - } - return retValid -} - -func checkNamespaceFormation(ns *protoblocktx.TxNamespace) protoblocktx.Status { - if len(ns.ReadWrites) == 0 && len(ns.BlindWrites) == 0 { - return protoblocktx.Status_ABORTED_NO_WRITES - } - - keys := make([][]byte, 0, len(ns.ReadsOnly)+len(ns.ReadWrites)+len(ns.BlindWrites)) - for _, r := range ns.ReadsOnly { - keys = append(keys, r.Key) - } - for _, r := range ns.ReadWrites { - keys = append(keys, r.Key) - } - for _, r := range ns.BlindWrites { - keys = append(keys, r.Key) - } - - return checkKeys(keys) -} - -func checkMetaNamespace(txNs *protoblocktx.TxNamespace) protoblocktx.Status { - if txNs.NsId != types.MetaNamespaceID { - return retValid - } - if len(txNs.BlindWrites) > 0 { - return protoblocktx.Status_ABORTED_BLIND_WRITES_NOT_ALLOWED - } - - nsUpdate := make(map[string]any) - u := policy.GetUpdatesFromNamespace(txNs) - if u == nil { - return retValid - } - for _, pd := range u.NamespacePolicies.Policies { - _, err := policy.ParseNamespacePolicyItem(pd) - if err != nil { - if errors.Is(err, policy.ErrInvalidNamespaceID) { - return protoblocktx.Status_ABORTED_NAMESPACE_ID_INVALID - } - return protoblocktx.Status_ABORTED_NAMESPACE_POLICY_INVALID - } - // The meta-namespace is updated only via blind-write. - if pd.Namespace == types.MetaNamespaceID { - return protoblocktx.Status_ABORTED_NAMESPACE_POLICY_INVALID - } - if _, ok := nsUpdate[pd.Namespace]; ok { - return protoblocktx.Status_ABORTED_NAMESPACE_POLICY_INVALID - } - nsUpdate[pd.Namespace] = nil - } - - return retValid -} - -// checkConfigNamespace theoretically, this method is redundant as a config namespace TX -// is only generated by the sidecar, and never by a user. And its content was already -// verified by the Orderer. -// However, a bug might cause us to commit un-parsable config TX, so we validate it anyway. -// We return ABORTED_NAMESPACE_ID_INVALID for errors as we assume a client submitted a config -// transaction. -func checkConfigNamespace(txNs *protoblocktx.TxNamespace) protoblocktx.Status { - if txNs.NsId != types.ConfigNamespaceID { - return retValid - } - if len(txNs.ReadWrites) > 0 || len(txNs.BlindWrites) != 1 { - return protoblocktx.Status_ABORTED_NAMESPACE_ID_INVALID - } - if string(txNs.BlindWrites[0].Key) != types.ConfigKey { - return protoblocktx.Status_ABORTED_NAMESPACE_ID_INVALID - } - _, err := policy.ParsePolicyFromConfigTx(txNs.BlindWrites[0].Value) - if err != nil { - return protoblocktx.Status_ABORTED_NAMESPACE_ID_INVALID - } - return retValid -} - -func checkKeys(keys [][]byte) protoblocktx.Status { - seenKeys := make(map[string]any, len(keys)) - for _, k := range keys { - if k == nil { - return protoblocktx.Status_ABORTED_NIL_KEY + if err := nsVerifier.VerifyNs(request.Tx, nsIndex); err != nil { + logger.Debugf("Invalid signature found: '%v', namespace id: '%v'", request.Tx.Id, ns.NsId) + response.Status = protoblocktx.Status_ABORTED_SIGNATURE_INVALID + return response } - - sK := string(k) - if _, ok := seenKeys[sK]; ok { - return protoblocktx.Status_ABORTED_DUPLICATE_KEY_IN_READ_WRITE_SET - } - seenKeys[sK] = nil - } - - return retValid -} - -func debug(request *protosigverifierservice.Request) { - if logger.Level() > zapcore.DebugLevel { - return - } - data, err := json.Marshal(request.Tx) - if err != nil { - logger.Debugf("Failed to marshal TX [%d:%d]", request.BlockNum, request.TxNum) - return } - logger.Debugf("Validating TX [%d:%d]:\n%s", request.BlockNum, request.TxNum, string(data)) + return response } diff --git a/service/verifier/verify_test.go b/service/verifier/verify_test.go deleted file mode 100644 index 8af55271..00000000 --- a/service/verifier/verify_test.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package verifier - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/hyperledger/fabric-x-committer/api/protoblocktx" - "github.com/hyperledger/fabric-x-committer/loadgen/workload" - "github.com/hyperledger/fabric-x-committer/utils/logging" -) - -func TestVerifyBadTxForm(t *testing.T) { - t.Parallel() - for _, tt := range BadTxFormatTestCases { - tx := tt.Tx - expectedStatus := tt.ExpectedStatus - if tt.ExpectedStatus == protoblocktx.Status_ABORTED_SIGNATURE_INVALID { - // verifyTxForm doesn't check for signature. - expectedStatus = protoblocktx.Status_COMMITTED - } - t.Run(tt.Tx.Id, func(t *testing.T) { - t.Parallel() - actualStatus := verifyTxForm(tx) - require.Equal(t, expectedStatus, actualStatus) - }) - } -} - -func BenchmarkVerifyForm(b *testing.B) { - logging.SetupWithConfig(&logging.Config{Enabled: false}) - txs := workload.GenerateTransactions(b, workload.DefaultProfile(8), b.N) - - var status protoblocktx.Status - b.ResetTimer() - for _, tx := range txs { - status = verifyTxForm(tx) - } - b.StopTimer() - require.NotEqual(b, protoblocktx.Status_NOT_VALIDATED, status) -} diff --git a/utils/serialization/envelope_wrapper.go b/utils/serialization/envelope_wrapper.go index 254fbd95..3c674e11 100644 --- a/utils/serialization/envelope_wrapper.go +++ b/utils/serialization/envelope_wrapper.go @@ -12,6 +12,8 @@ import ( "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric/protoutil" "google.golang.org/protobuf/proto" + + "github.com/hyperledger/fabric-x-committer/api/protoblocktx" ) // NoOpSigner supports unsigned envelopes. @@ -72,7 +74,12 @@ func CreateEnvelope( } signatureHeader := protoutil.NewSignatureHeaderOrPanic(signer) channelHeader := protoutil.MakeChannelHeader(common.HeaderType_MESSAGE, 0, channelID, 0) - channelHeader.TxId = protoutil.ComputeTxID(signatureHeader.Nonce, signatureHeader.Creator) + switch msg := protoMsg.(type) { + case *protoblocktx.Tx: + channelHeader.TxId = msg.Id + default: + channelHeader.TxId = protoutil.ComputeTxID(signatureHeader.Nonce, signatureHeader.Creator) + } payloadHeader := protoutil.MakePayloadHeader(channelHeader, signatureHeader) payload := WrapEnvelope(data, payloadHeader) diff --git a/utils/signature/verify.go b/utils/signature/verify.go index 19c7070a..5335b2ad 100644 --- a/utils/signature/verify.go +++ b/utils/signature/verify.go @@ -21,27 +21,31 @@ type DigestVerifier interface { // NsVerifier verifies a given namespace. type NsVerifier struct { - verifier DigestVerifier + verifier DigestVerifier + Scheme Scheme + PublicKey PublicKey } // NewNsVerifier creates a new namespace verifier according to the implementation scheme. -func NewNsVerifier(scheme Scheme, key []byte) (*NsVerifier, error) { - scheme = strings.ToUpper(scheme) +func NewNsVerifier(scheme Scheme, key PublicKey) (*NsVerifier, error) { + res := &NsVerifier{ + Scheme: strings.ToUpper(scheme), + PublicKey: key, + } var err error - var v DigestVerifier - switch scheme { + switch res.Scheme { case NoScheme, "": - v = nil + res.verifier = nil case Ecdsa: - v, err = NewEcdsaVerifier(key) + res.verifier, err = NewEcdsaVerifier(key) case Bls: - v, err = NewBLSVerifier(key) + res.verifier, err = NewBLSVerifier(key) case Eddsa: - v = &EdDSAVerifier{PublicKey: key} + res.verifier = &EdDSAVerifier{PublicKey: key} default: return nil, errors.Newf("scheme '%v' not supported", scheme) } - return &NsVerifier{verifier: v}, errors.Wrap(err, "failed creating verifier") + return res, errors.Wrap(err, "failed creating verifier") } // VerifyNs verifies a transaction's namespace signature. diff --git a/utils/utils.go b/utils/utils.go index bc3e8322..e719ac67 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -75,3 +75,12 @@ func ProcessErr(err error, msg string) error { } return nil } + +// CountAppearances returns the number of appearances each item have. +func CountAppearances[T comparable](items []T) map[T]int { + countMap := make(map[T]int) + for _, item := range items { + countMap[item]++ + } + return countMap +}