Skip to content

Transaction substatus #51

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 4 commits into from
Jan 27, 2023

Conversation

matthew1001
Copy link
Contributor

@matthew1001 matthew1001 commented Jan 12, 2023

Closes #49

A diagram and more detail about the changes under this PR are available in the FireFly Micro board at https://miro.com/app/board/uXjVOWHk_6s=/?moveToWidget=3458764544594470770&cot=14 and in the original issue #49

The PR introduces additional granularity to transaction progress by introducing sub-status types, and actions that can cause a change in sub-status:

image

These are incorporated into the ManagedTX data structure with a history field and a historySummary field (see Miro board for more detailed description).

@matthew1001 matthew1001 force-pushed the transaction-substatus branch 2 times, most recently from 4879a6c to 25ec21f Compare January 12, 2023 11:39
Copy link
Contributor

@peterbroadhurst peterbroadhurst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @matthew1001,

I've made some suggestions on the detail of this new implementation.

I am expecting to see deletion of the old implementation in this one too - and probably we want this to get in before v1.2 cuts, so we don't have a double-migration of the data structures.

func (mtx *ManagedTX) AddSubStatus(ctx context.Context, subStatus TxSubStatus) {
// See if this status exists in the list already
for _, entry := range mtx.SubStatusHistory {
if entry.Status == subStatus {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a split approach here that I can't reconcile.

The code below suggests we are aiming to limit the number of unique entries to 50 (I guess the most recent 50?). This fits with an approach where (like k8s describe) we de-duplicate when something is in exactly the same state, but try to provide an accurate a history as possible.

However, the comparison on this line only uses the TxSubStatus line for comparison bare - without all the interesting things that might make it unique, such as the TX Hash. So it's impossible to get more than 6 entries in the table. In fact the entries don't really contain any detail at all now.

I think we need to choose whether to give detailed information, or not. I believe we should give detailed information - as we do before this PR.

I assert the following are a must, which certainly does introduce the potential for more than 50 records:

  • The current Hash of the transaction we are tracking
  • The receipt ProtocolID

The number of confirmations is slightly interesting, as we could choose to update that on an existing record, rather than produce a new record.

I like the approach you've got of adding the raw information on the JSON, and allowing translation of that into human readable form by the consumer of the API (such as a UX, or application).

So I think I'm proposing:

type TxSubStatusEntry struct {
	Occurrences     int              `json:"occurrences"`        // updates every time we add a duplicate
	FirstOccurrence *fftypes.FFTime  `json:"first"`              // set when first added to array
	LastOccurrence  *fftypes.FFTime  `json:"last"`               // set when first added, and updated on each duplicate
	Status          TxSubStatus      `json:"status"`             // part of uniqueness
	TX              string           `json:"tx"`                 // part of uniqueness
	Receipt         string           `json:"receipt"`            // part of uniqueness
	GasPrice        *fftypes.JSONAny `json:"gasPrice,omitempty"` // part of uniqueness - only present on Status==RetrievedGasPrice
	Confirmations   int              `json:"confirmations"`      // updates as more are added
}

func (s *TxSubStatusEntry) Key() string {
	return fmt.Sprintf("%s:%s:%s:%s", s.Status, s.TX, s.Receipt, s.GasPrice.String())
}

Maybe a higher level point as to why I feel quite strongly about ^^^: if the system is looping through fundamentally different sub-step, then I think it's really important that the information shows this.

e.g. I think between (A) and (B) I feel like people need (B):

Information preservation A

  • ✅ validated (1)
  • ✅ estimatedgas (2)
  • ✅ submitted (3)

Information preservation B

  • ✅ validated (1)
  • ✅ estimatedgas (1) GasPrice=12345
  • ✅ submitted (1) TXHash=12345
  • ✅ estimatedgas (1) GasPrice=12999
  • ✅ submitted (2) TXHash=23456

Copy link
Contributor

@peterbroadhurst peterbroadhurst Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... there's a subtle point here too, that I just realized I'm making.

I think the list of sub-status's is a little inconsistent. For some steps we've got before+after states, for others we've just got after states. I think we should be very consistent on that.

The example above is we just "after" states. I didn't include any "before" states ... apart from one initial state which is actually something that happens before we accept the transaction - I was wondering if we might get stuck in that state and it would be useful to have a count there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, darn - and that suddenly reminded me there might be an error that we're getting preventing us moving to the next state, so I guess there's a decision as to whether we record errors in special "before" states (doubling the length of the array)... or by just updating the previous state with the error preventing us from progressing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... which maybe means we should only have "before" states, rather than only have "after" states:

Information preservation C

  • ✅ estimatingGas (3) LastError="Node unavaialable", LastErrorTime=10:57
  • ✅ submitting (1) TXHash=12345, GasPrice=12345
  • ✅ estimatingGas (1)
  • ✅ submitted (2) TXHash=23456, GasPrice=12999


// Prevent crazy run-away situations by limiting the number of unique sub-status
// types we will keep
if len(mtx.SubStatusHistory) >= 50 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we retain a limit on the buffer, I think we need to:

  1. Make the limit configurable
  2. Discard the oldest record, not the newest

@matthew1001
Copy link
Contributor Author

Thanks for the comments. So I think there are 3 broad categories for discussion perhaps in person if easier:

  • How much information should be in a sub-status entry
    • I guess I wasn't sure of the benefit of reporting TXHash, gas price etc. on every status change. Today we report these elsewhere in the detail section so is there a benefit to showing e.g. the gas price at every sub-status change?
  • Should a state change be recorded for a 'before' and 'after' step
    • My thinking here was that a discrete before and after status would make it easy to determine how long certain steps were taking while allowing a caller who didn't care about that detail to ignore either the before or after step.
  • How are errors reported vs sub-status changes
    • The existing history mechanism gives a slightly different view on changes in the transaction, including errors at a given point. Not having 2 "history" sections does make sense, so are you thinking that TxSubStatusEntry should have an optional error field similar to the current history list?

Signed-off-by: Matthew Whitehead <[email protected]>
Signed-off-by: Matthew Whitehead <[email protected]>
@matthew1001 matthew1001 force-pushed the transaction-substatus branch 6 times, most recently from f654308 to 9201251 Compare January 27, 2023 17:10
…ditional information pertaining to each action

Signed-off-by: Matthew Whitehead <[email protected]>
@matthew1001 matthew1001 force-pushed the transaction-substatus branch from 9201251 to 2e5ad24 Compare January 27, 2023 17:13
@matthew1001
Copy link
Contributor Author

matthew1001 commented Jan 27, 2023

Thanks for the comments & discussion @peterbroadhurst. The new commit I think addresses many of the concerns over how we want to structure the data, how much of it to keep, and how much detail to retain etc.

There's one last tidy-up thing I could do with a pointer on - passing the configurable maxHistory* values from fftm.manager down into the apitypes.managed_tx so that AddSubStatus(...) have access to them (note the // TODO comments). Probably just an obvious best way of doing it that I'm missing so any pointers let me know.

I'm also seeing struct fields serialise in alphabetical order, not the order defined in the struct. Suspect I'm missing something simple as it would be nice for most important fields to be near the top when serialising.

|errorHistoryCount|The number of historical errors to retain in the operation|`int`|`25`
|maxHistoryActions|The number of actions to store per historical status updates|`int`|`50`
|maxHistoryCount|The number of historical status updates to retain in the operation|`int`|`50`
|maxHistorySummaryCount|The number of historical status summary records to retain in the operation|`int`|`50`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we really need this configuration parameter, as this should be bounded by the number of types of status we have.

Info string `json:"info,omitempty"`
Error string `json:"error,omitempty"`
MappedReason ffcapi.ErrorReason `json:"reason,omitempty"`
// TxSubStatus is an intermediate status a transaction may go through
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting this list can be extended by policy engine implementations.


// This action hasn't been recorded yet in this sub-status. Add a new entry for it.
if len(currentSubStatus.Actions) >= 50 { // TODO - get from config
log.L(ctx).Warn("Number of unique sub-status actions. New action detail will not be recorded.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be FIFO I think too

Copy link
Contributor

@peterbroadhurst peterbroadhurst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @matthew1001 - this seems like a great step forwards.

I note the PR does not contain a summary of the new architecture / data structure that is proposed, so we'll need to document that separately somewhere (or update this PR).

I do have detail proposals on the TODOs, and some detail thoughts on the code, but I suspect the best way for me to express these efficiently is with code. So I'm going to merge this as an excellent step forwards, then make my suggestions as a PR for you to react to.

@peterbroadhurst peterbroadhurst merged commit 952269d into hyperledger:main Jan 27, 2023
@peterbroadhurst peterbroadhurst deleted the transaction-substatus branch January 27, 2023 18:10
@matthew1001
Copy link
Contributor Author

Yes agreed re. the documentation of the structures. I'll add something to this PR to describe them. Thanks for the code suggestions via #58.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add more granular status updates to indicate transaction progress
2 participants