Skip to content
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

Add Tx Indexer Extension #1143

Merged
merged 25 commits into from
Jul 18, 2024
Merged

Add Tx Indexer Extension #1143

merged 25 commits into from
Jul 18, 2024

Conversation

aaronbuchwald
Copy link
Collaborator

This PR refactors the tx indexing from the MorpheusVM/TokenVM controller code into a separate module under extensions/indexer

This also fixes an apparent bug where the database created internally to the MorpheusVM/TokenVM for tx indexing would never be closed.

@aaronbuchwald aaronbuchwald marked this pull request as draft July 15, 2024 15:53
@aaronbuchwald aaronbuchwald changed the base branch from remove-rejected-controller to main July 15, 2024 21:35
@aaronbuchwald aaronbuchwald marked this pull request as ready for review July 15, 2024 22:08
@aaronbuchwald aaronbuchwald self-assigned this Jul 15, 2024

var _ indexer.SuccessfulTxIndexer = &successfulTxIndexer{}

type successfulTxIndexer struct {
Copy link
Contributor

@joshua-kim joshua-kim Jul 16, 2024

Choose a reason for hiding this comment

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

IMO these implementing the indexer interface (tokenvm one as well) seems weird to me because this is not an indexer. I'm wondering if this metrics code should go back to living in the Controller code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could rename it as well. We could more accurately call it an AcceptedSubscriber.

This would be more accurate to exactly what it's doing and an indexer would be one thing you could build as an AcceptedSubscriber. Similarly, you could pass in an acceptedActionHandler or similar, which would be just this for the MorpheusVM and would also maintain the in-memory order book in the case of TokenVM.

extensions/indexer/indexer.go Outdated Show resolved Hide resolved
if err != nil {
return vm.Config{}, nil, nil, nil, nil, nil, nil, nil, err
}
c.txIndexer = indexer.NewTxIndexer(
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer if we just had a no-op implementation of the indexer interface that was used instead if GetStoreTransactions was set here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is just keeping the same pattern that was there before. The for loop currently does two things, it iterates and indexes every transaction and then for each successful transaction it performs some action for each of the multi-actions that were included.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alternatively, if we switch to AcceptedSubscriber, then the accepted action indexer can just be completely separate.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I would prefer it be decoupled from each other

Comment on lines 20 to 22
type SuccessfulTxIndexer interface {
Accepted(ctx context.Context, tx *chain.Transaction, result *chain.Result) error
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think if we go with the route of removing the metrics code out of indexer implementations we should be able to remove this interface as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll move the actions to their own accepted subscriber as suggested here #1143 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Still leaving this extra interface in, so that each controller can pass in its own successful tx handler. The alternative would be for both of them to just implement the full interface. Either is fine imo, lmk if you'd prefer the latter.

extensions/indexer/indexer.go Outdated Show resolved Hide resolved
extensions/indexer/storage.go Outdated Show resolved Hide resolved
extensions/indexer/storage.go Outdated Show resolved Hide resolved
extensions/indexer/storage.go Outdated Show resolved Hide resolved
extensions/indexer/indexer.go Outdated Show resolved Hide resolved
Accepted(ctx context.Context, tx *chain.Transaction, result *chain.Result) error
}

type TxIndexer struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're going to make this an extension that other vms will pull in it probably makes sense to add unit tests for this

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wasn't sure if that should be part of this PR, but can add it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we should make adding a unit test a separate PR because you currently need a builder in order to construct a stateless block...

We need to clean a lot of this code up to make writing unit tests more reasonable, so I'd prefer to defer this to a separate PR and keep the scope of this one a little bit smaller.

@aaronbuchwald
Copy link
Collaborator Author

707dad3 fixes https://github.com/ava-labs/hypersdk/actions/runs/9963000960/job/27528179448?pr=1143#step:5:433

Screenshot 2024-07-16 at 16 07 11

where I did not not invoke the combined subscriber correctly (still called the tx indexer instead)

@aaronbuchwald
Copy link
Collaborator Author

707dad3 fixes https://github.com/ava-labs/hypersdk/actions/runs/9963000960/job/27528179448?pr=1143#step:5:433

Screenshot 2024-07-16 at 16 07 11 where I did not not invoke the combined subscriber correctly (still called the tx indexer instead)

Digging into this a bit more, this happens for the TokenVM tests when the actionIndexer is a subscriber registered AFTER the txIndexer. If I reverse the order, the tests pass reliably locally.

I can also fix this test by inserting a sleep statement between marking the block as accepted and invoking the tcli. It seems that this code is racey because controller.Accepted is actually called from a goroutine processes accepted blocks asynchronously:

for b := range vm.acceptedQueue {
.

This race condition is not introduced by this change, but it does appear to happen reliably with this change iff the actionIndexer comes second.

I think it's reasonable to swap the order, so that this passes CI and add a TODO calling out the race condition in the integration test.


var _ indexer.SuccessfulTxSubscriber = (*actionHandler)(nil)

type actionHandler struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we call this something else...? Something related metrics or something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

acceptedActionMetricsHandler? This is a bit too long imo and I slightly prefer actionHandler in this case unless you have an alternative suggestion.


type noopTxIndexer struct{}

func NewNoopTxIndexer() *noopTxIndexer {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: NoOp

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

return true, t, success, d, fee, nil
}

type noopTxIndexer struct{}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: export type so caller can use this type if it wants to. Applicable to rest of concrete-types in this package

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I disagree with this. This is intentionally hiding that detail assuming callers choose to create this type and don't need to know the details or change anything about it after the fact.

Open to changing my mind on this though

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. If you don't want to let the caller know about the type for some reason, then you can just return the interface. Returning an unexported struct makes the type awkward to use. You can't embed, wrap, return a non-exported type from a calling package.
  2. At the same time what's the disadvantage of exporting the type? Returning a concrete type gives more flexibility to the caller to use it either as the interface or as the concrete type. I think we should let consumers of the package decide how they want to use our api (this way they can use our indexer interface, wrap it with their own, or use the type).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Exported

subscribers []AcceptedSubscriber
}

func NewCombinedAcceptedSubscriber(subscribers ...AcceptedSubscriber) *CombinedAcceptedSubscriber {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we call this function + this type func NewAcceptedSubscribers(...) *AcceptedSubscribers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I feel like combined is more explicit than adding -s, which seems easier to miss.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated

_ AcceptedSubscriber = (*noopTxIndexer)(nil)
)

var (
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: combine the two var blocks

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I generally prefer a separate var block for interface assertions, but don't see that used elsewhere in this repo and do see a combined var block with another variable used in one place.

Will update.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done


var _ indexer.SuccessfulTxSubscriber = (*actionHandler)(nil)

type actionHandler struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comment as the corresponding comment in morpheusvm

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Renaming

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

a.c.orderBook.Add(chain.CreateActionID(tx.ID(), uint8(i)), tx.Auth.Actor(), action)
case *actions.FillOrder:
a.c.metrics.fillOrder.Inc()
outputs := result.Outputs[i]
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering if the order-book updates should happen in their own subscriber since they are logically decoupled from the metrics

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it's fine to do more than one thing here.

Doing this async (as it's handled prior to this PR as well) does introduce a race condition because the in-memory order book is not updated during block accept. We may want to support a SyncAcceptedSubscriber and AsyncAcceptedSubscriber to fix that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I think long term we're gonna try to get rid of the metrics in here and push it up into hypersdk anyways so... not too concerned

GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimensions, uint64, error)
}

type txIndexer struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could probably just call this DiskIndexer. Feels weird to have the concrete-type just be the name of the interface.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Renamed to txDBIndexer and renaming the constructor as well

)

type TxIndexer interface {
Accepted(ctx context.Context, blk *chain.StatelessBlock) error
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably should just replace this line w/ an embed for the AcceptedSubscriber interface it since that's the interface we're using to get access to accepted blocks

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done


type TxIndexer interface {
Accepted(ctx context.Context, blk *chain.StatelessBlock) error
GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimensions, uint64, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we return a struct here instead? We can define an indexer.Tx struct type or something that the indexer defines for indexed tx data.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is an interface, so we can swap a noop version in if tx indexing is disabled as per #1143 (comment)

return &AcceptedSubscribers{subscribers: subscribers}
}

func (c *AcceptedSubscribers) Accepted(ctx context.Context, blk *chain.StatelessBlock) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: update to a since we updated this struct name

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

extension/indexer/successful_tx_subscriber.go Show resolved Hide resolved
return &txDBIndexer{db: db}
}

func (i *txDBIndexer) Accepted(_ context.Context, blk *chain.StatelessBlock) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: export the struct + rename the receiver

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

)

