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

feat: Store mapping of eth transaction hashes to message cids #9965

Merged
merged 6 commits into from
Jan 18, 2023

Conversation

geoff-vball
Copy link
Contributor

@geoff-vball geoff-vball commented Jan 4, 2023

Related Issues

Closes #9839

Proposed Changes

Adds a database to keep a mapping of Eth Transaction Hash to Filecoin Message CID. Originally we were just using reversible transformation of the Message CID for the hash, but Eth tooling expects a specific method of generation, so we have changed it to match. This is only true for Ethereum messages, all other messages will use the old reversible transformation.

I've created a separate database from the events database just for ease of use. If we really want them to be in the same database, we should refactor the database to be more globally accessible.

@arajasek is worried that saving this mapping for all mpool messages is a possible attack vector to spam the node.

I do want to add options for being able to clear out old entries to the database. I'm welcome to suggestions on how this might be implemented.

Additional Info

Checklist

Before you mark the PR ready for review, please make sure that:

  • Commits have a clear commit message.
  • PR title is in the form of of <PR type>: <area>: <change being made>
    • example: fix: mempool: Introduce a cache for valid signatures
    • PR type: fix, feat, build, chore, ci, docs, perf, refactor, revert, style, test
    • area, e.g. api, chain, state, market, mempool, multisig, networking, paych, proving, sealing, wallet, deps
  • New features have usage guidelines and / or documentation updates in
  • Tests exist for new functionality or change in behavior
  • CI is green

@geoff-vball geoff-vball force-pushed the gstuart/eth-hash branch 7 times, most recently from b686a5e to 7618fa6 Compare January 5, 2023 17:42
@geoff-vball geoff-vball marked this pull request as ready for review January 5, 2023 18:47
@geoff-vball geoff-vball requested a review from a team as a code owner January 5, 2023 18:47
@geoff-vball geoff-vball requested review from raulk and arajasek January 5, 2023 18:50
@geoff-vball geoff-vball force-pushed the gstuart/eth-hash branch 18 times, most recently from 3140076 to 5c3c863 Compare January 12, 2023 17:13
Base automatically changed from feat/nv18-fevm to release/v1.20.0 January 13, 2023 19:11
Copy link
Member

@raulk raulk left a comment

Choose a reason for hiding this comment

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

Great work here! Feel free to ping me if you have any doubts about these comments.

@@ -778,6 +778,7 @@ type FullNode interface {
EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read
EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read
EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) //perm:read
EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) //perm:read
Copy link
Member

Choose a reason for hiding this comment

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

Interesting, this is a Filecoin specific eth_ method but I don't think it matters. Can we separate such Filecoin specific Eth methods to another interface that we embed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you explain exactly what you mean? Do you want another API in EthAPI on the same level as EthModuleAPI and EthEventAPI?

