agents/agents/guard/fraud_test.go
package guard_test
import (
"context"
"fmt"
"math/big"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/Flaque/filet"
"github.com/ethereum/go-ethereum/common"
. "github.com/stretchr/testify/assert"
"github.com/synapsecns/sanguine/agents/agents/executor"
"github.com/synapsecns/sanguine/agents/agents/executor/db"
"github.com/synapsecns/sanguine/agents/agents/guard"
"github.com/synapsecns/sanguine/agents/config"
execConfig "github.com/synapsecns/sanguine/agents/config/executor"
"github.com/synapsecns/sanguine/agents/domains"
"github.com/synapsecns/sanguine/agents/testutil/agentstestcontract"
"github.com/synapsecns/sanguine/agents/types"
"github.com/synapsecns/sanguine/ethergo/backends"
"github.com/synapsecns/sanguine/ethergo/backends/anvil"
signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config"
"github.com/synapsecns/sanguine/ethergo/signer/signer"
submitterConfig "github.com/synapsecns/sanguine/ethergo/submitter/config"
omniClient "github.com/synapsecns/sanguine/services/omnirpc/client"
"github.com/synapsecns/sanguine/services/scribe/backend"
"github.com/synapsecns/sanguine/services/scribe/client"
scribeConfig "github.com/synapsecns/sanguine/services/scribe/config"
"github.com/synapsecns/sanguine/services/scribe/service"
)
func (g *GuardSuite) getTestGuard(scribeConfig scribeConfig.Config) (testGuard *guard.Guard, sclient client.ScribeClient, err error) {
testConfig := config.AgentConfig{
Domains: map[string]config.DomainConfig{
"origin_client": g.OriginDomainClient.Config(),
"destination_client": g.DestinationDomainClient.Config(),
"summit_client": g.SummitDomainClient.Config(),
},
DomainID: uint32(0),
SummitDomainID: g.SummitDomainClient.Config().DomainID,
BondedSigner: signerConfig.SignerConfig{
Type: signerConfig.FileType.String(),
File: filet.TmpFile(g.T(), "", g.GuardBondedWallet.PrivateKeyHex()).Name(),
},
UnbondedSigner: signerConfig.SignerConfig{
Type: signerConfig.FileType.String(),
File: filet.TmpFile(g.T(), "", g.GuardUnbondedWallet.PrivateKeyHex()).Name(),
},
RefreshIntervalSeconds: 5,
MaxRetrySeconds: 60,
}
// Scribe setup.
omniRPCClient := omniClient.NewOmnirpcClient(g.TestOmniRPC, g.GuardMetrics, omniClient.WithCaptureReqRes())
originClient, err := backend.DialBackend(g.GetTestContext(), g.TestBackendOrigin.RPCAddress(), g.ScribeMetrics)
Nil(g.T(), err)
destinationClient, err := backend.DialBackend(g.GetTestContext(), g.TestBackendDestination.RPCAddress(), g.ScribeMetrics)
Nil(g.T(), err)
summitClient, err := backend.DialBackend(g.GetTestContext(), g.TestBackendSummit.RPCAddress(), g.ScribeMetrics)
Nil(g.T(), err)
clients := map[uint32][]backend.ScribeBackend{
uint32(g.TestBackendOrigin.GetChainID()): {originClient, originClient},
uint32(g.TestBackendDestination.GetChainID()): {destinationClient, destinationClient},
uint32(g.TestBackendSummit.GetChainID()): {summitClient, summitClient},
}
scribe, err := service.NewScribe(g.ScribeTestDB, clients, scribeConfig, g.ScribeMetrics)
Nil(g.T(), err)
scribeClient := client.NewEmbeddedScribe("sqlite", g.DBPath, g.ScribeMetrics)
//nolint:errcheck
go scribeClient.Start(g.GetTestContext())
//nolint:errcheck
go scribe.Start(g.GetTestContext())
//nolint:wrapcheck
testGuard, err = guard.NewGuard(g.GetTestContext(), testConfig, omniRPCClient, scribeClient.ScribeClient, g.GuardTestDB, g.GuardMetrics)
sclient = scribeClient.ScribeClient
if err != nil {
return nil, sclient, fmt.Errorf("could not create guard: %w", err)
}
if testGuard == nil {
return nil, sclient, fmt.Errorf("guard is nil")
}
return testGuard, sclient, nil
}
func (g *GuardSuite) bumpBackends() {
txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.SummitMetadata.OwnerPtr())
txContextOrigin := g.TestBackendOrigin.GetTxContext(g.GetTestContext(), g.OriginContractMetadata.OwnerPtr())
txContextDestination := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr())
g.bumpBackend(g.TestBackendSummit, g.TestContractOnSummit, txContextSummit.TransactOpts)
g.bumpBackend(g.TestBackendOrigin, g.TestContractOnOrigin, txContextOrigin.TransactOpts)
g.bumpBackend(g.TestBackendDestination, g.TestContractOnDestination, txContextDestination.TransactOpts)
}
// Helper to get the test backend to emit expected events.
func (g *GuardSuite) bumpBackend(backend backends.SimulatedTestBackend, contract *agentstestcontract.AgentsTestContractRef, txOpts *bind.TransactOpts) {
bumpTx, err := contract.EmitAgentsEventA(txOpts, big.NewInt(gofakeit.Int64()), big.NewInt(gofakeit.Int64()), big.NewInt(gofakeit.Int64()))
Nil(g.T(), err)
backend.WaitForConfirmation(g.GetTestContext(), bumpTx)
}
func (g *GuardSuite) updateAgentStatus(lightManager domains.LightManagerContract, bondedSigner, unbondedSigner signer.Signer, chainID uint32) {
agentStatus, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), bondedSigner.Address())
Nil(g.T(), err)
agentProof, err := g.SummitDomainClient.BondingManager().GetProof(g.GetTestContext(), bondedSigner.Address())
Nil(g.T(), err)
transactor, err := unbondedSigner.GetTransactor(g.GetTestContext(), big.NewInt(int64(chainID)))
Nil(g.T(), err)
_, err = lightManager.UpdateAgentStatus(
transactor,
bondedSigner.Address(),
agentStatus,
agentProof,
)
Nil(g.T(), err)
g.bumpBackends()
}
// TODO: Add a test for exiting the report logic early when the snapshot submitter is a guard.
func (g *GuardSuite) TestFraudulentStateInSnapshot() {
testDone := false
defer func() {
testDone = true
}()
originConfig := scribeConfig.ContractConfig{
Address: g.OriginContract.Address().String(),
StartBlock: 0,
}
originChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendOrigin.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{originConfig},
}
destinationConfig := scribeConfig.ContractConfig{
Address: g.LightInboxOnDestination.Address().String(),
StartBlock: 0,
}
destinationChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendDestination.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{destinationConfig},
}
summitConfig := scribeConfig.ContractConfig{
Address: g.InboxOnSummit.Address().String(),
StartBlock: 0,
}
summitChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendSummit.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{summitConfig},
}
scribeConfig := scribeConfig.Config{
Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig},
}
// Start a new Guard.
guard, _, err := g.getTestGuard(scribeConfig)
Nil(g.T(), err)
go func() {
guardErr := guard.Start(g.GetTestContext())
if !testDone {
Nil(g.T(), guardErr)
}
}()
// Update the agent status on Origin.
g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID()))
// Verify that the agent is marked as Active.
status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Equal(g.T(), status.Flag(), types.AgentFlagActive)
Nil(g.T(), err)
// Store agent trees and roots so that the agent status can be updated by the guard.
agentRoot, err := g.SummitDomainClient.BondingManager().GetAgentRoot(g.GetTestContext())
Nil(g.T(), err)
blockNumber, err := g.SummitDomainClient.BlockNumber(g.GetTestContext())
Nil(g.T(), err)
notaryProof, err := g.SummitDomainClient.BondingManager().GetProof(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
err = g.GuardTestDB.StoreAgentTree(
g.GetTestContext(),
agentRoot,
g.NotaryBondedSigner.Address(),
uint64(blockNumber),
notaryProof,
)
Nil(g.T(), err)
err = g.GuardTestDB.StoreAgentRoot(g.GetTestContext(), agentRoot, uint64(blockNumber))
Nil(g.T(), err)
// Before submitting the attestation, ensure that there are no disputes opened.
err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0))
NotNil(g.T(), err)
// Create a fraudulent snapshot.
getState := func(nonce uint32) types.State {
gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16())
state := types.NewState(
common.BigToHash(big.NewInt(gofakeit.Int64())),
g.OriginDomainClient.Config().DomainID,
nonce,
big.NewInt(int64(gofakeit.Int32())),
big.NewInt(int64(gofakeit.Int32())),
gasData,
)
return state
}
fraudulentSnapshot := types.NewSnapshot([]types.State{getState(1), getState(2)})
// Submit the snapshot with a guard then notary.
guardSnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner)
Nil(g.T(), err)
txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr())
tx, err := g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
notarySnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner)
Nil(g.T(), err)
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
// Verify that the guard eventually marks the accused agent as Fraudulent on Origin.
g.Eventually(func() bool {
status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
if status.Flag() == types.AgentFlagFraudulent {
return true
}
g.bumpBackends()
return false
})
// Verify that a report has been submitted by the Guard by checking that a Dispute is now open.
g.Eventually(func() bool {
err = g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0))
return err == nil
})
// Verify that a state report was submitted on summit.
fraudulentState := fraudulentSnapshot.States()[0]
g.verifyStateReport(g.InboxOnSummit, 1, fraudulentState)
// Verify that a state report was submitted on destination.
g.verifyStateReport(g.LightInboxOnDestination, 1, fraudulentState)
}
func (g *GuardSuite) TestFraudulentAttestationOnDestination() {
testDone := false
defer func() {
testDone = true
}()
originConfig := scribeConfig.ContractConfig{
Address: g.OriginContract.Address().String(),
StartBlock: 0,
}
originChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendOrigin.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{originConfig},
}
destinationConfig := scribeConfig.ContractConfig{
Address: g.LightInboxOnDestination.Address().String(),
StartBlock: 0,
}
destinationChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendDestination.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{destinationConfig},
}
summitConfig := scribeConfig.ContractConfig{
Address: g.InboxOnSummit.Address().String(),
StartBlock: 0,
}
bondingManagerConfig := scribeConfig.ContractConfig{
Address: g.BondingManagerOnSummit.Address().String(),
StartBlock: 0,
}
summitChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendSummit.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{summitConfig, bondingManagerConfig},
}
scribeConfig := scribeConfig.Config{
Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig},
}
// Start a new Guard.
guard, _, err := g.getTestGuard(scribeConfig)
Nil(g.T(), err)
go func() {
guardErr := guard.Start(g.GetTestContext())
if !testDone {
Nil(g.T(), guardErr)
}
}()
_, gasDataContract := g.TestDeployManager.GetGasDataHarness(g.GetTestContext(), g.TestBackendDestination)
_, attestationContract := g.TestDeployManager.GetAttestationHarness(g.GetTestContext(), g.TestBackendDestination)
// Verify that the agent is marked as Active.
txContextDest := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr())
status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.GuardBondedSigner.Address())
Equal(g.T(), status.Flag(), types.AgentFlagActive)
Nil(g.T(), err)
agentRoot := common.BigToHash(big.NewInt(gofakeit.Int64()))
gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16())
chainGas := types.NewChainGas(gasData, uint32(g.TestBackendOrigin.GetChainID()))
chainGasBytes, err := types.EncodeChainGas(chainGas)
Nil(g.T(), err)
// Build and sign a fraudulent attestation.
// TODO: Change from using a harness to using the Go code.
snapGas := []*big.Int{new(big.Int).SetBytes(chainGasBytes)}
snapGasHash, err := gasDataContract.SnapGasHash(&bind.CallOpts{Context: g.GetTestContext()}, snapGas)
Nil(g.T(), err)
dataHash, err := attestationContract.DataHash(&bind.CallOpts{Context: g.GetTestContext()}, agentRoot, snapGasHash)
Nil(g.T(), err)
attestationData := types.NewAttestation(
common.BigToHash(big.NewInt(int64(gofakeit.Int32()))),
dataHash,
1,
big.NewInt(int64(gofakeit.Int32())),
big.NewInt(int64(gofakeit.Int32())),
)
attSignature, attEncoded, _, err := attestationData.SignAttestation(g.GetTestContext(), g.NotaryBondedSigner, true)
Nil(g.T(), err)
// Before submitting the attestation, ensure that there are no disputes opened.
err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0))
NotNil(g.T(), err)
// Update the agent status of the Guard and Notary.
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.GuardBondedSigner, g.GuardUnbondedSigner, uint32(g.TestBackendDestination.GetChainID()))
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendDestination.GetChainID()))
// Submit the attestation.
tx, err := g.DestinationDomainClient.LightInbox().SubmitAttestation(
txContextDest.TransactOpts,
attEncoded,
attSignature,
agentRoot,
snapGas,
)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx)
// Verify that the guard eventually marks the accused agent as Slashed.
g.Eventually(func() bool {
status, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
if status.Flag() == types.AgentFlagSlashed {
return true
}
// Make sure that scribe keeps producing new blocks.
g.bumpBackends()
return false
})
// Verify that a report has been submitted by the Guard by checking that a Dispute is now open.
g.Eventually(func() bool {
err := g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0))
return err == nil
})
}
func (g *GuardSuite) TestReportFraudulentStateInAttestation() {
testDone := false
defer func() {
testDone = true
}()
// This scribe config omits the Summit and Origin chains, since we do not want to pick up the fraud coming from the
// fraudulent snapshots, only from the Attestation submitted on the Destination that is associated with a fraudulent
// snapshot.
destinationConfig := scribeConfig.ContractConfig{
Address: g.LightInboxOnDestination.Address().String(),
StartBlock: 0,
}
destinationChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendDestination.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{destinationConfig},
}
inboxConfig := scribeConfig.ContractConfig{
Address: g.InboxOnSummit.Address().String(),
StartBlock: 0,
}
bondingManagerConfig := scribeConfig.ContractConfig{
Address: g.BondingManagerOnSummit.Address().String(),
StartBlock: 0,
}
summitChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendSummit.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{inboxConfig, bondingManagerConfig},
}
scribeConfig := scribeConfig.Config{
Chains: []scribeConfig.ChainConfig{destinationChainConfig, summitChainConfig},
}
// Start a new Guard.
guard, _, err := g.getTestGuard(scribeConfig)
Nil(g.T(), err)
go func() {
guardErr := guard.Start(g.GetTestContext())
if !testDone {
Nil(g.T(), guardErr)
}
}()
// Verify that the agent is marked as Active.
txContextDest := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr())
status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.GuardBondedSigner.Address())
Equal(g.T(), status.Flag(), types.AgentFlagActive)
Nil(g.T(), err)
// Create a fraudulent snapshot.
gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16())
fraudulentState := types.NewState(
common.BigToHash(big.NewInt(gofakeit.Int64())),
g.OriginDomainClient.Config().DomainID,
1,
big.NewInt(int64(gofakeit.Int32())),
big.NewInt(int64(gofakeit.Int32())),
gasData,
)
fraudulentSnapshot := types.NewSnapshot([]types.State{fraudulentState})
// Before submitting the attestation, ensure that there are no disputes opened.
err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0))
NotNil(g.T(), err)
// Update the agent status of the Guard and Notary.
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.GuardBondedSigner, g.GuardUnbondedSigner, uint32(g.TestBackendDestination.GetChainID()))
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendDestination.GetChainID()))
g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID()))
// Submit the snapshot with a guard.
guardSnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner)
Nil(g.T(), err)
transactOptsSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr())
tx, err := g.SummitDomainClient.Inbox().SubmitSnapshot(transactOptsSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
// Submit the snapshot with a notary.
notarySnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner)
Nil(g.T(), err)
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(transactOptsSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
// Submit the attestation.
notaryAttestation, err := g.SummitDomainClient.Summit().GetAttestation(g.GetTestContext(), 1)
Nil(g.T(), err)
attSignature, attEncoded, _, err := notaryAttestation.Attestation().SignAttestation(g.GetTestContext(), g.NotaryBondedSigner, true)
Nil(g.T(), err)
tx, err = g.DestinationDomainClient.LightInbox().SubmitAttestation(
txContextDest.TransactOpts,
attEncoded,
attSignature,
notaryAttestation.AgentRoot(),
notaryAttestation.SnapGas(),
)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx)
// Verify that the guard eventually marks the accused agent as Fraudulent.
g.Eventually(func() bool {
status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
if status.Flag() == types.AgentFlagFraudulent {
return true
}
g.bumpBackends()
return false
})
// Verify that a dispute is now open on summit.
g.Eventually(func() bool {
err := g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0))
return err == nil
})
// Verify that a state report was submitted on summit.
g.verifyStateReport(g.InboxOnSummit, 1, fraudulentState)
// Verify that a state report was submitted on destination.
g.verifyStateReport(g.LightInboxOnDestination, 1, fraudulentState)
}
type statementInboxContract interface {
GetReportsAmount(opts *bind.CallOpts) (*big.Int, error)
GetGuardReport(opts *bind.CallOpts, index *big.Int) (struct {
StatementPayload []byte
ReportSignature []byte
}, error)
}
// Verify that a state report was submitted on the given contract.
//
//nolint:unparam
func (g *GuardSuite) verifyStateReport(contract statementInboxContract, expectedNumReports int64, expectedState types.State) {
g.Eventually(func() bool {
numReports, err := contract.GetReportsAmount(&bind.CallOpts{Context: g.GetTestContext()})
Nil(g.T(), err)
if numReports.Int64() < expectedNumReports {
return false
}
if numReports.Int64() != expectedNumReports {
g.T().Fatalf("too many reports; expected %d, got %v", expectedNumReports, numReports.Int64())
}
stateReportIdx := big.NewInt(numReports.Int64() - 1)
stateReport, err := contract.GetGuardReport(&bind.CallOpts{Context: g.GetTestContext()}, stateReportIdx)
Nil(g.T(), err)
expected, err := expectedState.Encode()
Nil(g.T(), err)
return Equal(g.T(), stateReport.StatementPayload, expected)
})
}
func (g *GuardSuite) TestInvalidReceipt() {
testDone := false
defer func() {
testDone = true
}()
originConfig := scribeConfig.ContractConfig{
Address: g.OriginContract.Address().String(),
StartBlock: 0,
}
originChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendOrigin.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{originConfig},
}
destinationConfig := scribeConfig.ContractConfig{
Address: g.LightInboxOnDestination.Address().String(),
StartBlock: 0,
}
destinationChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendDestination.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{destinationConfig},
}
summitConfig := scribeConfig.ContractConfig{
Address: g.InboxOnSummit.Address().String(),
StartBlock: 0,
}
bondingManagerConfig := scribeConfig.ContractConfig{
Address: g.BondingManagerOnSummit.Address().String(),
StartBlock: 0,
}
summitChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendSummit.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{summitConfig, bondingManagerConfig},
}
scribeConfig := scribeConfig.Config{
Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig},
}
// Start a new Guard.
guard, _, err := g.getTestGuard(scribeConfig)
Nil(g.T(), err)
go func() {
guardErr := guard.Start(g.GetTestContext())
if !testDone {
Nil(g.T(), guardErr)
}
}()
// Send a message on Origin for it to be included in a valid state.
summitTip := big.NewInt(int64(gofakeit.Uint32()))
attestationTip := big.NewInt(int64(gofakeit.Uint32()))
executorTip := big.NewInt(int64(gofakeit.Uint32()))
deliveryTip := big.NewInt(int64(gofakeit.Uint32()))
tips := types.NewTips(summitTip, attestationTip, executorTip, deliveryTip)
optimisticSeconds := uint32(1)
recipientDestination := common.BytesToHash(g.TestClientMetadataOnDestination.Address().Bytes())
nonce := uint32(1)
body := []byte{byte(gofakeit.Uint32())}
txContextOrigin := g.TestBackendOrigin.GetTxContext(g.GetTestContext(), g.OriginContractMetadata.OwnerPtr())
txContextOrigin.Value = types.TotalTips(tips)
paddedRequest := big.NewInt(0)
msgSender := common.BytesToHash(txContextOrigin.TransactOpts.From.Bytes())
header := types.NewHeader(types.MessageFlagBase, uint32(g.TestBackendOrigin.GetChainID()), nonce, uint32(g.TestBackendDestination.GetChainID()), optimisticSeconds)
msgRequest := types.NewRequest(uint32(0), uint64(0), big.NewInt(0))
baseMessage := types.NewBaseMessage(msgSender, recipientDestination, tips, msgRequest, body)
message, err := types.NewMessageFromBaseMessage(header, baseMessage)
Nil(g.T(), err)
tx, err := g.OriginContract.SendBaseMessage(
txContextOrigin.TransactOpts,
uint32(g.TestBackendDestination.GetChainID()),
recipientDestination,
optimisticSeconds,
paddedRequest,
body,
)
Nil(g.T(), err)
g.TestBackendOrigin.WaitForConfirmation(g.GetTestContext(), tx)
// Submit the snapshot with a guard.
latestOriginState, err := g.OriginDomainClient.Origin().SuggestLatestState(g.GetTestContext())
Nil(g.T(), err)
snapshot := types.NewSnapshot([]types.State{latestOriginState})
guardSnapshotSignature, encodedSnapshot, _, err := snapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner)
Nil(g.T(), err)
txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr())
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
// Submit the snapshot with a notary.
notarySnapshotSignature, encodedSnapshot, _, err := snapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner)
Nil(g.T(), err)
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
// Build and sign a receipt.
snapshotRoot, _, err := snapshot.SnapshotRootAndProofs()
Nil(g.T(), err)
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendDestination.GetChainID()))
messageHash, err := message.ToLeaf()
Nil(g.T(), err)
receipt := types.NewReceipt(
g.OriginDomainClient.Config().DomainID,
g.DestinationDomainClient.Config().DomainID,
messageHash,
snapshotRoot,
0,
g.NotaryBondedWallet.Address(),
common.BigToAddress(big.NewInt(gofakeit.Int64())),
common.BigToAddress(big.NewInt(gofakeit.Int64())),
)
rcptSignature, rcptPayload, _, err := receipt.SignReceipt(g.GetTestContext(), g.NotaryBondedSigner, true)
Nil(g.T(), err)
// Submit the receipt.
bodyHash, err := baseMessage.BodyLeaf()
Nil(g.T(), err)
var bodyHashB32 [32]byte
copy(bodyHashB32[:], bodyHash)
headerHash, err := header.Leaf()
Nil(g.T(), err)
paddedTips, err := types.EncodeTipsBigInt(tips)
Nil(g.T(), err)
txContextSummit = g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.SummitMetadata.OwnerPtr())
tx, err = g.SummitDomainClient.Inbox().SubmitReceipt(
txContextSummit.TransactOpts,
rcptPayload,
rcptSignature,
paddedTips,
headerHash,
bodyHashB32,
)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
// Verify that the guard eventually marks the accused agent as Fraudulent.
g.Eventually(func() bool {
status, err := g.DestinationDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
if status.Flag() == types.AgentFlagFraudulent {
return true
}
g.bumpBackends()
return false
})
// TODO: Uncomment once updating agent status is implemented.
// g.Eventually(func() bool {
// status, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
// Nil(g.T(), err)
// if status.Flag() == types.AgentFlagSlashed {
// return true
// }
// g.bumpBackends()
// return false
// })
// Verify that a report has been submitted by the Guard by checking that a Dispute is now open.
g.Eventually(func() bool {
err := g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0))
return err == nil
})
}
//nolint:maintidx,cyclop
func (g *GuardSuite) TestUpdateAgentStatusOnRemote() {
// This test requires a call to anvil's evm.IncreaseTime() cheat code, so we should
// set up the backends with anvil.
testDone := false
defer func() {
testDone = true
}()
// This scribe config omits the Summit and Origin chains, since we do not want to pick up the fraud coming from the
// fraudulent snapshots, only from the Attestation submitted on the Destination that is associated with a fraudulent
// snapshot.
originConfig := scribeConfig.ContractConfig{
Address: g.OriginContract.Address().String(),
StartBlock: 0,
}
lightManagerConfig := scribeConfig.ContractConfig{
Address: g.LightManagerOnOrigin.Address().String(),
StartBlock: 0,
}
originChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendOrigin.GetChainID()),
GetLogsBatchAmount: 1,
StoreConcurrency: 1,
GetLogsRange: 1,
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{originConfig, lightManagerConfig},
}
destinationConfig := scribeConfig.ContractConfig{
Address: g.DestinationContract.Address().String(),
StartBlock: 0,
}
lightInboxDestinationConfig := scribeConfig.ContractConfig{
Address: g.LightInboxOnDestination.Address().String(),
StartBlock: 0,
}
lightManagerDestinationConfig := scribeConfig.ContractConfig{
Address: g.LightManagerOnDestination.Address().String(),
StartBlock: 0,
}
destinationChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendDestination.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{destinationConfig, lightInboxDestinationConfig, lightManagerDestinationConfig},
}
summitConfig := scribeConfig.ContractConfig{
Address: g.SummitContract.Address().String(),
StartBlock: 0,
}
inboxConfig := scribeConfig.ContractConfig{
Address: g.InboxOnSummit.Address().String(),
StartBlock: 0,
}
bondingManagerConfig := scribeConfig.ContractConfig{
Address: g.BondingManagerOnSummit.Address().String(),
StartBlock: 0,
}
summitChainConfig := scribeConfig.ChainConfig{
ChainID: uint32(g.TestBackendSummit.GetChainID()),
Confirmations: 1,
Contracts: []scribeConfig.ContractConfig{summitConfig, inboxConfig, bondingManagerConfig},
}
scribeConfig := scribeConfig.Config{
Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig},
}
// Start a new Guard.
guard, scribeClient, err := g.getTestGuard(scribeConfig)
Nil(g.T(), err)
go func() {
guardErr := guard.Start(g.GetTestContext())
if !testDone {
Nil(g.T(), guardErr)
}
}()
// Scribe setup.
omniRPCClient := omniClient.NewOmnirpcClient(g.TestOmniRPC, g.GuardMetrics, omniClient.WithCaptureReqRes())
chainID := uint32(g.TestBackendOrigin.GetChainID())
destination := uint32(g.TestBackendDestination.GetChainID())
summit := uint32(g.TestBackendSummit.GetChainID())
excCfg := execConfig.Config{
SummitChainID: summit,
SummitAddress: g.SummitContract.Address().String(),
InboxAddress: g.InboxOnSummit.Address().String(),
Chains: []execConfig.ChainConfig{
{
ChainID: chainID,
OriginAddress: g.OriginContract.Address().String(),
},
{
ChainID: destination,
LightInboxAddress: g.LightInboxOnDestination.Address().String(),
DestinationAddress: g.DestinationContract.Address().String(),
},
{
ChainID: summit,
DestinationAddress: g.DestinationContractOnSummit.Address().String(),
},
},
BaseOmnirpcURL: g.TestBackendOrigin.RPCAddress(),
UnbondedSigner: signerConfig.SignerConfig{
Type: signerConfig.FileType.String(),
File: filet.TmpFile(g.T(), "", g.ExecutorUnbondedWallet.PrivateKeyHex()).Name(),
},
SubmitterConfig: submitterConfig.Config{
ChainConfig: submitterConfig.ChainConfig{
GasEstimate: uint64(5000000),
},
},
}
// This function will allow us to override the current time perceived by Executor.
var currentTime *time.Time
nowFunc := func() time.Time {
if currentTime == nil {
return time.Now()
}
return *currentTime
}
getChainTimeFunc := func(ctx context.Context, backend executor.Backend) (uint64, error) {
return uint64(nowFunc().Unix()), nil
}
// Start a new Executor.
exec, err := executor.NewExecutor(g.GetTestContext(), excCfg, g.ExecutorTestDB, scribeClient, omniRPCClient, g.ExecutorMetrics)
Nil(g.T(), err)
exec.NowFunc = nowFunc
exec.GetChainTimeFunc = getChainTimeFunc
go func() {
execErr := exec.Run(g.GetTestContext())
if !testDone {
Nil(g.T(), execErr)
}
}()
// Verify that the agent is marked as Active.
txContextDest := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr())
status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.GuardBondedSigner.Address())
Equal(g.T(), status.Flag(), types.AgentFlagActive)
Nil(g.T(), err)
// Create a fraudulent snapshot.
gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16())
fraudulentState := types.NewState(
common.BigToHash(big.NewInt(gofakeit.Int64())),
g.OriginDomainClient.Config().DomainID,
1,
big.NewInt(int64(gofakeit.Int32())),
big.NewInt(int64(gofakeit.Int32())),
gasData,
)
fraudulentSnapshot := types.NewSnapshot([]types.State{fraudulentState})
// Before submitting the attestation, ensure that there are no disputes opened.
err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0))
NotNil(g.T(), err)
// Update the agent status of the Guard and Notaries.
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.GuardBondedSigner, g.GuardUnbondedSigner, destination)
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, destination)
g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID()))
g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryOnDestinationBondedSigner, g.NotaryOnDestinationUnbondedSigner, destination)
g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryOnDestinationBondedSigner, g.NotaryOnDestinationUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID()))
// Submit the snapshot with a guard.
guardSnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner)
Nil(g.T(), err)
txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr())
tx, err := g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
g.bumpBackends()
// Submit the snapshot with a notary.
notarySnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner)
Nil(g.T(), err)
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
g.bumpBackends()
// Submit the attestation.
notaryAttestation, err := g.SummitDomainClient.Summit().GetAttestation(g.GetTestContext(), 1)
Nil(g.T(), err)
attSignature, attEncoded, _, err := notaryAttestation.Attestation().SignAttestation(g.GetTestContext(), g.NotaryBondedSigner, true)
Nil(g.T(), err)
tx, err = g.DestinationDomainClient.LightInbox().SubmitAttestation(
txContextDest.TransactOpts,
attEncoded,
attSignature,
notaryAttestation.AgentRoot(),
notaryAttestation.SnapGas(),
)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx)
g.bumpBackends()
// Verify that the guard eventually marks the accused agent as Fraudulent.
g.Eventually(func() bool {
status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
if status.Flag() == types.AgentFlagFraudulent {
return true
}
g.bumpBackends()
return false
})
// Verify that a report has been submitted by the Guard by checking that a Dispute is now open.
g.Eventually(func() bool {
err := g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0))
return err == nil
})
// Get the origin state so we can submit it on the Summit.
originStateRaw, err := g.OriginContract.SuggestLatestState(&bind.CallOpts{Context: g.GetTestContext()})
g.Nil(err)
originState, err := types.DecodeState(originStateRaw)
g.Nil(err)
snapshot := types.NewSnapshot([]types.State{originState})
//nolint:wrapcheck
submitAndVerifySnapshot := func(originState types.State, agentSigner signer.Signer) error {
agentSnapshotSignature, encodedSnapshot, _, err := snapshot.SignSnapshot(g.GetTestContext(), agentSigner)
if err != nil {
return err
}
txContextSummit = g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.SummitMetadata.OwnerPtr())
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(
txContextSummit.TransactOpts,
encodedSnapshot,
agentSnapshotSignature,
)
if err != nil {
return err
}
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
g.bumpBackends()
latestStateRaw, err := g.SummitContract.GetLatestAgentState(&bind.CallOpts{Context: g.GetTestContext()}, uint32(g.TestBackendOrigin.GetChainID()), agentSigner.Address())
if err != nil {
return err
}
latestState, err := types.DecodeState(latestStateRaw)
if err != nil {
return err
}
latestStateHash, err := latestState.Hash()
if err != nil {
return err
}
originStateHash, err := originState.Hash()
if err != nil {
return err
}
latestStateHashHex := common.BytesToHash(latestStateHash[:])
originStateHashHex := common.BytesToHash(originStateHash[:])
if latestStateHash != originStateHash {
return fmt.Errorf("latest state hash mismatch; expected %v, got %v", originStateHashHex, latestStateHashHex)
}
return nil
}
// Submit snapshot with Guard.
g.Eventually(func() bool {
err := submitAndVerifySnapshot(originState, g.GuardBondedSigner)
if err != nil {
fmt.Println(err)
return false
}
return true
})
time.Sleep(5 * time.Second)
// Submit snapshot with Notary.
g.Eventually(func() bool {
err := submitAndVerifySnapshot(originState, g.NotaryOnOriginBondedSigner)
if err != nil {
fmt.Println(err)
return false
}
return true
})
// Wait for the executor to have attestations before increasing time.
summitChainID := uint32(g.TestBackendSummit.GetChainID())
attestationNonce := uint32(2)
g.Eventually(func() bool {
attest, err := g.ExecutorTestDB.GetAttestation(g.GetTestContext(), db.DBAttestation{
Destination: &summitChainID,
AttestationNonce: &attestationNonce,
})
Nil(g.T(), err)
return attest != nil
})
// Increase EVM time to allow agent status to be updated to Slashed on summit.
optimisticPeriodSeconds := int64(86400)
offset := optimisticPeriodSeconds / 2
increaseEvmTime := func(backend backends.SimulatedTestBackend, seconds int64) {
anvilClient, err := anvil.Dial(g.GetTestContext(), backend.RPCAddress())
Nil(g.T(), err)
err = anvilClient.IncreaseTime(g.GetTestContext(), seconds)
Nil(g.T(), err)
err = anvilClient.Mine(g.GetTestContext(), 1)
Nil(g.T(), err)
}
increaseEvmTime(g.TestBackendSummit, optimisticPeriodSeconds+offset)
g.bumpBackends()
// Increase executor time so that the manager message may be executed.
updatedTime := time.Now().Add(time.Duration(optimisticPeriodSeconds+offset) * time.Second)
currentTime = &updatedTime
// Verify that the accused agent is eventually Slashed on Summit.
g.Eventually(func() bool {
status, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
if status.Flag() == types.AgentFlagSlashed {
return true
}
time.Sleep(5 * time.Second)
g.bumpBackends()
return false
})
// Get the origin state so we can submit it on the Summit.
originStateRaw, err = g.OriginContract.SuggestLatestState(&bind.CallOpts{Context: g.GetTestContext()})
g.Nil(err)
originState, err = types.DecodeState(originStateRaw)
g.Nil(err)
snapshot = types.NewSnapshot([]types.State{originState})
// Submit snapshot with Guard.
guardSnapshotSignature, encodedSnapshot, _, err = snapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner)
g.Nil(err)
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(
txContextSummit.TransactOpts,
encodedSnapshot,
guardSnapshotSignature,
)
g.Nil(err)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
g.bumpBackends()
// Submit snapshot with Notary.
notarySnapshotSignature, encodedSnapshot, _, err = snapshot.SignSnapshot(g.GetTestContext(), g.NotaryOnOriginBondedSigner)
g.Nil(err)
tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(
txContextSummit.TransactOpts,
encodedSnapshot,
notarySnapshotSignature,
)
g.Nil(err)
g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx)
g.bumpBackends()
// Create a new attestation with the agent root corresponding to newly Slashed status.
latestAgentRoot, err := g.SummitDomainClient.BondingManager().GetAgentRoot(g.GetTestContext())
Nil(g.T(), err)
_, gasDataContract := g.TestDeployManager.GetGasDataHarness(g.GetTestContext(), g.TestBackendDestination)
_, attestationContract := g.TestDeployManager.GetAttestationHarness(g.GetTestContext(), g.TestBackendDestination)
chainGas := types.NewChainGas(originState.GasData(), uint32(g.TestBackendOrigin.GetChainID()))
chainGasBytes, err := types.EncodeChainGas(chainGas)
Nil(g.T(), err)
// TODO: Change from using a harness to using the Go code.
snapGas := []*big.Int{new(big.Int).SetBytes(chainGasBytes)}
snapGasHash, err := gasDataContract.SnapGasHash(&bind.CallOpts{Context: g.GetTestContext()}, snapGas)
Nil(g.T(), err)
dataHash, err := attestationContract.DataHash(&bind.CallOpts{Context: g.GetTestContext()}, latestAgentRoot, snapGasHash)
Nil(g.T(), err)
notaryAttestation, err = g.SummitDomainClient.Summit().GetAttestation(g.GetTestContext(), 2)
Nil(g.T(), err)
attestation := types.NewAttestation(
notaryAttestation.Attestation().SnapshotRoot(),
dataHash,
2,
notaryAttestation.Attestation().BlockNumber(),
notaryAttestation.Attestation().Timestamp(),
)
attEncoded, err = attestation.Encode()
Nil(g.T(), err)
notaryAttestation, err = types.NewNotaryAttestation(attEncoded, latestAgentRoot, snapGas)
Nil(g.T(), err)
// Submit the attestation.
attSignature, attEncoded, _, err = attestation.SignAttestation(g.GetTestContext(), g.NotaryOnDestinationBondedSigner, true)
Nil(g.T(), err)
tx, err = g.DestinationDomainClient.LightInbox().SubmitAttestation(
txContextDest.TransactOpts,
attEncoded,
attSignature,
latestAgentRoot,
notaryAttestation.SnapGas(),
)
Nil(g.T(), err)
NotNil(g.T(), tx)
g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx)
// Advance time on destination and call passAgentRoot() so that the latest agent root is accepted.
increaseEvmTime(g.TestBackendDestination, optimisticPeriodSeconds+offset)
g.bumpBackends()
txContextDestination := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr())
tx, err = g.DestinationDomainClient.Destination().PassAgentRoot(txContextDestination.TransactOpts)
g.Nil(err)
g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx)
g.bumpBackends()
// Verify that the guard eventually marks the accused agent as Slashed.
g.Eventually(func() bool {
status, err := g.DestinationDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address())
Nil(g.T(), err)
if status.Flag() == types.AgentFlagSlashed {
return true
}
g.bumpBackends()
return false
})
}