type AcceptedSubscriber interface {
Accepted(ctx context.Context, blk *chain.StatelessBlock) error
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need this context + in the other tx subscriber interface currently... we could leave it in since I think it's reasonable that you would want a context though (say if you were archiving into s3 or something).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ya I think this is reasonable to leave in as part of the interface

@aaronbuchwald aaronbuchwald merged commit c348296 into main Jul 18, 2024
20 checks passed
@joshua-kim joshua-kim deleted the tx-indexer branch July 18, 2024 20:47
iFrostizz pushed a commit that referenced this pull request Jul 19, 2024
* Remove rejected from controller interface

* Update mocks and remove unused mock_auth from mocks.mockgen.txt

* Refactor tx indexing into indexer extension

* Close txDB in morpheusvm on shutdown

* Update name of unused result param to _

* fix lint

* Update license year

* Address nits

* move action handler to its own accepted subscriber

* Add interface check for noopTxIndexer

* Fix typo

* fix unused receivers

* fix linting

* invoke combined subscriber to fix integration test

* move action handler prior to tx indexer to fix pre-existing race condition

* Add TODO to integration test flagging flaky test

* Noop -> NoOp

* Address comments

* Export NoOpTxIndexer

* rename combinedAcceptedSubscriber AcceptedSubscribers
iFrostizz pushed a commit that referenced this pull request Jul 19, 2024
* Remove rejected from controller interface

* Update mocks and remove unused mock_auth from mocks.mockgen.txt

* Refactor tx indexing into indexer extension

* Close txDB in morpheusvm on shutdown

* Update name of unused result param to _

* fix lint

* Update license year

* Address nits

* move action handler to its own accepted subscriber

* Add interface check for noopTxIndexer

* Fix typo

* fix unused receivers

* fix linting

* invoke combined subscriber to fix integration test

* move action handler prior to tx indexer to fix pre-existing race condition

* Add TODO to integration test flagging flaky test

* Noop -> NoOp

* Address comments

* Export NoOpTxIndexer

* rename combinedAcceptedSubscriber AcceptedSubscribers
aaronbuchwald added a commit that referenced this pull request Jul 24, 2024
…eful shutdown

* keep track of the last accepted block at the end of the
`processAcceptedBlocks` func

* handle error and panic

* Create a new processed key and retrieve it to send to websocket if
present on boot

* use `lastProcessed` key

* update metrics on block retrieval

* set processed height at the end of processing and process block on
reboot for each unprocessed block

* remove processed block function is useless

* break the block into a function and add blocks to the chan

* add logs, return fatal error, fix cache miss

* set bounds on `AcceptorSize` and `AcceptedBlockWindow`

* [cli/spam] Incorporate `vryx-poc` Improvements (#1091)

* fix withholding spelling

* define prompt float

* remove useless const

* more cleanup

* making progress

* working on issue loop

* turn this into an interface rather than providing this functions

* making progress on core loop

* code is compiling

* bound inflight returns with channel

* cleanup struct

* remove resolved todo

* update transfer to include memo

* cleanup morephus cli definition

* remove unused variables

* cleanup tokenvm client

* add issuance limiter

* fix errors

* don't add tx to block if too big

* put more txs in flight

* cleanup logging

* add fix for fees

* track outstanding txs

* fix var order

* fix returned balance

* test fix

* linter

* add comments

* resolve linter for morpheusvm

* pass tokenvm linter

* add memo to tx size

* gci imports

* trying again

* integration tests now passing

* Add TODO to fix race conditions in spam script

---------

Co-authored-by: Aaron Buchwald <aaron.buchwald56@gmail.com>

* Extract shared scripts (#1161)

* chore: rebase on top of master

* refactor: shellcheck

* fix: update mockgen version to pass ci checks

* fix: build script merge

* fix: resolve issues in tests.lint.sh

* fix: resolve imports and shellcheck issues

* fix: shellcheck issues

* fix: downgrade golangci-lint to pass ci

* fix: separate unit testing scripts

* fix: ci issues

* fix: ci issues

* fix: tokenvm static analysis

* fixup: Remove merge conflicts

---------

Co-authored-by: vikinatora <vikinatora@gmail.com>

* fix comment (#1163)

* Add commit signing as instruction in contributing.md (#1164)

* Add Tx Indexer Extension (#1143)

* Remove rejected from controller interface

* Update mocks and remove unused mock_auth from mocks.mockgen.txt

* Refactor tx indexing into indexer extension

* Close txDB in morpheusvm on shutdown

* Update name of unused result param to _

* fix lint

* Update license year

* Address nits

* move action handler to its own accepted subscriber

* Add interface check for noopTxIndexer

* Fix typo

* fix unused receivers

* fix linting

* invoke combined subscriber to fix integration test

* move action handler prior to tx indexer to fix pre-existing race condition

* Add TODO to integration test flagging flaky test

* Noop -> NoOp

* Address comments

* Export NoOpTxIndexer

* rename combinedAcceptedSubscriber AcceptedSubscribers

* [cli/spam] Incorporate `vryx-poc` Improvements (#1091)

* fix withholding spelling

* define prompt float

* remove useless const

* more cleanup

* making progress

* working on issue loop

* turn this into an interface rather than providing this functions

* making progress on core loop

* code is compiling

* bound inflight returns with channel

* cleanup struct

* remove resolved todo

* update transfer to include memo

* cleanup morephus cli definition

* remove unused variables

* cleanup tokenvm client

* add issuance limiter

* fix errors

* don't add tx to block if too big

* put more txs in flight

* cleanup logging

* add fix for fees

* track outstanding txs

* fix var order

* fix returned balance

* test fix

* linter

* add comments

* resolve linter for morpheusvm

* pass tokenvm linter

* add memo to tx size

* gci imports

* trying again

* integration tests now passing

* Add TODO to fix race conditions in spam script

---------

Co-authored-by: Aaron Buchwald <aaron.buchwald56@gmail.com>

* Extract shared scripts (#1161)

* chore: rebase on top of master

* refactor: shellcheck

* fix: update mockgen version to pass ci checks

* fix: build script merge

* fix: resolve issues in tests.lint.sh

* fix: resolve imports and shellcheck issues

* fix: shellcheck issues

* fix: downgrade golangci-lint to pass ci

* fix: separate unit testing scripts

* fix: ci issues

* fix: ci issues

* fix: tokenvm static analysis

* fixup: Remove merge conflicts

---------

Co-authored-by: vikinatora <vikinatora@gmail.com>

* fix comment (#1163)

* Add commit signing as instruction in contributing.md (#1164)

* Add Tx Indexer Extension (#1143)

* Remove rejected from controller interface

* Update mocks and remove unused mock_auth from mocks.mockgen.txt

* Refactor tx indexing into indexer extension

* Close txDB in morpheusvm on shutdown

* Update name of unused result param to _

* fix lint

* Update license year

* Address nits

* move action handler to its own accepted subscriber

* Add interface check for noopTxIndexer

* Fix typo

* fix unused receivers

* fix linting

* invoke combined subscriber to fix integration test

* move action handler prior to tx indexer to fix pre-existing race condition

* Add TODO to integration test flagging flaky test

* Noop -> NoOp

* Address comments

* Export NoOpTxIndexer

* rename combinedAcceptedSubscriber AcceptedSubscribers

* Update vm/vm.go

Co-authored-by: aaronbuchwald <aaron.buchwald56@gmail.com>
Signed-off-by: Franfran <51274081+iFrostizz@users.noreply.github.com>

* wrap err and fix restoring off-by-one

* fix operator inverted

* off-by-one on amount of accepted to restore

* typo

* Update vm/vm.go

Co-authored-by: aaronbuchwald <aaron.buchwald56@gmail.com>
Signed-off-by: Franfran <51274081+iFrostizz@users.noreply.github.com>

* Update vm/vm.go

Co-authored-by: aaronbuchwald <aaron.buchwald56@gmail.com>
Signed-off-by: Franfran <51274081+iFrostizz@users.noreply.github.com>

* use constants for MaxAcceptorSize and MinAcceptedBlockWindow and update
error messages

* add provided value in error message

---------

Signed-off-by: Franfran <51274081+iFrostizz@users.noreply.github.com>
Co-authored-by: Patrick O'Grady <prohb125@gmail.com>
Co-authored-by: Aaron Buchwald <aaron.buchwald56@gmail.com>
Co-authored-by: marun <maru.newby@avalabs.org>
Co-authored-by: vikinatora <vikinatora@gmail.com>
Co-authored-by: PolyMa <151764357+polymaer@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

2 participants