chain/ethhash/eth_transaction_hash_lookup.go Outdated Show resolved Hide resolved
chain/ethhash/eth_transaction_hash_lookup.go Outdated Show resolved Hide resolved
chain/ethhash/eth_transaction_hash_lookup.go Outdated Show resolved Hide resolved
chain/ethhash/eth_transaction_hash_lookup.go Outdated Show resolved Hide resolved
@@ -69,6 +69,9 @@ type LockedRepo interface {
// SplitstorePath returns the path for the SplitStore
SplitstorePath() (string, error)

// SqlitePath returns the path for the Sqlite database
SqlitePath() (string, error)
Copy link
Member

Choose a reason for hiding this comment

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

I think this is a bit ambiguous, since there's already a sqlite for events. Either we add that path here too, or we qualify this path to say that it's for the EthTxHash mapping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do think we should map the events db to the same path. It gives an easy place to find the databases that we don't need to configure.

It also allows us to keep the database inside the LOTUS_PATH, which I wasn't able to do with the setup from the events db (though that might have been my error with the way our dependency injection works).

Comment on lines 1765 to 1797
func (m EthTxHashManager) Apply(ctx context.Context, from, to *types.TipSet) error {
for _, blk := range to.Blocks() {
_, smsgs, err := m.StateAPI.Chain.MessagesForBlock(ctx, blk)
if err != nil {
return err
}

for _, smsg := range smsgs {
if smsg.Signature.Type != crypto.SigTypeDelegated {
continue
}

hash, err := EthTxHashFromSignedFilecoinMessage(ctx, smsg, m.StateAPI)
if err != nil {
return err
}

err = m.TransactionHashLookup.InsertTxHash(hash, smsg.Cid(), int64(to.Height()))
if err != nil {
return err
}
}
}

return nil
}

type EthTxHashManager struct {
StateAPI StateAPI
TransactionHashLookup *ethhashlookup.TransactionHashLookup
}

func (m EthTxHashManager) Revert(ctx context.Context, from, to *types.TipSet) error {
return nil
}
Copy link
Member

Choose a reason for hiding this comment

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

The edge case where a reorg happens and messages get included in a future epoch is handled by the ON CONFLICT clause, which is great. However, if a message is added in tipset 1A, then we reorg to tipset 1B which does not include the message, and it only lands in tipset 4A, we would've spent 3 tipsets thinking that the message exists and it landed on epoch 1. Is that correct?

Similarly, there's the edge case where the chain reorgs and completely removes a message such that it never again lands on chain.

I'm not sure how important consistency in such scenarios is -- it might be acceptable to have wrong data in the index if the epoch is only used for garbage collection. But we should never return data over the interface that is not truthful!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I want to use just raw timestamps instead of epochs here. The column should just be for optionally periodically clearing out the db so users can manage the size of their state. The problem with the current setup is that we don't know when to clear out mpool mappings if they're removed. We don't need the epoch information in this table, we can look it up once we get the cid.

If we're not storing the epoch, I don't think there are any inconsistency issues.

Copy link
Member

Choose a reason for hiding this comment

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

AFAIK once a transaction is successfully added in the mpool (has passed validation), I believe that four logical outcomes are possible:

  1. It can land on chain, and is eventually finalized.
  2. It can land on chain and then be reverted due to a reorg, "returning" to the mpool.
  3. It can be replaced.
  4. It can drop.

Are we handling all of these? Wanna make sure that there's no possible issue here with stale or bad data in the cache?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't explicitly remove mappings for messages in scenarios 2-4, so we might end up with a few mappings in the database for transactions that never fully land on chain. I don't think this is an issue. I've removed the epoch which could've led to inconsistencies. There's no harm in returning a cid for a message that is no longer relevant - the caller will look up the cid and will be unable to find it in their store.

Choose a reason for hiding this comment

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

@geoff-vball are you saying that we have covered the proper use cases? To your point, stale or useless messages are likely harmless to leave around outside of the space it takes up. When it comes to getting a canonical version of the data, I imagine that comes from a snapshot?

Either way, being able to garbage collect that information seems orthogonal to the criticality of how re-orgs are handled.

Are you saying that we are safe against the 4 considered cases? Is there any other blocker?

Copy link
Contributor Author

@geoff-vball geoff-vball Jan 17, 2023

Choose a reason for hiding this comment

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

Yeah, the only situations where you'd be missing something from your DB are:

  • It has been garbage collected by the retention policy
  • You had the DB turned off when you synced.

We can add tooling later to rebuild the index if desired.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to summarize how it works:
Adds mapping when:

  • Tx added to mpool
  • Tx processed in block

Removes mappings when:

  • Checks every hour for mappings that have been in the database longer than the retention policy.

Choose a reason for hiding this comment

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

What is the default retention policy? Curious.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@scotthconner Default sets the database to not garbage collect at all. If they notice their db getting too large they can change the retention policy.

Thinking about this some more, I think the database should mirror the retention policy of messages in the blockstore. The mappings become (relatively) useless if we prune a message from our store. You'll be able to find the cid, but you won't be able to use to cid to look up the message.

You could theoretically use that cid to look up the transaction on filfox or filscan. Do we know if they're planning on implementing lookups by Transaction Hashes?

node/impl/full/eth.go Outdated Show resolved Hide resolved
node/impl/full/eth.go Show resolved Hide resolved
node/impl/full/eth.go Outdated Show resolved Hide resolved
@geoff-vball geoff-vball force-pushed the gstuart/eth-hash branch 2 times, most recently from 108f7cc to 47023ec Compare January 16, 2023 06:38
@geoff-vball geoff-vball force-pushed the gstuart/eth-hash branch 3 times, most recently from b8a14fe to e92f754 Compare January 16, 2023 11:43
Copy link
Member

@raulk raulk left a comment

Choose a reason for hiding this comment

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

Great work here. Some loose ends we need to tie up in subsequent PRs:

  1. Populating the index on snapshot import.
  2. Mpool index management (see threads above).

@mur-me
Copy link

mur-me commented Jan 18, 2023

Howdy!
Glif is here.

Our RPC endpoints is still having issues with EthGetBlockByHash operation on the Metamask -> RPC.

I've go deeper and it looks like that something goes wrong on the lotus side, because it putting from ETH Hash into the txhash.db

Example:
0xbc51be0da51511bcf2f9b7fa4915b7fee936d3cf34e4d6ac811f1aefb283fc5d|bafy2bzaceb7qsy5gvx7kygtol5sw7yypomslher73vbp2ssb6rnzrwo6rykku|2023-01-18 13:24:00 - line from db

Just look to the glif explorer hashes, e.x bafy2bzaceb7qsy5gvx7kygtol5sw7yypomslher73vbp2ssb6rnzrwo6rykku - https://explorer.glif.io/tx/bafy2bzaceb7qsy5gvx7kygtol5sw7yypomslher73vbp2ssb6rnzrwo6rykku/?network=hyperspace

You can see that there is no 0xbc51be0da51511bcf2f9b7fa4915b7fee936d3cf34e4d6ac811f1aefb283fc5d like it stored in the db, but there is 0x7f0963a6adfeac1a6e5f656fe30f7324b3923fdd42fd4a41f45b98d9de8e14aa

Ignore this ^, right now this should be implemented on the explorer's side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Transaction Hash Mismatch when deploying contract using Open Zeppelin Upgrades
6 participants