-
Notifications
You must be signed in to change notification settings - Fork 736
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
node: Integrate Transfer Verifier into the Ethereum watcher #4233
base: main
Are you sure you want to change the base?
Conversation
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 a couple of suggestions
688ee88
to
96a2566
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.
Integration looks good overall! Would be good to deal with the outstanding TODOs if possible
node/pkg/watchers/evm/watcher.go
Outdated
switch w.env { | ||
case common.UnsafeDevNet: | ||
tbridge = eth_common.BytesToAddress(sdk.KnownDevnetTokenbridgeEmitters[w.chainID]) | ||
weth = eth_common.HexToAddress("0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E") |
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.
Re the TODO, how about creating a set of Known[X]NetWETHAddress
constants in the SDK alongside the existing constants?
node/pkg/watchers/evm/watcher.go
Outdated
w.txVerifier, tvErr = txverifier.NewTransferVerifier( | ||
w.ethConn, | ||
&addrs, | ||
20, |
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.
Comment to explain the choice of magic number?
2e9b737
to
bdcbf9f
Compare
sdk/mainnet_consts.go
Outdated
var KnownWrappedNativeAddress = map[vaa.ChainID]common.Address{ | ||
// WETH |
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.
It makes sense to try and maintain consistency here by typing the mapping as map[vaa.ChainID]string
.
sdk/devnet_consts.go
Outdated
@@ -41,3 +42,9 @@ var KnownDevnetAutomaticRelayerEmitters = []struct { | |||
{ChainId: vaa.ChainIDEthereum, Addr: "000000000000000000000000cc680d088586c09c3e0e099a676fa4b6e42467b4"}, | |||
{ChainId: vaa.ChainIDBSC, Addr: "000000000000000000000000cc680d088586c09c3e0e099a676fa4b6e42467b4"}, | |||
} | |||
|
|||
// KnownDevnetWrappedNativeAddress is a map of wrapped native addresses by chain ID, e.g. WETH for Ethereum | |||
var KnownDevnetWrappedNativeAddresses = map[vaa.ChainID]common.Address{ |
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.
Same comment here about typing to map[vaa.ChainID]string
.
sdk/testnet_consts.go
Outdated
var KnownTestnetWrappedNativeAddresses = map[vaa.ChainID]common.Address{ | ||
// WETH |
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.
Same comment here about typing to map[vaa.ChainID]string
. I'd also argue that we can have one less import in this file by doing so.
node/pkg/common/chainlock.go
Outdated
// This type represents whether a message has been verified for some definition of verification. This may mean different things | ||
// on a per-application or per-chain basis. | ||
// NOTE: This status is currently used only by the Transfer Verifier for supported chains. |
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.
// This type represents whether a message has been verified for some definition of verification. This may mean different things | |
// on a per-application or per-chain basis. | |
// NOTE: This status is currently used only by the Transfer Verifier for supported chains. | |
// The `VerificationState` is the result of applying transfer verification to the transaction associated with the `MessagePublication`. | |
// While this could likely be extended to additional security controls in the future, it is only used for `txverifier` at present. |
node/pkg/common/chainlock.go
Outdated
|
||
const ( | ||
// The default state for a message. This can be used before verification occurs. If no verification is required, `NotApplicable` should be used instead. | ||
// NOTE: this value is used as the default, zero-value for the type, so this field should not be re-ordered among other variants. |
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.
Can remove this line
node/pkg/common/chainlock.go
Outdated
Rejected | ||
// Represents an unusual state after validation, neither confirmed to be good or bad. | ||
Anomalous | ||
// Represents a "known good" status where a Message has been validated and the result is good. The message should be process normally. |
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.
// Represents a "known good" status where a Message has been validated and the result is good. The message should be process normally. | |
// Represents a "known good" status where a Message has been validated and the result is good. The message should be processed normally. |
node/pkg/common/chainlock.go
Outdated
@@ -33,6 +52,9 @@ type MessagePublication struct { | |||
// Unreliable indicates if this message can be reobserved. If a message is considered unreliable it cannot be | |||
// reobserved. | |||
Unreliable bool | |||
// This type represents whether a message has been verified for some definition of verification. This may mean different things |
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.
Reuse comment above VerificationState
type definition.
node/pkg/common/chainlock.go
Outdated
) | ||
} | ||
|
||
func (v VerificationState) String() string { |
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 move this definition up to the type definition.
node/pkg/watchers/evm/watcher.go
Outdated
} | ||
} | ||
|
||
updateErr := msg.SetVerificationState(verificationState) |
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 wonder about this pattern. My thinking is that before transfer verification this logic did not matter, and therefore the guardian would continue processing transfers regardless of the transfer verification operations. Should we not have a verification state that indicates this error ocurred, keep logging it, and pass the message to the msg channel anyway? Until we see this succeeding in a live environment, we should probably make transfer verification observent rather than interacting, if that makes sense.
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 that's what happens generally. This error is more of a type-safety thing to prevent overwriting an existing state. It could log an error instead of returning I suppose.
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 moved this inside of the if
statement in this function. In the previous commit, the state of the message would be changed from NotVerified to NotApplicable, and potentially return an error. Now, the error handling and state change will not occur unless transfer verifier is enabled.
@@ -701,14 +783,25 @@ func (w *Watcher) postMessage(logger *zap.Logger, ev *ethabi.AbiLogMessagePublis | |||
zap.Uint8("ConsistencyLevel", ev.ConsistencyLevel), | |||
) | |||
|
|||
w.msgC <- message | |||
ctx, cancel := context.WithCancel(context.Background()) |
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 took a queue from the Sui watcher and made it so the sui tx verifier uses context.WithTimeout
, which kind of makes more sense to me. Unless there is something specific that cancels the context in the event of an 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 I chose WithCancel here because other parts of the watcher do this, and they can be cancelled if the code panics. Basically I'm going off of this:
wormhole/node/pkg/watchers/evm/watcher.go
Lines 206 to 210 in c913135
// later on we will spawn multiple go-routines through `RunWithScissors`, i.e. catching panics. | |
// If any of them panic, this function will return, causing this child context to be canceled | |
// such that the other go-routines can free up resources | |
ctx, watcherContextCancelFunc := context.WithCancel(parentCtx) | |
defer watcherContextCancelFunc() |
node/pkg/watchers/evm/reobserve.go
Outdated
|
||
if pubErr != nil { | ||
w.logger.Error("Error when publishing message", zap.Error(err)) | ||
continue |
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.
If we do decide to continue sending the message publication over the channel, regardless of potential runtime errors, remember to remove the continue
so the number of observations is still incremented.
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 removed the continue statements in the most recent commit
@@ -146,6 +146,7 @@ spec: | |||
- --ccqEnabled=true | |||
- --ccqAllowedRequesters=beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe,25021A4FCAf61F2EADC8202D3833Df48B2Fa0D54 | |||
- --ccqAllowedPeers=12D3KooWSnju8zhywCYVi2JwTqky1sySPnmtYLsHHzc4WerMnDQH,12D3KooWM6WqedfR6ehtTd1y6rJu3ZUrEkTjcJJnJZYesjd89zj8 | |||
# - --transferVerifierEnabledChains=1 |
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.
Is this meant to be commented?
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.
Yes, similar to Governor in the same file.
node/cmd/guardiand/node.go
Outdated
// NOTE: Using a known capacity and counter here avoids unnecessary reallocations compared to using `append()`. | ||
enabled := make([]vaa.ChainID, len(parsed)) |
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.
Why not just append? This is startup, which I don't think is that performance-sensitive.
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.
Agreed but I don't think there's much benefit to append
node/cmd/guardiand/node.go
Outdated
@@ -500,6 +510,8 @@ func init() { | |||
gatewayRelayerKeyPassPhrase = NodeCmd.Flags().String("gatewayRelayerKeyPassPhrase", "", "Pass phrase used to unarmor the gateway relayer key file") | |||
|
|||
subscribeToVAAs = NodeCmd.Flags().Bool("subscribeToVAAs", false, "Guardiand should subscribe to incoming signed VAAs, set to true if running a public RPC node") | |||
|
|||
transferVerifierEnabledChains = NodeCmd.Flags().String("transferVerifierEnabledChains", "", "Transfer Verifier will be enabled for these chain IDs (comma-separated)") |
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.
Why not use Flags().IntSlice()?
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.
Done using UintSlice in the latest commit
- Avoid modifying the message or changing error handling in the watcher's message publication flow when transfer verifier is disabled - Remove continue statements when verifyAndPublish returns an error in the reobservation flow - Modify code comments around VerificationState - Use UintSlice when parsing CLI flags - Change new SDK constants to be strings instead of common.Address to be consistent with other SDK values
@@ -500,6 +510,8 @@ func init() { | |||
gatewayRelayerKeyPassPhrase = NodeCmd.Flags().String("gatewayRelayerKeyPassPhrase", "", "Pass phrase used to unarmor the gateway relayer key file") | |||
|
|||
subscribeToVAAs = NodeCmd.Flags().Bool("subscribeToVAAs", false, "Guardiand should subscribe to incoming signed VAAs, set to true if running a public RPC node") | |||
|
|||
transferVerifierEnabledChains = NodeCmd.Flags().UintSlice("transferVerifierEnabledChains", make([]uint, 2), "Transfer Verifier will be enabled for these chain IDs (comma-separated)") |
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's the idea behind the default case?
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.
Hm my intention is that you can invoke the guardian with e.g.:
txVerifierChains=1,2
but not txVerifierChains=
(blank)
If you mean the make
here, I gave it a length and capacity of 2
more or less arbitrarily. I guess I was thinking that if we enable Ethereum and Sui, that's two chains. The value could easily be 0
or 10
or anything else.
// KnownWrappedNativeAddress is a map of wrapped native addresses by chain ID, e.g. WETH for Ethereum | ||
var KnownWrappedNativeAddress = map[vaa.ChainID]string{ | ||
// WETH | ||
vaa.ChainIDEthereum: "0xc8f93d9738e7Ad5f3aF8c548DB2f6B7F8082B5e8", |
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.
Don't know what I'm missing, but WETH on eth mainnet is at 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
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 you're right, I think this was a bad copy-paste from the testnet or devnet constant
// Channel to send new messages to. | ||
// Channel for sending new MesssagePublications. Messages should not be sent | ||
// to this channel directly. Instead, they should be wrapped by | ||
// a call to `publishIfSafe()`. |
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.
Update to verifyAndPublish()
|
||
// Fetch the constants for the Token Bridge and the WETH address from the SDK. | ||
var tbridge []byte | ||
var weth string |
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 we should rename weth
to wnative
?
weth = sdk.KnownTestnetWrappedNativeAddresses[w.chainID] | ||
case common.MainNet: | ||
tbridge = sdk.KnownTokenbridgeEmitters[w.chainID] | ||
// https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 |
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.
remove comment
CoreBridgeAddr: w.contract, | ||
TokenBridgeAddr: eth_common.BytesToAddress(tbridge[:]), |
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.
In my code I added a KnownMainnetCoreContracts
to the constants file. I think it just makes sense. You don't have to do it here, but I'm going to keep the change on my branch.
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.
Why not use KnownTokenbridgeEmitters
?
pruneHeightDelta := uint64(20) | ||
var tvErr 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 can here Jeff and Bruce saying this needs to be a constant.
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.
This needs to be a constant!
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 prune height delta you mean?
New Features
verifyAndPublish
VerificationState
.If Transfer Verifier is enabled
If Transfer Verifier is not enabled
Design Considerations
Modifying MessagePublication
This PR modifies
MessagePublication
to add a new status based on whether the Message Publication is verified. This decision was made to handle Transfer Verification cases across many chains. For example, the EVM logs are reliable enough that we can confidently rule a message as Valid or Rejected. Other ecosystems (i.e. Sui, but perhaps also Solana, etc.) are not so clear cut. In this case, we may want to mark a transaction as Anomalous rather than outright rejecting it.Using this new enum allow us to do this.
Other potential benefits:
Scope of this PR
The idea with this PR is to test the modifications to the Ethereum watcher without enabling the transfer verifier yet. We can release this in testnet or mainnet to be sure that the changes here are stable.
Once we're confident that the underlying mechanism is working well, we can add some logic in the Guardian to actually react when a MessagePublication has a status like Rejected or Anomalous. For now, changing the reaction of the watcher to a "bad" message is out of scope.
Questions
Related Work