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

Fraud reporting improvements #1375

Merged
merged 30 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
706e3b0
Feat: add getStateReportChains()
dwasse Sep 14, 2023
6750c23
Feat: prepareStateReport -> ensureAgentActive, use it before verifyin…
dwasse Sep 14, 2023
9b97682
Feat: ensureAgentActive called before verfication, but no longer need…
dwasse Sep 14, 2023
806df4e
Feat: add calls.go with chain-agnostic funcs
dwasse Sep 14, 2023
7cc9e5f
Feat: use submitStateReport() helper
dwasse Sep 15, 2023
13c6cff
Feat: add verifyState() helper
dwasse Sep 15, 2023
16d6878
Cleanup: rename handleX() -> handleXAccepted()
dwasse Sep 15, 2023
2484435
Feat: add handleSnapshot() helper
dwasse Sep 15, 2023
f7df42e
WIP: coalesce FraudSnapshot and FraudAttestation into StateValidation…
dwasse Sep 15, 2023
7a41261
Feat: encorporate StateValidationData usage in contract calls
dwasse Sep 15, 2023
372c0c9
Cleanup: FraudSnapshot -> SnapshotWithMetadata, FraudAttestation -> A…
dwasse Sep 15, 2023
0912968
Cleanup: consistent data field naming, add comments
dwasse Sep 15, 2023
e0493fc
Cleanup: remove logs
dwasse Sep 15, 2023
3ac93cf
Cleanup: lint
dwasse Sep 15, 2023
2068db0
Feat: use getAgentStatus() helper
dwasse Sep 15, 2023
c3dfd2d
Cleanup: unused param
dwasse Sep 15, 2023
d85dabb
Cleanup: move getDisputeStatus() to calls.go, add comments
dwasse Sep 15, 2023
5620e75
Cleanup: move ensureAgentActive() helper to calls.go
dwasse Sep 15, 2023
7fd9a71
Cleanup: move relayActiveAgentStatus() helper to calls.go
dwasse Sep 15, 2023
bb290b6
Cleanup: remove log
dwasse Sep 15, 2023
3759690
Merge branch 'master' into feat/fraud-report-enhancement
dwasse Sep 15, 2023
7d819c0
Fix: use ptr in data assign
dwasse Sep 15, 2023
c8c2b00
Cleanup: define SnapshotWithMetadata first
dwasse Sep 15, 2023
76ff0a4
Cleanup: better err msg
dwasse Sep 15, 2023
83f2ec3
Feat: fetch agent status from summit instead of assuming Active in re…
dwasse Sep 15, 2023
3eccc29
Cleanup: chainID shadowing
dwasse Sep 15, 2023
82db3d8
Cleanup: err msg verbosity
dwasse Sep 15, 2023
77b8cee
Cleanup: add guard README
dwasse Sep 15, 2023
61b4492
Cleanup: add testing docs to agents README
dwasse Sep 15, 2023
1997382
Cleanup: remove 'Testing Suite' section from guard README
dwasse Sep 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agents/agents/executor/executor_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (e Executor) logToSnapshot(log ethTypes.Log, chainID uint32) (types.Snapsho
return nil, fmt.Errorf("could not parse snapshot: %w", err)
}

if snapshotMetadata.Snapshot == nil || snapshotMetadata.AgentDomain == 0 {
if snapshotMetadata.Snapshot == nil || snapshotMetadata.AgentDomain() == 0 {
//nolint:nilnil
return nil, nil
}
Expand Down
218 changes: 218 additions & 0 deletions agents/agents/guard/calls.go
Copy link
Contributor

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

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
}
Loading