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

Enable out-of-order transaction processing for asset table #77

Merged
merged 36 commits into from
Jul 26, 2023

Conversation

danenbm
Copy link
Contributor

@danenbm danenbm commented May 24, 2023

Notes

  • Update owner and delegate in asset table when collection or creator verification occurs.
  • Modify program transformers to enable out-of-order transaction processing by adding the following changes:
    • Upsert leaf info based on seq OR was_decompressed flag.
    • Upsert owner and delegate based on owner_delegate_seq.
    • Upsert "compression status" fields (compressed, compressible, supply, and supply_mint) based on was_decompressed flag.

Database changes

  • Add owner_delegate_seq and was_decompressed columns to asset table, and PROGRAMMABLE_NFT to specification_asset_class type.
    • Regenerate Sea ORM objects.
    • Needed to temporarily comment out a performance improvement migration that removed foreign keys so that SeaORM CLI would regenerate, since it builds relations based on those constraints.
    • Needed to add a migration to add PROGRAMMABLE_NFT to specification_asset_class since it is needed by the API code but missing from the SQL code.

Other misc. changes

  • Fix docker preparation script to build solana-program-library.
  • Also remove unused 'filling' variable.
  • Rustfmt/clippy fixes as I change files.

Testing

  1. Made sure it builds and runs in Docker.
  2. Read API specific test: Ran a sequence of CreateTree/Mint, Transfer, and Burn in reverse order. While running in reverse order I observed that could not get info for the asset via getAsset until Mint was indexed.
  3. Used transaction forwarder to send out sequences of transactions in correct and reverse order, and in each case observe cl_items and asset table in database. Covered every indexed instruction (note MintToCollection indexes as Mint). Did this process on both main branch and this PR branch and compared all results.
Transaction sequence Forwards on main Backwards on main Forwards on PR branch Backwards on PR branch
CreateTree/Mint, Transfer, and Burn OK cl_items OK, asset incorrect due to only indexing Mint OK OK
CreateTree/Mint, Redeem, CancelRedeem, second Redeem, and Decompress cl_items andasset incorrect due to Redeem not indexing cl_items incorrect due to Redeem not indexing, asset incorrect due to only indexing Mint OK OK
CreateTree/Mint, Transfer, second Transfer OK cl_items OK, asset incorrect due to only indexing Mint OK OK
CreateTree/Mint, Delegate, Transfer OK cl_items OK, asset incorrect due to only indexing Mint OK OK
CreateTree/Mint, VerifyCreator OK cl_items OK, asset incorrect due to only indexing Mint, asset_creators incorrect due to missing VerifyCreator OK OK, fixed in #87 cl_items and asset OK, asset_creators incorrect due to missing VerifyCreator
CreateTree/Mint, VerifyCollection cl_items and asset OK, asset_grouping incorrect due to missing VerifyCollection cl_items OK, asset incorrect due to only indexing Mint, asset_grouping incorrect due to missing VerifyCollection OK, fixed in #90. SetAndVerifyCollection still needs Blockbuster fix to index. cl_items and asset OK, asset_grouping incorrect due to missing VerifyCollection OK, fixed in #90. SetAndVerifyCollection still needs Blockbuster fix to index. cl_items and asset OK, asset_grouping incorrect due to missing VerifyCollection

@danenbm danenbm force-pushed the danenbm/update-asset branch from 39c630c to 9299d79 Compare May 24, 2023 21:34
* This allows out-of-order Bubblegum transactions to
create and update the asset table.
* Upsert leaf schema, owner, delegate, and seq separately
since those are updated by all instructions and gated
by sequence number to ensure freshest value.
* Mint, burn, and decompress happen without regard to
sequence number because they operate on unique fields.
* Mint and burn have been updated but Decompress still
needs to be fixed to handle out of order transactions.
* Also remove unused 'filling' variable.
@NicolasPennie
Copy link
Collaborator

What's the motivation for this PR? Was anyone observing indexing issues due to out of order transactions?

@danenbm
Copy link
Contributor Author

danenbm commented May 26, 2023

What's the motivation for this PR? Was anyone observing indexing issues due to out of order transactions?

