-
Notifications
You must be signed in to change notification settings - Fork 9
MailboxRPC 1/7: durability foundation #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
66ce116
db: add durable mailbox schema and SQLC queries
Roasbeef 5f1f68d
db: implement ActorDeliveryStore for mailbox persistence
Roasbeef b7cba3a
baselib/actor: add TLVMessage codec for message serialization
Roasbeef 817852c
baselib/actor: add Delivery abstraction with lease operations
Roasbeef 6b06337
baselib/actor: add DurableMailbox with lease-based delivery
Roasbeef 05a2dae
baselib/actor: add AskResponse for durable request-response
Roasbeef 76313f7
baselib/actor: add DurableActor with deduplication and retry
Roasbeef 97385fc
baselib/actor: add RestartMessage for crash recovery
Roasbeef 0464d62
baselib/actor: add OutboxPublisher CDC and MapRef adapter
Roasbeef 21f0250
internal/actortest: add e2e integration tests
Roasbeef 4f3d6f6
docs: add durable actor architecture documentation
Roasbeef 701a51c
multi: fix lint on rebased durability
bhandras abdad2a
db: renumber durable mailbox migration
bhandras ee105d6
baselib/actor: harden Delivery with mutex and deferred promise comple…
Roasbeef c3655da
baselib/actor: nack DurableAsk on outbox failure and defer tx promise
Roasbeef 9f9741b
baselib/actor: harden mailbox with poison dead-lettering and outbox d…
Roasbeef 0e153ad
db/sqlc: regenerate queries after mailbox ON CONFLICT change
Roasbeef 36211ee
baselib/actor: add tests for durability review fixes
Roasbeef 494d497
baselib/actor: fix tx path deferPromise propagation and DurableAsk retry
Roasbeef 29b4706
multi: check Tell error return values in wallet, round, and vtxo
Roasbeef 0086c81
baselib/actor: reorder SaveAskResult after lease validation in Ack
Roasbeef 6035f5b
multi: use BIGINT for timestamps and add outbox claim lease
Roasbeef File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| package actor | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io" | ||
|
|
||
| "github.com/lightningnetwork/lnd/tlv" | ||
| ) | ||
|
|
||
| // AskResponseMsgType is the TLV type identifier for AskResponse messages. | ||
| // This is a well-known type used by the DurableAsk pattern. | ||
| const AskResponseMsgType tlv.Type = 0xFFFF // 65535 - reserved for system messages | ||
|
|
||
| // TLV record type constants for AskResponse fields. | ||
| const ( | ||
| askResponseCorrelationIDType tlv.Type = 1 | ||
| askResponseResultBlobType tlv.Type = 2 | ||
| askResponseErrorTextType tlv.Type = 3 | ||
| ) | ||
|
|
||
| // AskResponse is a durable response message for DurableAsk requests. | ||
| // When an actor processes a message with callback metadata, it writes an | ||
| // AskResponse to its outbox targeting the callback actor. The OutboxPublisher | ||
| // then delivers this response to the caller's durable mailbox. | ||
| // | ||
| // This is the core mechanism for crash-safe Ask semantics: the response | ||
| // survives both caller and target crashes because it flows through the | ||
| // durable outbox/mailbox infrastructure. | ||
| // | ||
| // The ResultBlob contains a fully-encoded TLVMessage (with type ID prefix), | ||
| // allowing the caller to use their MessageCodec to decode the typed result. | ||
| // This enables generic AskResponse handling while preserving type safety. | ||
| type AskResponse struct { | ||
| BaseMessage | ||
|
|
||
| // CorrelationID links this response to the original DurableAsk request. | ||
| // The caller uses this to match responses to pending requests. | ||
| CorrelationID string | ||
|
|
||
| // ResultBlob contains the codec-encoded result (includes TLV type ID). | ||
| // Use DecodeResult() with a MessageCodec to get the typed result. | ||
| // Empty if the request failed with an error. | ||
| ResultBlob tlv.Blob | ||
|
|
||
| // ErrorText contains the error message if the request failed. | ||
| // Empty string if the request succeeded. | ||
| ErrorText string | ||
| } | ||
|
|
||
| // MessageType returns a human-readable type name for logging. | ||
| func (m AskResponse) MessageType() string { | ||
| return "actor.AskResponse" | ||
| } | ||
|
|
||
| // TLVType returns the unique TLV type identifier for this message. | ||
| func (m AskResponse) TLVType() tlv.Type { | ||
| return AskResponseMsgType | ||
| } | ||
|
|
||
| // Encode serializes the message to the provided writer. | ||
| func (m AskResponse) Encode(w io.Writer) error { | ||
| correlationID := []byte(m.CorrelationID) | ||
| resultBlob := m.ResultBlob | ||
| errorText := []byte(m.ErrorText) | ||
|
|
||
| records := []tlv.Record{ | ||
| tlv.MakePrimitiveRecord( | ||
| askResponseCorrelationIDType, &correlationID, | ||
| ), | ||
| tlv.MakePrimitiveRecord( | ||
| askResponseResultBlobType, &resultBlob, | ||
| ), | ||
| tlv.MakePrimitiveRecord( | ||
| askResponseErrorTextType, &errorText, | ||
| ), | ||
| } | ||
|
|
||
| stream, err := tlv.NewStream(records...) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return stream.Encode(w) | ||
| } | ||
|
|
||
| // Decode deserializes the message from the provided reader. | ||
| func (m *AskResponse) Decode(r io.Reader) error { | ||
| var ( | ||
| correlationID []byte | ||
| resultBlob []byte | ||
| errorText []byte | ||
| ) | ||
|
|
||
| records := []tlv.Record{ | ||
| tlv.MakePrimitiveRecord( | ||
| askResponseCorrelationIDType, &correlationID, | ||
| ), | ||
| tlv.MakePrimitiveRecord( | ||
| askResponseResultBlobType, &resultBlob, | ||
| ), | ||
| tlv.MakePrimitiveRecord( | ||
| askResponseErrorTextType, &errorText, | ||
| ), | ||
| } | ||
|
|
||
| stream, err := tlv.NewStream(records...) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if _, err := stream.DecodeWithParsedTypes(r); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| m.CorrelationID = string(correlationID) | ||
| m.ResultBlob = resultBlob | ||
| m.ErrorText = string(errorText) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // IsError returns true if this response represents an error. | ||
| func (m AskResponse) IsError() bool { | ||
| return m.ErrorText != "" | ||
| } | ||
|
|
||
| // DecodeResult decodes the result blob using the provided codec. | ||
| // Returns an error if the response is an error or if decoding fails. | ||
| func (m AskResponse) DecodeResult(codec *MessageCodec) (TLVMessage, error) { | ||
| if m.IsError() { | ||
| return nil, fmt.Errorf("ask failed: %s", m.ErrorText) | ||
| } | ||
|
|
||
| if len(m.ResultBlob) == 0 { | ||
| return nil, nil | ||
| } | ||
|
|
||
| return codec.Decode(m.ResultBlob) | ||
| } | ||
|
|
||
| // NewAskResponseSuccess creates a successful AskResponse with a raw result blob. | ||
| // Use NewAskResponseWithResult to encode a TLVMessage result. | ||
| func NewAskResponseSuccess(correlationID string, resultBlob tlv.Blob) *AskResponse { | ||
| return &AskResponse{ | ||
| CorrelationID: correlationID, | ||
| ResultBlob: resultBlob, | ||
| ErrorText: "", | ||
| } | ||
| } | ||
|
|
||
| // NewAskResponseWithResult creates a successful AskResponse by encoding the | ||
| // result using the provided codec. This is the preferred way to create | ||
| // responses as it ensures the result is properly encoded for decoding. | ||
| func NewAskResponseWithResult( | ||
| correlationID string, | ||
| codec *MessageCodec, | ||
| result TLVMessage, | ||
| ) (*AskResponse, error) { | ||
|
|
||
| resultBlob, err := codec.Encode(result) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("encode result: %w", err) | ||
| } | ||
|
|
||
| return &AskResponse{ | ||
| CorrelationID: correlationID, | ||
| ResultBlob: resultBlob, | ||
| ErrorText: "", | ||
| }, nil | ||
| } | ||
|
|
||
| // NewAskResponseError creates an error AskResponse with the given error text. | ||
| func NewAskResponseError(correlationID string, errorText string) *AskResponse { | ||
| return &AskResponse{ | ||
| CorrelationID: correlationID, | ||
| ResultBlob: nil, | ||
| ErrorText: errorText, | ||
| } | ||
| } | ||
|
|
||
| // Compile-time interface check. | ||
| var _ TLVMessage = (*AskResponse)(nil) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.