-
Notifications
You must be signed in to change notification settings - Fork 33
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
Fraud reporting improvements #1375
Merged
Merged
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
706e3b0
Feat: add getStateReportChains()
dwasse 6750c23
Feat: prepareStateReport -> ensureAgentActive, use it before verifyin…
dwasse 9b97682
Feat: ensureAgentActive called before verfication, but no longer need…
dwasse 806df4e
Feat: add calls.go with chain-agnostic funcs
dwasse 7cc9e5f
Feat: use submitStateReport() helper
dwasse 13c6cff
Feat: add verifyState() helper
dwasse 16d6878
Cleanup: rename handleX() -> handleXAccepted()
dwasse 2484435
Feat: add handleSnapshot() helper
dwasse f7df42e
WIP: coalesce FraudSnapshot and FraudAttestation into StateValidation…
dwasse 7a41261
Feat: encorporate StateValidationData usage in contract calls
dwasse 372c0c9
Cleanup: FraudSnapshot -> SnapshotWithMetadata, FraudAttestation -> A…
dwasse 0912968
Cleanup: consistent data field naming, add comments
dwasse e0493fc
Cleanup: remove logs
dwasse 3ac93cf
Cleanup: lint
dwasse 2068db0
Feat: use getAgentStatus() helper
dwasse c3dfd2d
Cleanup: unused param
dwasse d85dabb
Cleanup: move getDisputeStatus() to calls.go, add comments
dwasse 5620e75
Cleanup: move ensureAgentActive() helper to calls.go
dwasse 7fd9a71
Cleanup: move relayActiveAgentStatus() helper to calls.go
dwasse bb290b6
Cleanup: remove log
dwasse 3759690
Merge branch 'master' into feat/fraud-report-enhancement
dwasse 7d819c0
Fix: use ptr in data assign
dwasse c8c2b00
Cleanup: define SnapshotWithMetadata first
dwasse 76ff0a4
Cleanup: better err msg
dwasse 83f2ec3
Feat: fetch agent status from summit instead of assuming Active in re…
dwasse 3eccc29
Cleanup: chainID shadowing
dwasse 82db3d8
Cleanup: err msg verbosity
dwasse 77b8cee
Cleanup: add guard README
dwasse 61b4492
Cleanup: add testing docs to agents README
dwasse 1997382
Cleanup: remove 'Testing Suite' section from guard README
dwasse File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
package guard | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
ethTypes "github.com/ethereum/go-ethereum/core/types" | ||
"github.com/synapsecns/sanguine/agents/types" | ||
"github.com/synapsecns/sanguine/core/retry" | ||
"github.com/synapsecns/sanguine/ethergo/signer/signer" | ||
) | ||
|
||
type agentStatusContract interface { | ||
// GetAgentStatus returns the current agent status for the given agent. | ||
GetAgentStatus(ctx context.Context, address common.Address) (types.AgentStatus, error) | ||
} | ||
|
||
// getAgentStatus fetches the agent status of an agent from the given chain. | ||
func (g Guard) getAgentStatus(ctx context.Context, chainID uint32, agent common.Address) (agentStatus types.AgentStatus, err error) { | ||
var contract agentStatusContract | ||
if chainID == g.summitDomainID { | ||
contract = g.domains[chainID].BondingManager() | ||
} else { | ||
contract = g.domains[chainID].LightManager() | ||
} | ||
contractCall := func(ctx context.Context) error { | ||
agentStatus, err = contract.GetAgentStatus(ctx, agent) | ||
if err != nil { | ||
return fmt.Errorf("could not get agent status: %w", err) | ||
} | ||
return nil | ||
} | ||
err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not get agent status: %w", err) | ||
} | ||
return agentStatus, nil | ||
dwasse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
dwasse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// verifyState verifies a state on a given chain. | ||
func (g Guard) verifyState(ctx context.Context, state types.State, stateIndex int, data types.StateValidationData) (err error) { | ||
var submitFunc func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) | ||
if types.HasAttestation(data) { | ||
submitFunc = func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { | ||
tx, err = g.domains[state.Origin()].LightInbox().VerifyStateWithAttestation( | ||
transactor, | ||
int64(stateIndex), | ||
data.SnapshotPayload(), | ||
data.AttestationPayload(), | ||
data.AttestationSignature(), | ||
) | ||
return | ||
} | ||
} else { | ||
submitFunc = func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { | ||
tx, err = g.domains[state.Origin()].LightInbox().VerifyStateWithSnapshot( | ||
transactor, | ||
int64(stateIndex), | ||
data.SnapshotPayload(), | ||
data.SnapshotSignature(), | ||
) | ||
return | ||
} | ||
} | ||
|
||
// Ensure the agent that provided the snapshot is active on origin. | ||
ok, err := g.ensureAgentActive(ctx, data.Agent(), state.Origin()) | ||
if err != nil { | ||
return fmt.Errorf("could not ensure agent is active: %w", err) | ||
} | ||
if !ok { | ||
logger.Infof("Agent %s is not active on chain %d; not verifying snapshot state", data.Agent().Hex(), state.Origin()) | ||
return nil | ||
} | ||
|
||
_, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(state.Origin())), submitFunc) | ||
if err != nil { | ||
return fmt.Errorf("could not verify state on chain %d: %w", state.Origin(), err) | ||
} | ||
return nil | ||
dwasse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
type stateReportContract interface { | ||
// SubmitStateReportWithSnapshot reports to the inbox that a state within a snapshot is invalid. | ||
SubmitStateReportWithSnapshot(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload []byte, snapSignature []byte) (tx *ethTypes.Transaction, err error) | ||
// SubmitStateReportWithAttestation submits a state report corresponding to an attesation for an invalid state. | ||
SubmitStateReportWithAttestation(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload, attPayload, attSignature []byte) (tx *ethTypes.Transaction, err error) | ||
} | ||
|
||
// submitStateReport submits a state report to the given chain, provided a snapshot or attestation. | ||
func (g Guard) submitStateReport(ctx context.Context, chainID uint32, state types.State, stateIndex int, data types.StateValidationData) (err error) { | ||
var contract stateReportContract | ||
if chainID == g.summitDomainID { | ||
contract = g.domains[chainID].Inbox() | ||
} else { | ||
contract = g.domains[chainID].LightInbox() | ||
} | ||
|
||
var submitFunc func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) | ||
srSignature, _, _, err := state.SignState(ctx, g.bondedSigner) | ||
if err != nil { | ||
return fmt.Errorf("could not sign state: %w", err) | ||
} | ||
if types.HasAttestation(data) { | ||
submitFunc = func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { | ||
tx, err = contract.SubmitStateReportWithAttestation( | ||
transactor, | ||
int64(stateIndex), | ||
srSignature, | ||
data.SnapshotPayload(), | ||
data.AttestationPayload(), | ||
data.AttestationSignature(), | ||
) | ||
return | ||
} | ||
} else { | ||
submitFunc = func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { | ||
tx, err = contract.SubmitStateReportWithSnapshot( | ||
transactor, | ||
int64(stateIndex), | ||
srSignature, | ||
data.SnapshotPayload(), | ||
data.SnapshotSignature(), | ||
) | ||
return | ||
} | ||
} | ||
|
||
// Ensure the agent that provided the snapshot is active on the agent's respective domain. | ||
ok, err := g.ensureAgentActive(ctx, data.Agent(), chainID) | ||
if err != nil { | ||
return fmt.Errorf("could not ensure agent is active: %w", err) | ||
} | ||
if !ok { | ||
logger.Infof("Agent %s is not active on chain %d; not verifying snapshot state", data.Agent().Hex(), chainID) | ||
return nil | ||
} | ||
|
||
_, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), submitFunc) | ||
if err != nil { | ||
return fmt.Errorf("could not submit state report to chain %d: %w", chainID, err) | ||
} | ||
dwasse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return nil | ||
} | ||
|
||
// getDisputeStatus fetches the dispute status of an agent from Summit. | ||
func (g Guard) getDisputeStatus(ctx context.Context, agent common.Address) (status types.DisputeStatus, err error) { | ||
contractCall := func(ctx context.Context) error { | ||
status, err = g.domains[g.summitDomainID].BondingManager().GetDisputeStatus(ctx, agent) | ||
if err != nil { | ||
return fmt.Errorf("could not get dispute status: %w", err) | ||
} | ||
return nil | ||
} | ||
err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not get dispute status: %w", err) | ||
} | ||
return status, nil | ||
} | ||
dwasse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// ensureAgentActive checks if the given agent is in a slashable status (Active or Unstaking), | ||
// and relays the agent status from Summit to the given chain if necessary. | ||
func (g Guard) ensureAgentActive(ctx context.Context, agent common.Address, chainID uint32) (ok bool, err error) { | ||
agentStatus, err := g.getAgentStatus(ctx, chainID, agent) | ||
if err != nil { | ||
return false, fmt.Errorf("could not get agent status: %w", err) | ||
} | ||
|
||
//nolint:exhaustive | ||
switch agentStatus.Flag() { | ||
case types.AgentFlagUnknown: | ||
if chainID == g.summitDomainID { | ||
return false, fmt.Errorf("cannot submit state report for Unknown agent on summit") | ||
} | ||
// Fetch the agent status from Summit. | ||
agentStatusSummit, err := g.getAgentStatus(ctx, g.summitDomainID, agent) | ||
if err != nil { | ||
return false, fmt.Errorf("could not get agent status: %w", err) | ||
} | ||
if agentStatusSummit.Flag() != types.AgentFlagActive && agentStatusSummit.Flag() != types.AgentFlagUnstaking { | ||
return false, fmt.Errorf("agent is not active or unstaking on summit: %s [status=%s]", agent.Hex(), agentStatusSummit.Flag().String()) | ||
} | ||
// Update the agent status using the last known root on remote chain. | ||
err = g.relayAgentStatus(ctx, agent, chainID, agentStatusSummit.Flag()) | ||
if err != nil { | ||
return false, err | ||
} | ||
return true, nil | ||
case types.AgentFlagActive, types.AgentFlagUnstaking: | ||
return true, nil | ||
default: | ||
return false, nil | ||
} | ||
} | ||
dwasse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// relayAgentStatus relays an Active agent status from Summit to a remote | ||
// chain where the agent is unknown. | ||
func (g Guard) relayAgentStatus(ctx context.Context, agent common.Address, chainID uint32, flag types.AgentFlagType) error { | ||
err := g.guardDB.StoreRelayableAgentStatus( | ||
ctx, | ||
agent, | ||
types.AgentFlagUnknown, | ||
flag, | ||
chainID, | ||
) | ||
if err != nil { | ||
return fmt.Errorf("could not store relayable agent status: %w", err) | ||
} | ||
err = g.updateAgentStatus(ctx, chainID) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
ooh i like this file haha