@NicolasPennie what I heard from @austbot is that both you (Helius) and Triton had observed issues with out of order transactions and as a result, had to essentially stop running the backfiller and use a different method for in-order filling. I also talked to @austbot and @linuskendall about this and it does in fact look like this reference code could not handle out of order transactions in general.

The cl_table looks like it can handler out of order transactions, but the asset table does not. The reason the cl_table can handle out of order transactions is because all instructions except for burn and decompress update the same leaf schema fields, and use the tree sequence number to prevent stale data.

For example, if a transfer was ingested followed by a mint (out of order), the change log will be inserted into cl_items for the transfer, and then when the mint is ingested, it won't update the fields in the table with stale data (owner, delegate, etc.) because the sequence number for the mint will be a lower number than the sequence number for the transfer that was already ingested.

However, using this same example but considering the asset table, what happens is when the transfer comes in first, is the asset is not found in the table, because the mint did not get indexed yet. So the update fails and logs a message and the indexer continues. Later when the mint instruction is indexed, it inserts the row for the asset into the asset table. Now the asset table contains the asset with a stale owner, delegate, and leaf hash.

This PR addresses this issue with the asset table, allowing for out-of-order Bubblegum transactions to be processed in general. I am still looking at the edge cases and how this works for non-compressed NFTs.

@NicolasPennie
Copy link
Collaborator

What's the motivation for this PR? Was anyone observing indexing issues due to out of order transactions?

@NicolasPennie what I heard from @austbot is that both you (Helius) and Triton had observed issues with out of order transactions and as a result, had to essentially stop running the backfiller and use a different method for in-order filling. I also talked to @austbot and @linuskendall about this and it does in fact look like this reference code could not handle out of order transactions in general.

The cl_table looks like it can handler out of order transactions, but the asset table does not. The reason the cl_table can handle out of order transactions is because all instructions except for burn and decompress update the same leaf schema fields, and use the tree sequence number to prevent stale data.

For example, if a transfer was ingested followed by a mint (out of order), the change log will be inserted into cl_items for the transfer, and then when the mint is ingested, it won't update the fields in the table with stale data (owner, delegate, etc.) because the sequence number for the mint will be a lower number than the sequence number for the transfer that was already ingested.

However, using this same example but considering the asset table, what happens is when the transfer comes in first, is the asset is not found in the table, because the mint did not get indexed yet. So the update fails and logs a message and the indexer continues. Later when the mint instruction is indexed, it inserts the row for the asset into the asset table. Now the asset table contains the asset with a stale owner, delegate, and leaf hash.

This PR addresses this issue with the asset table, allowing for out-of-order Bubblegum transactions to be processed in general. I am still looking at the edge cases and how this works for non-compressed NFTs.

Great, thanks for the context! That was really helpful.

@danenbm danenbm force-pushed the danenbm/update-asset branch from 6285923 to 1dfdfd0 Compare May 26, 2023 21:48
@danenbm danenbm force-pushed the danenbm/update-asset branch from 330f7b8 to 103a2ae Compare June 1, 2023 22:50
@danenbm danenbm force-pushed the danenbm/update-asset branch from 103a2ae to 634030d Compare June 2, 2023 15:58
@danenbm danenbm force-pushed the danenbm/update-asset branch from 98b5be0 to 07b6712 Compare June 2, 2023 17:19
@danenbm danenbm changed the title Danenbm/update asset Enable out-of-order transaction processing for asset table Jun 2, 2023
@danenbm danenbm marked this pull request as ready for review June 2, 2023 17:44
Copy link
Collaborator

@austbot austbot left a comment

Choose a reason for hiding this comment

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

reviewed on a call with md

@@ -116,10 +106,20 @@ pub enum SpecificationAssetClass {
Print,
#[sea_orm(string_value = "PRINTABLE_NFT")]
PrintableNft,
#[sea_orm(string_value = "PROGRAMMABLE_NFT")]
ProgrammableNft,
#[sea_orm(string_value = "TRANSFER_RESTRICTED_NFT")]
Copy link
Collaborator

Choose a reason for hiding this comment

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

can remove later

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why is that, because we won't ever use it?

@austbot
Copy link
Collaborator

austbot commented Jun 7, 2023

this is LGTM @danenbm

@danenbm
Copy link
Contributor Author

danenbm commented Jun 7, 2023

this is LGTM @danenbm

Thank you. Still testing this and working through some issues with upserting parts of an asset row due to NULL constraints on some of the fields. Putting back to draft until I fix them

@danenbm danenbm marked this pull request as draft June 7, 2023 22:34
README.md Show resolved Hide resolved
)
.await?;

