diff --git a/internal/api/handler/default.go b/internal/api/handler/default.go index 7bb3c44d4..0b31e84e2 100644 --- a/internal/api/handler/default.go +++ b/internal/api/handler/default.go @@ -31,9 +31,9 @@ import ( ) const ( - maxTimeout = 30 - maxTimeoutSecondsDefault = 5 - mapExpiryTimeDefault = 24 * time.Hour + maxTimeout = 30 + timeoutSecondsDefault = 5 + mapExpiryTimeDefault = 24 * time.Hour ) var ( @@ -53,11 +53,17 @@ type ArcDefaultHandler struct { rejectedCallbackURLSubstrings []string txFinder validator.TxFinderI mapExpiryTime time.Duration + defaultTimeout time.Duration mrVerifier validator.MerkleVerifierI tracingEnabled bool tracingAttributes []attribute.KeyValue } +type PostResponse struct { + StatusCode int + response interface{} +} + func WithNow(nowFunc func() time.Time) func(*ArcDefaultHandler) { return func(p *ArcDefaultHandler) { p.now = nowFunc @@ -70,6 +76,12 @@ func WithCallbackURLRestrictions(rejectedCallbackURLSubstrings []string) func(*A } } +func WithServerMaxTimeoutDefault(timeout time.Duration) func(*ArcDefaultHandler) { + return func(s *ArcDefaultHandler) { + s.defaultTimeout = timeout + } +} + func WithCacheExpiryTime(d time.Duration) func(*ArcDefaultHandler) { return func(p *ArcDefaultHandler) { p.mapExpiryTime = d @@ -109,6 +121,7 @@ func NewDefault( mrVerifier: mr, txFinder: cachedFinder, mapExpiryTime: mapExpiryTimeDefault, + defaultTimeout: timeoutSecondsDefault * time.Second, } // apply options @@ -180,11 +193,10 @@ func calcFeesFromBSVPerKB(feePerKB float64) (uint64, uint64) { return satoshis, bytes } -// POSTTransaction ... -func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTransactionParams) (err error) { - reqCtx := ctx.Request().Context() +func (m ArcDefaultHandler) postTransaction(ctx echo.Context, params api.POSTTransactionParams) PostResponse { + var err error - reqCtx, span := tracing.StartTracing(reqCtx, "POSTTransaction", m.tracingEnabled, m.tracingAttributes...) + reqCtx, span := tracing.StartTracing(ctx.Request().Context(), "POSTTransaction", m.tracingEnabled, m.tracingAttributes...) defer func() { tracing.EndTracing(span, err) }() @@ -192,7 +204,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran transactionOptions, err := getTransactionOptions(params, m.rejectedCallbackURLSubstrings) if err != nil { e := api.NewErrorFields(api.ErrStatusBadRequest, err.Error()) - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } txHex, err := parseTransactionFromRequest(ctx.Request()) @@ -202,8 +214,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran attr := e.GetSpanAttributes() span.SetAttributes(attr...) } - - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } // Now we check if we have the transaction present in db, if so we skip validation (as we must have already validated it) @@ -215,7 +226,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran span.SetAttributes(attr...) } // if an error is returned, the processing failed - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } // check if we already have the transaction in db (so no need to validate) @@ -224,7 +235,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran // if we have error which is NOT ErrTransactionNotFound, return err if !errors.Is(err, metamorph.ErrTransactionNotFound) { e := api.NewErrorFields(api.ErrStatusGeneric, err.Error()) - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } } else { // if we have found transaction skip the validation @@ -240,7 +251,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran // if LastSubmitted doesn't need to be updated and we already have provided callbacks - skip everything and return current status if time.Since(tx.LastSubmitted.AsTime()) < m.mapExpiryTime && callbackAlreadyExists { - return ctx.JSON(int(api.StatusOK), &api.TransactionResponse{ + return PostResponse{int(api.StatusOK), &api.TransactionResponse{ Status: int(api.StatusOK), Title: "OK", BlockHash: &tx.BlockHash, @@ -251,7 +262,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran Timestamp: m.now(), Txid: txIDs[0], MerklePath: &tx.MerklePath, - }) + }} } } @@ -262,7 +273,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran span.SetAttributes(attr...) } // if an error is returned, the processing failed - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } if len(fails) > 0 { @@ -272,19 +283,34 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran attr := e.GetSpanAttributes() span.SetAttributes(attr...) } - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } sizingCtx := context.WithValue(reqCtx, ContextSizings, prepareSizingInfo(txs)) ctx.SetRequest(ctx.Request().WithContext(sizingCtx)) - response := successes[0] + res := successes[0] if span != nil { - span.SetAttributes(attribute.String("status", string(response.TxStatus))) + span.SetAttributes(attribute.String("status", string(res.TxStatus))) + } + + return PostResponse{res.Status, res} +} + +// POSTTransaction ... +func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTransactionParams) (err error) { + timeout := m.defaultTimeout + if params.XMaxTimeout != nil && *params.XMaxTimeout < maxTimeout { + timeout = time.Second * time.Duration(*params.XMaxTimeout) } - return ctx.JSON(response.Status, response) + timeoutCtx, cancel := context.WithTimeout(ctx.Request().Context(), timeout) + ctx.SetRequest(ctx.Request().WithContext(timeoutCtx)) + defer cancel() + + postResponse := m.postTransaction(ctx, params) + return ctx.JSON(postResponse.StatusCode, postResponse.response) } // GETTransactionStatus ... @@ -332,11 +358,9 @@ func (m ArcDefaultHandler) GETTransactionStatus(ctx echo.Context, id string) (er }) } -// POSTTransactions ... -func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTransactionsParams) (err error) { - reqCtx := ctx.Request().Context() - - reqCtx, span := tracing.StartTracing(reqCtx, "POSTTransactions", m.tracingEnabled, m.tracingAttributes...) +func (m ArcDefaultHandler) postTransactions(ctx echo.Context, params api.POSTTransactionsParams) PostResponse { + var err error + reqCtx, span := tracing.StartTracing(ctx.Request().Context(), "POSTTransactions", m.tracingEnabled, m.tracingAttributes...) defer func() { tracing.EndTracing(span, err) }() @@ -349,7 +373,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra attr := e.GetSpanAttributes() span.SetAttributes(attr...) } - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } txsHex, err := parseTransactionsFromRequest(ctx.Request()) @@ -359,7 +383,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra attr := e.GetSpanAttributes() span.SetAttributes(attr...) } - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } // Now we check if we have the transactions present in db, if so we skip validation (as we must have already validated them) @@ -371,7 +395,8 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra span.SetAttributes(attr...) } // if an error is returned, the processing failed - return ctx.JSON(e.Status, e) + + return PostResponse{e.Status, e} } // check if we already have the transactions in db (so no need to validate) @@ -381,7 +406,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra // if we have error which is NOT ErrTransactionNotFound, return err if !errors.Is(err, metamorph.ErrTransactionNotFound) { e := api.NewErrorFields(api.ErrStatusGeneric, err.Error()) - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } } else if len(txStatuses) == len(txIDs) { // if we have found all the transactions, skip the validation @@ -427,7 +452,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra for _, o := range successes { responses = append(responses, o) } - return ctx.JSON(int(api.StatusOK), responses) + return PostResponse{int(api.StatusOK), responses} } txs, successes, fails, e := m.processTransactions(reqCtx, txsHex, transactionOptions) @@ -436,7 +461,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra attr := e.GetSpanAttributes() span.SetAttributes(attr...) } - return ctx.JSON(e.Status, e) + return PostResponse{e.Status, e} } sizingCtx := context.WithValue(reqCtx, ContextSizings, prepareSizingInfo(txs)) @@ -453,7 +478,22 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra responses = append(responses, fo) } - return ctx.JSON(int(api.StatusOK), responses) + return PostResponse{int(api.StatusOK), responses} +} + +// POSTTransactions ... +func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTransactionsParams) (err error) { + timeout := m.defaultTimeout + if params.XMaxTimeout != nil && *params.XMaxTimeout < maxTimeout { + timeout = time.Second * time.Duration(*params.XMaxTimeout) + } + + timeoutCtx, cancel := context.WithTimeout(ctx.Request().Context(), timeout) + ctx.SetRequest(ctx.Request().WithContext(timeoutCtx)) + defer cancel() + + postResponse := m.postTransactions(ctx, params) + return ctx.JSON(postResponse.StatusCode, postResponse.response) } func getTransactionOptions(params api.POSTTransactionParams, rejectedCallbackURLSubstrings []string) (*metamorph.TransactionOptions, error) { @@ -475,9 +515,7 @@ func ValidateCallbackURL(callbackURL string, rejectedCallbackURLSubstrings []str } func getTransactionsOptions(params api.POSTTransactionsParams, rejectedCallbackURLSubstrings []string) (*metamorph.TransactionOptions, error) { - transactionOptions := &metamorph.TransactionOptions{ - MaxTimeout: maxTimeoutSecondsDefault, - } + transactionOptions := &metamorph.TransactionOptions{} if params.XCallbackUrl != nil { if err := ValidateCallbackURL(*params.XCallbackUrl, rejectedCallbackURLSubstrings); err != nil { return nil, err @@ -542,14 +580,6 @@ func getTransactionsOptions(params api.POSTTransactionsParams, rejectedCallbackU transactionOptions.SkipTxValidation = *params.XSkipTxValidation } - if params.XMaxTimeout != nil { - if *params.XMaxTimeout > maxTimeout { - return nil, ErrMaxTimeoutExceeded - } - - transactionOptions.MaxTimeout = *params.XMaxTimeout - } - if params.XFullStatusUpdates != nil { transactionOptions.FullStatusUpdates = *params.XFullStatusUpdates } @@ -733,6 +763,14 @@ func (m ArcDefaultHandler) submitTransactions(ctx context.Context, txs []*sdkTx. var submitStatuses []*metamorph.TransactionStatus + // to avoid false negatives first check if ctx is expired + select { + case <-ctx.Done(): + _, arcError := m.handleError(ctx, nil, context.DeadlineExceeded) + return nil, arcError + default: + } + if len(txs) == 1 { tx := txs[0] diff --git a/internal/api/handler/default_test.go b/internal/api/handler/default_test.go index 24ca5d1ca..627bde9be 100644 --- a/internal/api/handler/default_test.go +++ b/internal/api/handler/default_test.go @@ -1267,19 +1267,13 @@ func TestGetTransactionOptions(t *testing.T) { name: "no options", params: api.POSTTransactionParams{}, - expectedOptions: &metamorph.TransactionOptions{ - MaxTimeout: 5, - }, + expectedOptions: &metamorph.TransactionOptions{}, }, { - name: "max timeout", - params: api.POSTTransactionParams{ - XMaxTimeout: PtrTo(20), - }, + name: "max timeout", + params: api.POSTTransactionParams{}, - expectedOptions: &metamorph.TransactionOptions{ - MaxTimeout: 20, - }, + expectedOptions: &metamorph.TransactionOptions{}, }, { name: "valid callback url", @@ -1291,7 +1285,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ CallbackURL: "http://api.callme.com", CallbackToken: "1234", - MaxTimeout: 5, }, }, { @@ -1311,7 +1304,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_QUEUED, - MaxTimeout: 5, }, }, { @@ -1323,7 +1315,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 5, }, }, { @@ -1335,7 +1326,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_SENT_TO_NETWORK, - MaxTimeout: 5, }, }, { @@ -1347,7 +1337,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_ACCEPTED_BY_NETWORK, - MaxTimeout: 5, }, }, { @@ -1358,7 +1347,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_QUEUED, - MaxTimeout: 5, }, }, { @@ -1369,7 +1357,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 5, }, }, { @@ -1380,7 +1367,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_SENT_TO_NETWORK, - MaxTimeout: 5, }, }, { @@ -1391,7 +1377,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_ACCEPTED_BY_NETWORK, - MaxTimeout: 5, }, }, { @@ -1402,7 +1387,6 @@ func TestGetTransactionOptions(t *testing.T) { expectedOptions: &metamorph.TransactionOptions{ WaitForStatus: metamorph_api.Status_SEEN_ON_NETWORK, - MaxTimeout: 5, }, }, } diff --git a/internal/blocktx/blocktx_api/blocktx_api.pb.go b/internal/blocktx/blocktx_api/blocktx_api.pb.go index d0f317be2..ed33cc5cb 100644 --- a/internal/blocktx/blocktx_api/blocktx_api.pb.go +++ b/internal/blocktx/blocktx_api/blocktx_api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.29.1 +// protoc v5.29.3 // source: internal/blocktx/blocktx_api/blocktx_api.proto package blocktx_api diff --git a/internal/blocktx/blocktx_api/blocktx_api_grpc.pb.go b/internal/blocktx/blocktx_api/blocktx_api_grpc.pb.go index 74bd4aeeb..c31176459 100644 --- a/internal/blocktx/blocktx_api/blocktx_api_grpc.pb.go +++ b/internal/blocktx/blocktx_api/blocktx_api_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.1 +// - protoc v5.29.3 // source: internal/blocktx/blocktx_api/blocktx_api.proto package blocktx_api diff --git a/internal/callbacker/callbacker_api/callbacker_api.pb.go b/internal/callbacker/callbacker_api/callbacker_api.pb.go index 9604ddab1..f5d249cb0 100644 --- a/internal/callbacker/callbacker_api/callbacker_api.pb.go +++ b/internal/callbacker/callbacker_api/callbacker_api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.29.1 +// protoc v5.29.3 // source: internal/callbacker/callbacker_api/callbacker_api.proto package callbacker_api diff --git a/internal/callbacker/callbacker_api/callbacker_api_grpc.pb.go b/internal/callbacker/callbacker_api/callbacker_api_grpc.pb.go index 70dd13b85..0e888c07d 100644 --- a/internal/callbacker/callbacker_api/callbacker_api_grpc.pb.go +++ b/internal/callbacker/callbacker_api/callbacker_api_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.1 +// - protoc v5.29.3 // source: internal/callbacker/callbacker_api/callbacker_api.proto package callbacker_api diff --git a/internal/metamorph/client.go b/internal/metamorph/client.go index 94c5c5970..c9992d61d 100644 --- a/internal/metamorph/client.go +++ b/internal/metamorph/client.go @@ -11,10 +11,7 @@ import ( sdkTx "github.com/bitcoin-sv/go-sdk/transaction" "go.opentelemetry.io/otel/attribute" - "google.golang.org/genproto/googleapis/rpc/code" "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" @@ -24,8 +21,6 @@ import ( "github.com/bitcoin-sv/arc/internal/tracing" ) -const retryInterval = 300 * time.Millisecond - var ( ErrTransactionNotFound = errors.New("transaction not found") ) @@ -66,13 +61,6 @@ type Metamorph struct { now func() time.Time tracingEnabled bool tracingAttributes []attribute.KeyValue - maxTimeout time.Duration -} - -func WithClientMaxTimeoutDefault(d time.Duration) func(*Metamorph) { - return func(m *Metamorph) { - m.maxTimeout = d - } } func WithMqClient(mqClient MessageQueueClient) func(*Metamorph) { @@ -109,10 +97,9 @@ func WithClientTracer(attr ...attribute.KeyValue) func(s *Metamorph) { // NewClient creates a connection to a list of metamorph servers via gRPC. func NewClient(client metamorph_api.MetaMorphAPIClient, opts ...func(client *Metamorph)) *Metamorph { m := &Metamorph{ - client: client, - logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})), - now: time.Now, - maxTimeout: maxTimeoutDefault, + client: client, + logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})), + now: time.Now, } for _, opt := range opts { @@ -281,7 +268,6 @@ func (m *Metamorph) SubmitTransaction(ctx context.Context, tx *sdkTx.Transaction }() request := transactionRequest(tx.Bytes(), options) - if options.WaitForStatus == metamorph_api.Status_QUEUED && m.mqClient != nil { err = m.mqClient.PublishMarshal(ctx, SubmitTxTopic, request) if err != nil { @@ -295,58 +281,29 @@ func (m *Metamorph) SubmitTransaction(ctx context.Context, tx *sdkTx.Transaction }, nil } - var response *metamorph_api.TransactionStatus - var getStatusErr error - - // in case of error try PutTransaction until timeout expires - - maxTimeout := max(time.Duration(request.MaxTimeout)*time.Second, m.maxTimeout) - retryTicker := time.NewTicker(retryInterval) - defer retryTicker.Stop() - - timeoutTimer := time.NewTimer(maxTimeout) - - for { - response, err = m.client.PutTransaction(ctx, request) - if err == nil { - txStatus = &TransactionStatus{ - TxID: response.GetTxid(), - Status: response.GetStatus().String(), - ExtraInfo: response.GetRejectReason(), - CompetingTxs: response.GetCompetingTxs(), - BlockHash: response.GetBlockHash(), - BlockHeight: response.GetBlockHeight(), - MerklePath: response.GetMerklePath(), - Callbacks: response.GetCallbacks(), - Timestamp: m.now().Unix(), - } - break - } - - m.logger.ErrorContext(ctx, "Failed to put transaction", slog.String("hash", tx.TxID()), slog.String("err", err.Error())) + deadline, _ := ctx.Deadline() + // increase time to make sure that expiration happens from inside the metramorph function + newDeadline := deadline.Add(time.Second * 2) - if status.Code(err) == codes.Code(code.Code_DEADLINE_EXCEEDED) { - // if error is deadline exceeded, check tx status to avoid false negatives + // Create a new context with the updated deadline + newCtx, newCancel := context.WithDeadline(context.Background(), newDeadline) + defer newCancel() - fallbackCtx, fallbackCtxCancel := cloneCtxWithNewTimeout(ctx, time.Second) - txStatus, getStatusErr = m.GetTransactionStatus(fallbackCtx, tx.TxID()) - fallbackCtxCancel() - - if getStatusErr == nil { - break - } - m.logger.ErrorContext(ctx, "Failed to get transaction status", slog.String("hash", tx.TxID()), slog.String("err", getStatusErr.Error())) - } - - select { - case <-timeoutTimer.C: - return nil, err - - case <-retryTicker.C: - continue - } + response, err := m.client.PutTransaction(newCtx, request) + if err != nil { + return nil, err + } + txStatus = &TransactionStatus{ + TxID: response.GetTxid(), + Status: response.GetStatus().String(), + ExtraInfo: response.GetRejectReason(), + CompetingTxs: response.GetCompetingTxs(), + BlockHash: response.GetBlockHash(), + BlockHeight: response.GetBlockHeight(), + MerklePath: response.GetMerklePath(), + Callbacks: response.GetCallbacks(), + Timestamp: m.now().Unix(), } - return txStatus, nil } @@ -386,67 +343,31 @@ func (m *Metamorph) SubmitTransactions(ctx context.Context, txs sdkTx.Transactio } var responses *metamorph_api.TransactionStatuses - // in case of error try PutTransaction until timeout expires - - maxTimeout := max(time.Duration(in.Transactions[0].MaxTimeout)*time.Second, m.maxTimeout) - retryTicker := time.NewTicker(retryInterval) - defer retryTicker.Stop() - - timeoutTimer := time.NewTimer(maxTimeout) - - for { - responses, err = m.client.PutTransactions(ctx, in) - if err == nil { - for _, response := range responses.GetStatuses() { - txStatuses = append(txStatuses, &TransactionStatus{ - TxID: response.GetTxid(), - MerklePath: response.GetMerklePath(), - Status: response.GetStatus().String(), - ExtraInfo: response.GetRejectReason(), - CompetingTxs: response.GetCompetingTxs(), - BlockHash: response.GetBlockHash(), - BlockHeight: response.GetBlockHeight(), - Callbacks: response.GetCallbacks(), - Timestamp: m.now().Unix(), - }) - } - break - } - - m.logger.ErrorContext(ctx, "Failed to put transactions", slog.String("err", err.Error())) - - // if error is deadline exceeded, check tx status to avoid false negatives - if status.Code(err) == codes.Code(code.Code_DEADLINE_EXCEEDED) { - completedErrorFree := true - for _, tx := range txs { - // Todo: Create and use here client.GetTransactionStatuses rpc function - fallbackCtx, fallbackCtxCancel := cloneCtxWithNewTimeout(ctx, time.Second) - txStatus, getStatusErr := m.GetTransactionStatus(fallbackCtx, tx.TxID()) - fallbackCtxCancel() - - if getStatusErr != nil { - m.logger.ErrorContext(ctx, "Failed to get transaction status", slog.String("hash", tx.TxID()), slog.String("err", getStatusErr.Error())) - completedErrorFree = false - break - } - - txStatuses = append(txStatuses, txStatus) - } - - if completedErrorFree { - break - } - } + deadline, _ := ctx.Deadline() + // decrease time to get initial deadline + newDeadline := deadline.Add(time.Second * 5) - select { - case <-timeoutTimer.C: - return nil, err + // increase time to make sure that expiration happens from inside the metramorph function + newCtx, newCancel := context.WithDeadline(context.Background(), newDeadline) + defer newCancel() - case <-retryTicker.C: - continue - } + responses, err = m.client.PutTransactions(newCtx, in) + if err != nil { + return nil, err + } + for _, response := range responses.GetStatuses() { + txStatuses = append(txStatuses, &TransactionStatus{ + TxID: response.GetTxid(), + MerklePath: response.GetMerklePath(), + Status: response.GetStatus().String(), + ExtraInfo: response.GetRejectReason(), + CompetingTxs: response.GetCompetingTxs(), + BlockHash: response.GetBlockHash(), + BlockHeight: response.GetBlockHeight(), + Callbacks: response.GetCallbacks(), + Timestamp: m.now().Unix(), + }) } - return txStatuses, nil } @@ -486,16 +407,9 @@ func transactionRequest(rawTx []byte, options *TransactionOptions) *metamorph_ap CallbackBatch: options.CallbackBatch, WaitForStatus: options.WaitForStatus, FullStatusUpdates: options.FullStatusUpdates, - MaxTimeout: int64(options.MaxTimeout), } } -// creates a new context with all parent data, but ignores its timing out or cancellation -func cloneCtxWithNewTimeout(parent context.Context, timeout time.Duration) (context.Context, func()) { - ctx := context.WithoutCancel(parent) - return context.WithTimeout(ctx, timeout) -} - // TransactionOptions options passed from header when creating transactions. type TransactionOptions struct { ClientID string `json:"client_id"` @@ -508,7 +422,6 @@ type TransactionOptions struct { CumulativeFeeValidation bool `json:"X-CumulativeFeeValidation,omitempty"` WaitForStatus metamorph_api.Status `json:"wait_for_status,omitempty"` FullStatusUpdates bool `json:"full_status_updates,omitempty"` - MaxTimeout int `json:"max_timeout,omitempty"` } type Transaction struct { diff --git a/internal/metamorph/client_test.go b/internal/metamorph/client_test.go index 0a8838437..6cf65c1a2 100644 --- a/internal/metamorph/client_test.go +++ b/internal/metamorph/client_test.go @@ -3,13 +3,10 @@ package metamorph_test import ( "context" "errors" - "github.com/bitcoin-sv/arc/internal/metamorph" "testing" "time" - "google.golang.org/genproto/googleapis/rpc/code" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "github.com/bitcoin-sv/arc/internal/metamorph" "github.com/bitcoin-sv/arc/internal/metamorph/metamorph_api" @@ -136,7 +133,6 @@ func TestClient_SubmitTransaction(t *testing.T) { Txid: testdata.TX1Hash.String(), Status: metamorph_api.Status_RECEIVED, }, - putTxErr: status.New(codes.Code(code.Code_DEADLINE_EXCEEDED), errors.New("deadline exceeded").Error()).Err(), withMqClient: false, getTxStatus: &metamorph_api.TransactionStatus{ Txid: testdata.TX1Hash.String(), @@ -155,14 +151,17 @@ func TestClient_SubmitTransaction(t *testing.T) { WaitForStatus: metamorph_api.Status_RECEIVED, }, putTxStatus: &metamorph_api.TransactionStatus{ - Txid: testdata.TX1Hash.String(), - Status: metamorph_api.Status_RECEIVED, + Txid: testdata.TX1Hash.String(), + Status: metamorph_api.Status_RECEIVED, + TimedOut: true, }, - putTxErr: status.New(codes.Code(code.Code_DEADLINE_EXCEEDED), errors.New("deadline exceeded").Error()).Err(), withMqClient: false, getTxErr: errors.New("failed to get tx status"), - - expectedErrorStr: "rpc error: code = DeadlineExceeded desc = deadline exceeded", + expectedStatus: &metamorph.TransactionStatus{ + TxID: testdata.TX1Hash.String(), + Status: metamorph_api.Status_RECEIVED.String(), + Timestamp: now.Unix(), + }, }, { name: "wait for queued, with mq client", @@ -204,7 +203,6 @@ func TestClient_SubmitTransaction(t *testing.T) { opts := []func(client *metamorph.Metamorph){ metamorph.WithClientNow(func() time.Time { return now }), - metamorph.WithClientMaxTimeoutDefault(1 * time.Second), } if tc.withMqClient { mqClient := &apiMocks.MessageQueueClientMock{ @@ -218,7 +216,10 @@ func TestClient_SubmitTransaction(t *testing.T) { tx, err := sdkTx.NewTransactionFromHex(testdata.TX1RawString) // Then require.NoError(t, err) - txStatus, err := client.SubmitTransaction(context.Background(), tx, tc.options) + ctx := context.Background() + timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + txStatus, err := client.SubmitTransaction(timeoutCtx, tx, tc.options) require.Equal(t, tc.expectedStatus, txStatus) @@ -340,7 +341,6 @@ func TestClient_SubmitTransactions(t *testing.T) { }, }, }, - putTxErr: status.New(codes.Code(code.Code_DEADLINE_EXCEEDED), errors.New("deadline exceeded").Error()).Err(), withMqClient: false, getTxStatus: &metamorph_api.TransactionStatus{ Txid: testdata.TX1Hash.String(), @@ -386,12 +386,26 @@ func TestClient_SubmitTransactions(t *testing.T) { }, }, }, - putTxErr: status.New(codes.Code(code.Code_DEADLINE_EXCEEDED), errors.New("deadline exceeded").Error()).Err(), withMqClient: false, getTxErr: errors.New("failed to get tx status"), - expectedStatuses: nil, - expectedErrorStr: "rpc error: code = DeadlineExceeded desc = deadline exceeded", + expectedStatuses: []*metamorph.TransactionStatus{ + { + TxID: tx1.TxID(), + Status: metamorph_api.Status_RECEIVED.String(), + Timestamp: now.Unix(), + }, + { + TxID: tx2.TxID(), + Status: metamorph_api.Status_RECEIVED.String(), + Timestamp: now.Unix(), + }, + { + TxID: tx3.TxID(), + Status: metamorph_api.Status_RECEIVED.String(), + Timestamp: now.Unix(), + }, + }, }, { name: "wait for queued, with mq client", @@ -445,7 +459,6 @@ func TestClient_SubmitTransactions(t *testing.T) { opts := []func(client *metamorph.Metamorph){ metamorph.WithClientNow(func() time.Time { return now }), - metamorph.WithClientMaxTimeoutDefault(1 * time.Second), } if tc.withMqClient { mqClient := &apiMocks.MessageQueueClientMock{ @@ -458,7 +471,10 @@ func TestClient_SubmitTransactions(t *testing.T) { client := metamorph.NewClient(apiClient, opts...) // When - statuses, err := client.SubmitTransactions(context.Background(), sdkTx.Transactions{tx1, tx2, tx3}, tc.options) + ctx := context.Background() + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + statuses, err := client.SubmitTransactions(timeoutCtx, sdkTx.Transactions{tx1, tx2, tx3}, tc.options) // Then require.Equal(t, tc.expectedStatuses, statuses) diff --git a/internal/metamorph/metamorph_api/metamorph_api.pb.go b/internal/metamorph/metamorph_api/metamorph_api.pb.go index c7b47f85b..4b3c230b3 100644 --- a/internal/metamorph/metamorph_api/metamorph_api.pb.go +++ b/internal/metamorph/metamorph_api/metamorph_api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.29.1 +// protoc v5.29.3 // source: internal/metamorph/metamorph_api/metamorph_api.proto package metamorph_api @@ -197,7 +197,6 @@ type TransactionRequest struct { RawTx []byte `protobuf:"bytes,9,opt,name=raw_tx,json=rawTx,proto3" json:"raw_tx,omitempty"` WaitForStatus Status `protobuf:"varint,10,opt,name=wait_for_status,json=waitForStatus,proto3,enum=metamorph_api.Status" json:"wait_for_status,omitempty"` FullStatusUpdates bool `protobuf:"varint,11,opt,name=full_status_updates,json=fullStatusUpdates,proto3" json:"full_status_updates,omitempty"` - MaxTimeout int64 `protobuf:"varint,12,opt,name=max_timeout,json=maxTimeout,proto3" json:"max_timeout,omitempty"` EventId string `protobuf:"bytes,13,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` } @@ -311,13 +310,6 @@ func (x *TransactionRequest) GetFullStatusUpdates() bool { return false } -func (x *TransactionRequest) GetMaxTimeout() int64 { - if x != nil { - return x.MaxTimeout - } - return 0 -} - func (x *TransactionRequest) GetEventId() string { if x != nil { return x.EventId @@ -1123,7 +1115,7 @@ var file_internal_metamorph_metamorph_api_metamorph_api_proto_rawDesc = []byte{ 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x11, 0x50, 0x65, 0x65, 0x72, 0x73, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x50, 0x65, 0x65, 0x72, 0x73, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x22, 0xf1, 0x03, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0xd0, 0x03, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, @@ -1150,190 +1142,188 @@ var file_internal_metamorph_metamorph_api_metamorph_api_proto_rawDesc = []byte{ 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6d, - 0x61, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x49, 0x64, 0x22, 0x77, 0x0a, 0x13, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x45, 0x0a, 0x0c, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, - 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x52, 0x0c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xbe, 0x03, - 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x74, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x64, 0x41, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, - 0x41, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x2d, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, - 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, - 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x78, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x74, 0x69, - 0x6e, 0x67, 0x54, 0x78, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, - 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x75, - 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x61, - 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x25, 0x0a, - 0x0e, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x62, 0x61, - 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x22, 0xd2, 0x03, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, - 0x74, 0x69, 0x6d, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, - 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6a, - 0x65, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, - 0x70, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x73, 0x12, 0x21, 0x0a, - 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, - 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, - 0x12, 0x41, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, - 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, - 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, - 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, - 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x22, 0x6e, 0x0a, 0x13, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, - 0x73, 0x12, 0x3c, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, - 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x49, 0x0a, 0x18, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, - 0x63, 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, - 0x63, 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x5f, 0x61, 0x66, - 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x73, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x38, 0x0a, - 0x10, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, - 0x79, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, - 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x22, 0x3e, 0x0a, 0x11, 0x43, 0x6c, 0x65, 0x61, 0x72, - 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x41, - 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x31, 0x0a, 0x19, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x4e, 0x0a, 0x0c, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3e, 0x0a, 0x0c, 0x74, 0x72, + 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x22, 0x77, 0x0a, 0x13, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x45, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, - 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2a, 0x9d, 0x02, 0x0a, 0x06, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, - 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x0c, - 0x0a, 0x08, 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x44, 0x10, 0x14, 0x12, 0x0a, 0x0a, 0x06, - 0x53, 0x54, 0x4f, 0x52, 0x45, 0x44, 0x10, 0x1e, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4e, 0x4e, 0x4f, - 0x55, 0x4e, 0x43, 0x45, 0x44, 0x5f, 0x54, 0x4f, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, - 0x10, 0x28, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, - 0x42, 0x59, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x32, 0x12, 0x13, 0x0a, 0x0f, - 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, - 0x3c, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x42, 0x59, - 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x46, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, - 0x45, 0x4e, 0x5f, 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x50, 0x48, 0x41, 0x4e, 0x5f, 0x4d, 0x45, 0x4d, - 0x50, 0x4f, 0x4f, 0x4c, 0x10, 0x50, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x45, 0x4e, 0x5f, 0x4f, - 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x5a, 0x12, 0x1a, 0x0a, 0x16, 0x44, - 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x5f, 0x41, 0x54, 0x54, 0x45, - 0x4d, 0x50, 0x54, 0x45, 0x44, 0x10, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4a, 0x45, 0x43, - 0x54, 0x45, 0x44, 0x10, 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x49, 0x4e, 0x45, 0x44, 0x5f, 0x49, - 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x4c, 0x45, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x73, 0x12, - 0x09, 0x0a, 0x05, 0x4d, 0x49, 0x4e, 0x45, 0x44, 0x10, 0x78, 0x32, 0xc7, 0x06, 0x0a, 0x0c, 0x4d, - 0x65, 0x74, 0x61, 0x4d, 0x6f, 0x72, 0x70, 0x68, 0x41, 0x50, 0x49, 0x12, 0x41, 0x0a, 0x06, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, - 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, - 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x21, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, + 0x32, 0x21, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, - 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, 0x6d, 0x65, 0x74, - 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x1a, 0x22, + 0x65, 0x73, 0x74, 0x52, 0x0c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xbe, 0x03, 0x0a, + 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x74, 0x69, 0x6d, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x07, 0x6d, 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x2d, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6d, + 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x73, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x74, 0x69, 0x6e, + 0x67, 0x54, 0x78, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x75, 0x0a, + 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x61, 0x6c, + 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x25, 0x0a, 0x0e, + 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x62, 0x61, 0x74, + 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x22, 0xd2, 0x03, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, + 0x69, 0x6d, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x78, 0x69, 0x64, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, + 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, + 0x65, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, + 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, + 0x0a, 0x0b, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, + 0x41, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, + 0x65, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, + 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, + 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, + 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x22, 0x6e, 0x0a, 0x13, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, + 0x12, 0x3c, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, + 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x49, 0x0a, 0x18, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x5f, 0x61, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x38, 0x0a, 0x10, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x79, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, + 0x6f, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x22, 0x3e, 0x0a, 0x11, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, + 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x41, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x31, 0x0a, 0x19, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x4e, 0x0a, 0x0c, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3e, 0x0a, 0x0c, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2a, 0x9d, 0x02, 0x0a, 0x06, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x0c, 0x0a, + 0x08, 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x44, 0x10, 0x14, 0x12, 0x0a, 0x0a, 0x06, 0x53, + 0x54, 0x4f, 0x52, 0x45, 0x44, 0x10, 0x1e, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4e, 0x4e, 0x4f, 0x55, + 0x4e, 0x43, 0x45, 0x44, 0x5f, 0x54, 0x4f, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, + 0x28, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x42, + 0x59, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x32, 0x12, 0x13, 0x0a, 0x0f, 0x53, + 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x3c, + 0x12, 0x17, 0x0a, 0x13, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x42, 0x59, 0x5f, + 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x46, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x45, + 0x4e, 0x5f, 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x50, 0x48, 0x41, 0x4e, 0x5f, 0x4d, 0x45, 0x4d, 0x50, + 0x4f, 0x4f, 0x4c, 0x10, 0x50, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x45, 0x4e, 0x5f, 0x4f, 0x4e, + 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x5a, 0x12, 0x1a, 0x0a, 0x16, 0x44, 0x4f, + 0x55, 0x42, 0x4c, 0x45, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x5f, 0x41, 0x54, 0x54, 0x45, 0x4d, + 0x50, 0x54, 0x45, 0x44, 0x10, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, + 0x45, 0x44, 0x10, 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x49, 0x4e, 0x45, 0x44, 0x5f, 0x49, 0x4e, + 0x5f, 0x53, 0x54, 0x41, 0x4c, 0x45, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x73, 0x12, 0x09, + 0x0a, 0x05, 0x4d, 0x49, 0x4e, 0x45, 0x44, 0x10, 0x78, 0x32, 0xc7, 0x06, 0x0a, 0x0c, 0x4d, 0x65, + 0x74, 0x61, 0x4d, 0x6f, 0x72, 0x70, 0x68, 0x41, 0x50, 0x49, 0x12, 0x41, 0x0a, 0x06, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, + 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, + 0x0e, 0x50, 0x75, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x21, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, + 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x1a, 0x22, 0x2e, + 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, + 0x73, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, + 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0f, + 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x28, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x65, 0x73, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, - 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x5a, 0x0a, - 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x28, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, - 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6d, 0x65, 0x74, - 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x14, 0x47, 0x65, 0x74, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x27, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, + 0x12, 0x27, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x00, 0x12, 0x68, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, + 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x65, 0x74, - 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x00, 0x12, 0x68, - 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, - 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, - 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x55, - 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x2e, - 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, - 0x74, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, - 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, - 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x50, 0x0a, 0x09, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x1f, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, - 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x20, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, - 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x3b, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, - 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x75, 0x73, 0x65, 0x73, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x55, 0x6e, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x2e, 0x6d, + 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, + 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, + 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, + 0x64, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x50, 0x0a, 0x09, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1f, + 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x43, + 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x20, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x2e, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x3b, 0x6d, 0x65, 0x74, 0x61, 0x6d, 0x6f, 0x72, + 0x70, 0x68, 0x5f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/metamorph/metamorph_api/metamorph_api.proto b/internal/metamorph/metamorph_api/metamorph_api.proto index bd5f0728d..8521e0712 100644 --- a/internal/metamorph/metamorph_api/metamorph_api.proto +++ b/internal/metamorph/metamorph_api/metamorph_api.proto @@ -60,7 +60,6 @@ message TransactionRequest { bytes raw_tx = 9; Status wait_for_status = 10; bool full_status_updates = 11; - int64 max_timeout = 12; string event_id = 13; } diff --git a/internal/metamorph/metamorph_api/metamorph_api_grpc.pb.go b/internal/metamorph/metamorph_api/metamorph_api_grpc.pb.go index 029f3aa10..ab8ff637a 100644 --- a/internal/metamorph/metamorph_api/metamorph_api_grpc.pb.go +++ b/internal/metamorph/metamorph_api/metamorph_api_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.1 +// - protoc v5.29.3 // source: internal/metamorph/metamorph_api/metamorph_api.proto package metamorph_api diff --git a/internal/metamorph/processor_test.go b/internal/metamorph/processor_test.go index 925ec6844..e86424c29 100644 --- a/internal/metamorph/processor_test.go +++ b/internal/metamorph/processor_test.go @@ -592,14 +592,12 @@ func TestStartProcessSubmittedTxs(t *testing.T) { CallbackToken: "token-1", RawTx: testdata.TX1Raw.Bytes(), WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 10, }, { CallbackUrl: "callback-2.example.com", CallbackToken: "token-2", RawTx: testdata.TX6Raw.Bytes(), WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 10, }, }, @@ -614,35 +612,30 @@ func TestStartProcessSubmittedTxs(t *testing.T) { CallbackToken: "token-1", RawTx: testdata.TX1Raw.Bytes(), WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 10, }, { CallbackUrl: "callback-2.example.com", CallbackToken: "token-2", RawTx: testdata.TX6Raw.Bytes(), WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 10, }, { CallbackUrl: "callback-3.example.com", CallbackToken: "token-2", RawTx: testdata.TX6Raw.Bytes(), WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 10, }, { CallbackUrl: "callback-4.example.com", CallbackToken: "token-2", RawTx: testdata.TX6Raw.Bytes(), WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 10, }, { CallbackUrl: "callback-5.example.com", CallbackToken: "token-2", RawTx: testdata.TX6Raw.Bytes(), WaitForStatus: metamorph_api.Status_RECEIVED, - MaxTimeout: 10, }, }, diff --git a/internal/metamorph/server.go b/internal/metamorph/server.go index dc765120e..da73ae22d 100644 --- a/internal/metamorph/server.go +++ b/internal/metamorph/server.go @@ -28,7 +28,6 @@ import ( ) const ( - maxTimeoutDefault = 5 * time.Second checkStatusIntervalDefault = 5 * time.Second minedDoubleSpendMsg = "previously double spend attempted" ) @@ -56,7 +55,6 @@ type Server struct { logger *slog.Logger processor ProcessorI store store.MetamorphStore - maxTimeoutDefault time.Duration checkStatusInterval time.Duration bitcoinNode BitcoinNode forceCheckUtxos bool @@ -71,12 +69,6 @@ func WithForceCheckUtxos(bitcoinNode BitcoinNode) func(*Server) { } } -func WithServerMaxTimeoutDefault(timeout time.Duration) func(*Server) { - return func(s *Server) { - s.maxTimeoutDefault = timeout - } -} - func WithCheckStatusInterval(d time.Duration) func(*Server) { return func(s *Server) { s.checkStatusInterval = d @@ -108,7 +100,6 @@ func NewServer(prometheusEndpoint string, maxMsgSize int, logger *slog.Logger, logger: logger, processor: processor, store: store, - maxTimeoutDefault: maxTimeoutDefault, checkStatusInterval: checkStatusIntervalDefault, forceCheckUtxos: false, } @@ -159,6 +150,21 @@ func (s *Server) Health(ctx context.Context, _ *emptypb.Empty) (healthResp *meta } func (s *Server) PutTransaction(ctx context.Context, req *metamorph_api.TransactionRequest) (txStatus *metamorph_api.TransactionStatus, err error) { + deadline, ok := ctx.Deadline() + + // decrease time to get initial deadline + newDeadline := deadline + if time.Now().Add(2 * time.Second).Before(deadline) { + newDeadline = deadline.Add(-(time.Second * 2)) + } + + // Create a new context with the updated deadline + if ok { + var newCancel context.CancelFunc + ctx, newCancel = context.WithDeadline(context.Background(), newDeadline) + defer newCancel() + } + ctx, span := tracing.StartTracing(ctx, "PutTransaction", s.tracingEnabled, s.tracingAttributes...) defer func() { tracing.EndTracing(span, err) @@ -169,10 +175,25 @@ func (s *Server) PutTransaction(ctx context.Context, req *metamorph_api.Transact // Convert gRPC req to store.Data struct... sReq := toStoreData(hash, statusReceived, req) - return s.processTransaction(ctx, req.GetWaitForStatus(), sReq, req.GetMaxTimeout(), hash.String()), nil + return s.processTransaction(ctx, req.GetWaitForStatus(), sReq, hash.String()), nil } func (s *Server) PutTransactions(ctx context.Context, req *metamorph_api.TransactionRequests) (txsStatuses *metamorph_api.TransactionStatuses, err error) { + deadline, ok := ctx.Deadline() + + // decrease time to get initial deadline + newDeadline := deadline + if time.Now().Add(2 * time.Second).Before(deadline) { + newDeadline = deadline.Add(-(time.Second * 2)) + } + + // Create a new context with the updated deadline + if ok { + var newCancel context.CancelFunc + ctx, newCancel = context.WithDeadline(context.Background(), newDeadline) + defer newCancel() + } + ctx, span := tracing.StartTracing(ctx, "PutTransactions", s.tracingEnabled, s.tracingAttributes...) defer func() { tracing.EndTracing(span, err) @@ -191,12 +212,10 @@ func (s *Server) PutTransactions(ctx context.Context, req *metamorph_api.Transac resp.Statuses = make([]*metamorph_api.TransactionStatus, len(req.GetTransactions())) processTxsInputMap := make(map[chainhash.Hash]processTxInput) - var timeout int64 for ind, txReq := range req.GetTransactions() { statusReceived := metamorph_api.Status_RECEIVED hash := PtrTo(chainhash.DoubleHashH(txReq.GetRawTx())) - timeout = txReq.GetMaxTimeout() processTxsInputMap[*hash] = processTxInput{ data: toStoreData(hash, statusReceived, txReq), @@ -213,7 +232,7 @@ func (s *Server) PutTransactions(ctx context.Context, req *metamorph_api.Transac go func(ctx context.Context, processTxInput processTxInput, txID string, wg *sync.WaitGroup, resp *metamorph_api.TransactionStatuses) { defer wg.Done() - statusNew := s.processTransaction(ctx, processTxInput.waitForStatus, processTxInput.data, timeout, txID) + statusNew := s.processTransaction(ctx, processTxInput.waitForStatus, processTxInput.data, txID) resp.Statuses[processTxInput.responseIndex] = statusNew }(ctx, input, hash.String(), wg, resp) @@ -237,7 +256,7 @@ func toStoreData(hash *chainhash.Hash, statusReceived metamorph_api.Status, req RawTx: req.GetRawTx(), } } -func (s *Server) processTransaction(ctx context.Context, waitForStatus metamorph_api.Status, data *store.Data, timeoutSeconds int64, txID string) *metamorph_api.TransactionStatus { +func (s *Server) processTransaction(ctx context.Context, waitForStatus metamorph_api.Status, data *store.Data, txID string) *metamorph_api.TransactionStatus { var err error ctx, span := tracing.StartTracing(ctx, "processTransaction", s.tracingEnabled, s.tracingAttributes...) returnedStatus := &metamorph_api.TransactionStatus{ @@ -261,20 +280,14 @@ func (s *Server) processTransaction(ctx context.Context, waitForStatus metamorph tracing.EndTracing(span, err) }() - responseChannel := make(chan StatusAndError, 10) - - // normally a node would respond very quickly, unless it's under heavy load - timeDuration := s.maxTimeoutDefault - if timeoutSeconds > 0 { - timeDuration = time.Second * time.Duration(timeoutSeconds) + // to avoid false negatives first check if ctx is expired + select { + case <-ctx.Done(): + return nil + default: } - ctx, cancel := context.WithTimeout(ctx, timeDuration) - defer func() { - cancel() - close(responseChannel) - }() - + responseChannel := make(chan StatusAndError, 10) s.processor.ProcessTransaction(ctx, &ProcessorRequest{Data: data, ResponseChannel: responseChannel}) if waitForStatus == 0 { @@ -288,9 +301,12 @@ func (s *Server) processTransaction(ctx context.Context, waitForStatus metamorph } checkStatusTicker := time.NewTicker(s.checkStatusInterval) - for { select { + case <-ctx.Done(): + // Ensure that function returns at latest when context times out + returnedStatus.TimedOut = true + return returnedStatus case <-checkStatusTicker.C: // Check in intervals whether the tx was seen on network & updated on DB by another metamorph instance @@ -305,11 +321,6 @@ func (s *Server) processTransaction(ctx context.Context, waitForStatus metamorph if err == nil && tx.Status == waitForStatus { return tx } - - case <-ctx.Done(): - // Ensure that function returns at latest when context times out - returnedStatus.TimedOut = true - return returnedStatus case res := <-responseChannel: returnedStatus.Status = res.Status diff --git a/internal/metamorph/server_test.go b/internal/metamorph/server_test.go index 266e684fd..919b237f6 100644 --- a/internal/metamorph/server_test.go +++ b/internal/metamorph/server_test.go @@ -149,7 +149,7 @@ func TestPutTransaction(t *testing.T) { }, } - opts := []metamorph.ServerOption{metamorph.WithServerMaxTimeoutDefault(100 * time.Millisecond)} + opts := []metamorph.ServerOption{} if tc.checkStatusInterval { opts = append(opts, metamorph.WithCheckStatusInterval(50*time.Millisecond)) } @@ -164,7 +164,12 @@ func TestPutTransaction(t *testing.T) { } // when - actualStatus, err := sut.PutTransaction(context.Background(), txRequest) + // when + ctx := context.Background() + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + actualStatus, err := sut.PutTransaction(timeoutCtx, txRequest) + fmt.Println("shota ", err) // then assert.NoError(t, err) @@ -597,12 +602,15 @@ func TestPutTransactions(t *testing.T) { }, } - sut, err := metamorph.NewServer("", 0, slog.Default(), metamorphStore, processor, nil, metamorph.WithServerMaxTimeoutDefault(5*time.Second)) + sut, err := metamorph.NewServer("", 0, slog.Default(), metamorphStore, processor, nil) require.NoError(t, err) defer sut.GracefulStop() // when - statuses, err := sut.PutTransactions(context.Background(), tc.requests) + ctx := context.Background() + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + statuses, err := sut.PutTransactions(timeoutCtx, tc.requests) if tc.expectedErrorStr != "" || err != nil { require.ErrorContains(t, err, tc.expectedErrorStr) return