-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Conversation
b686a5e
to
7618fa6
Compare
3140076
to
5c3c863
Compare
a461a06
to
5704779
Compare
5704779
to
c5c19db
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
@@ -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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
node/impl/full/eth.go
Outdated
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 | ||
} |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- It can land on chain, and is eventually finalized.
- It can land on chain and then be reverted due to a reorg, "returning" to the mpool.
- It can be replaced.
- 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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
108f7cc
to
47023ec
Compare
b8a14fe
to
e92f754
Compare
e92f754
to
f8121c8
Compare
There was a problem hiding this 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:
- Populating the index on snapshot import.
- Mpool index management (see threads above).
Howdy!
Ignore this ^, right now this should be implemented on the explorer's side. |
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:
<PR type>: <area>: <change being made>
fix: mempool: Introduce a cache for valid signatures
PR type
: fix, feat, build, chore, ci, docs, perf, refactor, revert, style, testarea
, e.g. api, chain, state, market, mempool, multisig, networking, paych, proving, sealing, wallet, deps