upsert_asset_with_seq(txn, id_bytes.to_vec(), seq as i64).await
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this necessary? I think we can just update this within one of the earlier methods. We try to reduce to number of DB calls we need to make in favour of performance. (applies for all cNFT indexing methods).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a previous iteration of this PR I used the existing seq to protect leaf updates, and the new owner_delegate_seq to protect owner and delegate fields. Because of this, an effect of my PR changes is that when the leaf was set to None during decompression, so was the seq, and then for decompressed assets, a Read API call to getAsset would return null for the seq.

Maybe that was OK because people don't need the seq when the asset has been removed from the merkle tree. But the merkle tree is still listed and wanted it to match previous behavior.

Maybe I can change it back if we want to remove an extra update that is only for the seq.

Copy link
Collaborator

@NicolasPennie NicolasPennie Jun 27, 2023

Choose a reason for hiding this comment

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

a Read API call to getAsset would return null for the seq.

This would be acceptable if the asset can never be re-compresssed. But I think you plan on adding that, right? If that's the case, then it makes sense to keep the seq value available after a decompression.

Copy link
Contributor Author

@danenbm danenbm Jun 27, 2023

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 plan to add re-compression. Maybe it would insert back into the same tree like cancel_redeem does but instead of using a voucher it would burn the token-metadata based NFT. But I could also see it just doing a new append to the tree via mint_v1.

Either way when it got added back into the tree, I think the asset would get a new seq that is probably a much higher number that what was previously stored there when it was decompressed. I guess what I'm thinking at the moment is that I'm not sure of the use for the seq after decompression since you cannot use it for re-compression, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For reference the previous version of my PR prior to this commit used seq to protect leaf and owner_delegate_seq to protect owner+delegate and had one less upsert overall. May be worth revisiting that approach to save on an update/upsert.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess what I'm thinking at the moment is that I'm not sure of the use for the seq after decompression since you cannot use it for re-compression, right?

If recompressing will add that NFT back into the tree via the same leaf, then we need to keep the seq number around. Otherwise you can have out of order bugs with out of order decompress + recompress transactions. And we need to be able to always handle any order to be safe, even if its unlikely to happen.

Copy link
Contributor Author

@danenbm danenbm Jun 28, 2023

Choose a reason for hiding this comment

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

I think you uncovered something that will be an issue for decompress > recompress with the current design both of this PR and with the system in general. The fact that decompress doesn't have any association with the tree, means there is no change log event and thus no seq associated with the decompress.

For this PR, the way I found to deal with decompress being out of order with others was to add a boolean was_decompressed flag, which essentially acts like a 1-bit sequence number, which is a one way door assuming no events after the decompression.

If we add recompress we need to find another way to deal with decompress being out of order with other instructions that can provide a seq from the tree.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@NicolasPennie @danenbm we must keep the seq in the db if the asset is recompressed.
there are other things though, since you cant close the mint acccount you cannot fully recompress. so you can use that as a handle some how.

Copy link
Contributor Author

@danenbm danenbm Jul 13, 2023

Choose a reason for hiding this comment

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

Leaving code as is to make sure seq stays updated in asset table.

However, I still don't think recompression can work out of order, because decompression is not sequenced by the tree. Am I missing something?

@danenbm danenbm merged commit 8f82f42 into main Jul 26, 2023
muhitrhn pushed a commit to muhitrhn/digital-asset-rpc-infrastructure that referenced this pull request Oct 6, 2023
muhitrhn pushed a commit to muhitrhn/digital-asset-rpc-infrastructure that referenced this pull request Oct 6, 2023
@danenbm danenbm deleted the danenbm/update-asset branch October 9, 2023 06:07
@danenbm danenbm restored the danenbm/update-asset branch October 9, 2023 06:07
@danenbm danenbm deleted the danenbm/update-asset branch October 9, 2023 06:07
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.

4 participants