From 8bce96efd2b5e2fca83eb309a537a4e8290eb023 Mon Sep 17 00:00:00 2001 From: Max Planck <99688618+CryptoMaxPlanck@users.noreply.github.com> Date: Sun, 21 Aug 2022 15:31:07 -0400 Subject: [PATCH] Add Parser support for Destination and add DB models for monitoring (#102/#108) --- .../attestationcollector_test.go | 103 ++++++++++++++ .../attestationcollector/eventtype_string.go | 23 +++ core/contracts/attestationcollector/parser.go | 83 +++++++++++ .../attestationcollector/suite_test.go | 87 ++++++++++++ core/contracts/attestationcollector/topics.go | 44 ++++++ .../contracts/destination/destination_test.go | 92 ++++++++++++ .../contracts/destination/eventtype_string.go | 23 +++ core/contracts/destination/parser.go | 75 ++++++++++ core/contracts/destination/suite_test.go | 83 +++++++++++ core/contracts/destination/topics.go | 43 ++++++ core/db/datastore/sql/base/model.go | 45 +++++- core/db/datastore/sql/base/monitor.go | 132 ++++++++++++++++++ core/db/datastore/sql/base/txqueue.go | 5 +- core/db/db_test.go | 89 +++++++++++- core/db/message.go | 18 ++- core/testutil/contract_test.go | 2 +- core/testutil/contracttype.go | 2 +- core/testutil/deployers.go | 28 +++- core/types/attestation.go | 2 +- core/types/attestation_submitted.go | 35 +++++ core/types/message.go | 1 + 21 files changed, 999 insertions(+), 16 deletions(-) create mode 100644 core/contracts/attestationcollector/attestationcollector_test.go create mode 100644 core/contracts/attestationcollector/eventtype_string.go create mode 100644 core/contracts/attestationcollector/parser.go create mode 100644 core/contracts/attestationcollector/suite_test.go create mode 100644 core/contracts/attestationcollector/topics.go create mode 100644 core/contracts/destination/destination_test.go create mode 100644 core/contracts/destination/eventtype_string.go create mode 100644 core/contracts/destination/parser.go create mode 100644 core/contracts/destination/suite_test.go create mode 100644 core/contracts/destination/topics.go create mode 100644 core/db/datastore/sql/base/monitor.go create mode 100644 core/types/attestation_submitted.go diff --git a/core/contracts/attestationcollector/attestationcollector_test.go b/core/contracts/attestationcollector/attestationcollector_test.go new file mode 100644 index 0000000000..c1e9572c72 --- /dev/null +++ b/core/contracts/attestationcollector/attestationcollector_test.go @@ -0,0 +1,103 @@ +package attestationcollector_test + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + . "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/core/agents/notary" + "github.com/synapsecns/sanguine/core/contracts/attestationcollector" + "github.com/synapsecns/sanguine/core/types" + "github.com/synapsecns/synapse-node/contracts/bridge" +) + +func (a AttestationCollectorSuite) TestAttestationCollectorSuite() { + // Set up the contexts for all contracts, including the destination and attestation collector to get owner. + txContextOrigin := a.testBackendOrigin.GetTxContext(a.GetTestContext(), nil) + txContextDestination := a.testBackendDestination.GetTxContext(a.GetTestContext(), a.destinationContractMetadata.OwnerPtr()) + txContextAttestationCollector := a.testBackendDestination.GetTxContext(a.GetTestContext(), a.attestationContractMetadata.OwnerPtr()) + + // Create a channel and subscription to receive AttestationSubmitted events as they are emitted. + attestationSink := make(chan *attestationcollector.AttestationCollectorAttestationSubmitted) + subAttestation, err := a.attestationContract.WatchAttestationSubmitted(&bind.WatchOpts{Context: a.GetTestContext()}, attestationSink, []common.Address{}) + Nil(a.T(), err) + + encodedTips, err := types.EncodeTips(types.NewTips(big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0))) + Nil(a.T(), err) + + // Dispatch an event from the Origin contract to be accepted on the Destination contract. + tx, err := a.originContract.Dispatch(txContextOrigin.TransactOpts, 1, [32]byte{}, 1, encodedTips, nil) + Nil(a.T(), err) + a.testBackendOrigin.WaitForConfirmation(a.GetTestContext(), tx) + + // Create an attestation + localDomain := uint32(a.testBackendOrigin.Config().ChainID) + nonce := gofakeit.Uint32() + root := common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())) + unsignedAttestation := types.NewAttestation(localDomain, nonce, root) + hashedAttestation, err := notary.HashAttestation(unsignedAttestation) + Nil(a.T(), err) + + signature, err := a.signer.SignMessage(a.GetTestContext(), bridge.KappaToSlice(hashedAttestation), false) + Nil(a.T(), err) + + signedAttestation := types.NewSignedAttestation(unsignedAttestation, signature) + encodedSig, err := types.EncodeSignature(signedAttestation.Signature()) + Nil(a.T(), err) + + attestation, err := a.attestationHarness.FormatAttestation( + &bind.CallOpts{Context: a.GetTestContext()}, + signedAttestation.Attestation().Domain(), + signedAttestation.Attestation().Nonce(), + signedAttestation.Attestation().Root(), + encodedSig, + ) + Nil(a.T(), err) + + // Set notary to the testing address so we can submit attestations. + tx, err = a.destinationContract.SetNotary(txContextDestination.TransactOpts, uint32(a.testBackendOrigin.GetChainID()), a.signer.Address()) + Nil(a.T(), err) + a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx) + + // Submit the attestation to get an AttestationAccepted event. + tx, err = a.destinationContract.SubmitAttestation(txContextDestination.TransactOpts, attestation) + Nil(a.T(), err) + a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx) + + // Set notary to the testing address so we can submit attestations. + tx, err = a.attestationContract.AddNotary(txContextAttestationCollector.TransactOpts, uint32(a.testBackendOrigin.GetChainID()), a.signer.Address()) + Nil(a.T(), err) + a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx) + + // Submit the attestation to get an AttestationSubmitted event. + tx, err = a.attestationContract.SubmitAttestation(txContextAttestationCollector.TransactOpts, attestation) + Nil(a.T(), err) + a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx) + + watchCtx, cancel := context.WithTimeout(a.GetTestContext(), time.Second*10) + defer cancel() + + select { + // check for errors and fail + case <-watchCtx.Done(): + a.T().Error(a.T(), fmt.Errorf("test context completed %w", a.GetTestContext().Err())) + case <-subAttestation.Err(): + a.T().Error(a.T(), subAttestation.Err()) + // get AttestationSubmitted event + case item := <-attestationSink: + parser, err := attestationcollector.NewParser(a.attestationContract.Address()) + Nil(a.T(), err) + + // Check to see if the event was an AttestationSubmitted event. + eventType, ok := parser.EventType(item.Raw) + True(a.T(), ok) + Equal(a.T(), eventType, attestationcollector.AttestationSubmittedEvent) + + break + } +} diff --git a/core/contracts/attestationcollector/eventtype_string.go b/core/contracts/attestationcollector/eventtype_string.go new file mode 100644 index 0000000000..1d8882eaf0 --- /dev/null +++ b/core/contracts/attestationcollector/eventtype_string.go @@ -0,0 +1,23 @@ +// Code generated by "stringer -type=EventType"; DO NOT EDIT. + +package attestationcollector + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AttestationSubmittedEvent-0] +} + +const _EventType_name = "AttestationSubmittedEvent" + +var _EventType_index = [...]uint8{0, 25} + +func (i EventType) String() string { + if i >= EventType(len(_EventType_index)-1) { + return "EventType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] +} diff --git a/core/contracts/attestationcollector/parser.go b/core/contracts/attestationcollector/parser.go new file mode 100644 index 0000000000..5ea68e7433 --- /dev/null +++ b/core/contracts/attestationcollector/parser.go @@ -0,0 +1,83 @@ +package attestationcollector + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/synapsecns/sanguine/core/types" +) + +// init serves as a static assertion that AllEventTypes are accounted for. +func init() { + if len(_EventType_index)-1 != len(AllEventTypes) { + panic("add new events to alle vent types") + } +} + +// Parser parses events from the attestation collector contract. +type Parser interface { + // EventType determines if an event was initiated by the bridge or the user. + EventType(log ethTypes.Log) (_ EventType, ok bool) + // ParseAttestationSubmitted parses an AttestationSubmitted event + ParseAttestationSubmitted(log ethTypes.Log) (_ types.AttestationSubmitted, ok bool) +} + +type parserImpl struct { + // filterer is the parser filterer we use to parse events + filterer *AttestationCollectorFilterer +} + +// NewParser creates a new parser for the attestation collector contract. +func NewParser(attestationCollectorAddress common.Address) (Parser, error) { + parser, err := NewAttestationCollectorFilterer(attestationCollectorAddress, nil) + if err != nil { + return nil, fmt.Errorf("could not create %T: %w", AttestationCollectorFilterer{}, err) + } + + return &parserImpl{filterer: parser}, nil +} + +func (p parserImpl) EventType(log ethTypes.Log) (_ EventType, ok bool) { + for _, logTopic := range log.Topics { + eventType := eventTypeFromTopic(logTopic) + if eventType == nil { + continue + } + + return *eventType, true + } + // return an unknown event to avoid cases where user failed to check the event type + return EventType(len(AllEventTypes) + 2), false +} + +// ParseAttestationSubmitted parses an AttestationSubmitted event. +func (p parserImpl) ParseAttestationSubmitted(log ethTypes.Log) (_ types.AttestationSubmitted, ok bool) { + attestation, err := p.filterer.ParseAttestationSubmitted(log) + if err != nil { + return nil, false + } + + attestationSubmitted := types.NewAttestationSubmitted( + attestation.Notary.Hash(), + attestation.Attestation, + ) + + return attestationSubmitted, true +} + +// EventType is the type of the attestation collector events +//go:generate go run golang.org/x/tools/cmd/stringer -type=EventType +type EventType uint + +const ( + // AttestationSubmittedEvent is a AttestationSubmitted event. + AttestationSubmittedEvent EventType = 0 +) + +// Int gets the int for an event type. +func (i EventType) Int() uint8 { + return uint8(i) +} + +// AllEventTypes contains all event types. +var AllEventTypes = []EventType{AttestationSubmittedEvent} diff --git a/core/contracts/attestationcollector/suite_test.go b/core/contracts/attestationcollector/suite_test.go new file mode 100644 index 0000000000..781682b2e7 --- /dev/null +++ b/core/contracts/attestationcollector/suite_test.go @@ -0,0 +1,87 @@ +package attestationcollector_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core/contracts/attestationcollector" + "github.com/synapsecns/sanguine/core/contracts/destination" + "github.com/synapsecns/sanguine/core/contracts/origin" + "github.com/synapsecns/sanguine/core/contracts/test/attestationharness" + "github.com/synapsecns/sanguine/core/testutil" + "github.com/synapsecns/sanguine/ethergo/signer/signer" + "github.com/synapsecns/sanguine/ethergo/signer/signer/localsigner" + "github.com/synapsecns/sanguine/ethergo/signer/wallet" + "github.com/synapsecns/synapse-node/testutils" + "github.com/synapsecns/synapse-node/testutils/backends" + "github.com/synapsecns/synapse-node/testutils/backends/preset" +) + +// AttestationCollectorSuite is the attestation collector test suite. +type AttestationCollectorSuite struct { + *testutils.TestSuite + originContract *origin.OriginRef + destinationContract *destination.DestinationRef + destinationContractMetadata backends.DeployedContract + attestationHarness *attestationharness.AttestationHarnessRef + attestationContract *attestationcollector.AttestationCollectorRef + attestationContractMetadata backends.DeployedContract + testBackendOrigin backends.SimulatedTestBackend + testBackendDestination backends.SimulatedTestBackend + wallet wallet.Wallet + signer signer.Signer +} + +// NewAttestationCollectorSuite creates an end-to-end test suite. +func NewAttestationCollectorSuite(tb testing.TB) *AttestationCollectorSuite { + tb.Helper() + return &AttestationCollectorSuite{ + TestSuite: testutils.NewTestSuite(tb), + } +} + +func (a *AttestationCollectorSuite) SetupTest() { + a.TestSuite.SetupTest() + + a.testBackendOrigin = preset.GetRinkeby().Geth(a.GetTestContext(), a.T()) + a.testBackendDestination = preset.GetBSCTestnet().Geth(a.GetTestContext(), a.T()) + deployManager := testutil.NewDeployManager(a.T()) + + _, a.originContract = deployManager.GetOrigin(a.GetTestContext(), a.testBackendOrigin) + _, a.attestationHarness = deployManager.GetAttestationHarness(a.GetTestContext(), a.testBackendOrigin) + a.attestationContractMetadata, a.attestationContract = deployManager.GetAttestationCollector(a.GetTestContext(), a.testBackendDestination) + a.destinationContractMetadata, a.destinationContract = deployManager.GetDestination(a.GetTestContext(), a.testBackendDestination) + + var err error + a.wallet, err = wallet.FromRandom() + if err != nil { + a.T().Fatal(err) + } + + _, notaryManager := deployManager.GetNotaryManager(a.GetTestContext(), a.testBackendOrigin) + owner, err := notaryManager.Owner(&bind.CallOpts{Context: a.GetTestContext()}) + if err != nil { + a.T().Fatal(err) + } + + a.signer = localsigner.NewSigner(a.wallet.PrivateKey()) + a.testBackendOrigin.FundAccount(a.GetTestContext(), a.signer.Address(), *big.NewInt(params.Ether)) + a.testBackendDestination.FundAccount(a.GetTestContext(), a.signer.Address(), *big.NewInt(params.Ether)) + + transactOpts := a.testBackendOrigin.GetTxContext(a.GetTestContext(), &owner) + + tx, err := notaryManager.SetNotary(transactOpts.TransactOpts, a.signer.Address()) + if err != nil { + a.T().Fatal(err) + } + + a.testBackendOrigin.WaitForConfirmation(a.GetTestContext(), tx) +} + +// TestAttestationCollectorSuite runs the integration test suite. +func TestAttestationCollectorSuite(t *testing.T) { + suite.Run(t, NewAttestationCollectorSuite(t)) +} diff --git a/core/contracts/attestationcollector/topics.go b/core/contracts/attestationcollector/topics.go new file mode 100644 index 0000000000..6faaced9ba --- /dev/null +++ b/core/contracts/attestationcollector/topics.go @@ -0,0 +1,44 @@ +package attestationcollector + +import ( + "bytes" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func init() { + // set topics + var err error + + parsedAttestationCollector, err := abi.JSON(strings.NewReader(AttestationCollectorABI)) + if err != nil { + panic(err) + } + + AttestationSubmittedTopic = parsedAttestationCollector.Events["AttestationSubmitted"].ID +} + +// AttestationSubmittedTopic is the topic that gets emitted +// when the AttestationSubmitted event is called. +var AttestationSubmittedTopic common.Hash + +// topicMap maps events to topics. +// this is returned as a function to assert immutability. +func topicMap() map[EventType]common.Hash { + return map[EventType]common.Hash{ + AttestationSubmittedEvent: AttestationSubmittedTopic, + } +} + +// eventTypeFromTopic gets the event type from the topic +// returns nil if the topic is not found. +func eventTypeFromTopic(ogTopic common.Hash) *EventType { + for eventType, topic := range topicMap() { + if bytes.Equal(ogTopic.Bytes(), topic.Bytes()) { + return &eventType + } + } + return nil +} diff --git a/core/contracts/destination/destination_test.go b/core/contracts/destination/destination_test.go new file mode 100644 index 0000000000..650676d87b --- /dev/null +++ b/core/contracts/destination/destination_test.go @@ -0,0 +1,92 @@ +package destination_test + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + . "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/core/agents/notary" + "github.com/synapsecns/sanguine/core/contracts/destination" + "github.com/synapsecns/sanguine/core/types" + "github.com/synapsecns/synapse-node/contracts/bridge" +) + +func (d DestinationSuite) TestDestinationSuite() { + // Set up contexts for both Origin and Destination, also getting owner for Destination for reassigning notary role. + txContextOrigin := d.testBackendOrigin.GetTxContext(d.GetTestContext(), nil) + txContextDestination := d.testBackendDestination.GetTxContext(d.GetTestContext(), d.destinationContractMetadata.OwnerPtr()) + + // Create a channel and subscription to receive AttestationAccepted events as they are emitted. + attestationSink := make(chan *destination.DestinationAttestationAccepted) + subAttestation, err := d.destinationContract.WatchAttestationAccepted(&bind.WatchOpts{Context: d.GetTestContext()}, attestationSink, []uint32{}, []uint32{}, [][32]byte{}) + Nil(d.T(), err) + + encodedTips, err := types.EncodeTips(types.NewTips(big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0))) + Nil(d.T(), err) + + // Dispatch an event from the Origin contract to be accepted on the Destination contract. + tx, err := d.originContract.Dispatch(txContextOrigin.TransactOpts, 1, [32]byte{}, 1, encodedTips, nil) + Nil(d.T(), err) + d.testBackendOrigin.WaitForConfirmation(d.GetTestContext(), tx) + + // Create an attestation + localDomain := uint32(d.testBackendOrigin.Config().ChainID) + nonce := gofakeit.Uint32() + root := common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())) + unsignedAttestation := types.NewAttestation(localDomain, nonce, root) + hashedAttestation, err := notary.HashAttestation(unsignedAttestation) + Nil(d.T(), err) + + signature, err := d.signer.SignMessage(d.GetTestContext(), bridge.KappaToSlice(hashedAttestation), false) + Nil(d.T(), err) + + signedAttestation := types.NewSignedAttestation(unsignedAttestation, signature) + encodedSig, err := types.EncodeSignature(signedAttestation.Signature()) + Nil(d.T(), err) + + attestation, err := d.attestationHarness.FormatAttestation( + &bind.CallOpts{Context: d.GetTestContext()}, + signedAttestation.Attestation().Domain(), + signedAttestation.Attestation().Nonce(), + signedAttestation.Attestation().Root(), + encodedSig, + ) + Nil(d.T(), err) + + // Set notary to the testing address so we can submit attestations. + tx, err = d.destinationContract.SetNotary(txContextDestination.TransactOpts, uint32(d.testBackendOrigin.GetChainID()), d.signer.Address()) + Nil(d.T(), err) + d.testBackendDestination.WaitForConfirmation(d.GetTestContext(), tx) + + // Submit the attestation to get an AttestationAccepted event. + tx, err = d.destinationContract.SubmitAttestation(txContextDestination.TransactOpts, attestation) + Nil(d.T(), err) + d.testBackendDestination.WaitForConfirmation(d.GetTestContext(), tx) + + watchCtx, cancel := context.WithTimeout(d.GetTestContext(), time.Second*10) + defer cancel() + + select { + // check for errors and fail + case <-watchCtx.Done(): + d.T().Error(d.T(), fmt.Errorf("test context completed %w", d.GetTestContext().Err())) + case <-subAttestation.Err(): + d.T().Error(d.T(), subAttestation.Err()) + // get attestation accepted event + case item := <-attestationSink: + parser, err := destination.NewParser(d.destinationContract.Address()) + Nil(d.T(), err) + + // Check to see if the event was an AttestationAccepted event. + eventType, ok := parser.EventType(item.Raw) + True(d.T(), ok) + Equal(d.T(), eventType, destination.AttestationAcceptedEvent) + + break + } +} diff --git a/core/contracts/destination/eventtype_string.go b/core/contracts/destination/eventtype_string.go new file mode 100644 index 0000000000..9a9cfd781c --- /dev/null +++ b/core/contracts/destination/eventtype_string.go @@ -0,0 +1,23 @@ +// Code generated by "stringer -type=EventType"; DO NOT EDIT. + +package destination + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AttestationAcceptedEvent-0] +} + +const _EventType_name = "AttestationAcceptedEvent" + +var _EventType_index = [...]uint8{0, 24} + +func (i EventType) String() string { + if i >= EventType(len(_EventType_index)-1) { + return "EventType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] +} diff --git a/core/contracts/destination/parser.go b/core/contracts/destination/parser.go new file mode 100644 index 0000000000..34fa1eedf3 --- /dev/null +++ b/core/contracts/destination/parser.go @@ -0,0 +1,75 @@ +package destination + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/synapsecns/sanguine/core/types" + legacyTypes "github.com/synapsecns/synapse-node/pkg/types" +) + +// Parser parses events from the destination contract. +type Parser interface { + // EventType determines if an event was initiated by the bridge or the user. + EventType(log ethTypes.Log) (_ EventType, ok bool) + // ParseAttestationAccepted parses an AttestationAccepted event + ParseAttestationAccepted(log ethTypes.Log) (_ types.Attestation, ok bool) +} + +type parserImpl struct { + // filterer is the parser filterer we use to parse events + filterer *DestinationFilterer +} + +// NewParser creates a new parser for the destination contract. +func NewParser(destinationAddress common.Address) (Parser, error) { + parser, err := NewDestinationFilterer(destinationAddress, nil) + if err != nil { + return nil, fmt.Errorf("could not create %T: %w", DestinationFilterer{}, err) + } + + return &parserImpl{filterer: parser}, nil +} + +func (p parserImpl) EventType(log ethTypes.Log) (_ EventType, ok bool) { + for _, logTopic := range log.Topics { + eventType := eventTypeFromTopic(logTopic) + if eventType == nil { + continue + } + + return *eventType, true + } + // return an unknown event to avoid cases where user failed to check the event type + return EventType(len(legacyTypes.AllEventTypes()) + 2), false +} + +// ParseAttestationAccepted parses an AttestationAccepted event. +func (p parserImpl) ParseAttestationAccepted(log ethTypes.Log) (_ types.Attestation, ok bool) { + acceptedAttestation, err := p.filterer.ParseAttestationAccepted(log) + if err != nil { + return nil, false + } + + attestation := types.NewAttestation( + acceptedAttestation.Origin, + acceptedAttestation.Nonce, + acceptedAttestation.Root, + ) + return attestation, true +} + +// EventType is the type of the destination event +//go:generate go run golang.org/x/tools/cmd/stringer -type=EventType +type EventType uint + +const ( + // AttestationAcceptedEvent is an AttestationAccepted event. + AttestationAcceptedEvent EventType = 0 +) + +// Int gets the int for an event type. +func (i EventType) Int() uint8 { + return uint8(i) +} diff --git a/core/contracts/destination/suite_test.go b/core/contracts/destination/suite_test.go new file mode 100644 index 0000000000..1088e58629 --- /dev/null +++ b/core/contracts/destination/suite_test.go @@ -0,0 +1,83 @@ +package destination_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core/contracts/destination" + "github.com/synapsecns/sanguine/core/contracts/origin" + "github.com/synapsecns/sanguine/core/contracts/test/attestationharness" + "github.com/synapsecns/sanguine/core/testutil" + "github.com/synapsecns/sanguine/ethergo/signer/signer" + "github.com/synapsecns/sanguine/ethergo/signer/signer/localsigner" + "github.com/synapsecns/sanguine/ethergo/signer/wallet" + "github.com/synapsecns/synapse-node/testutils" + "github.com/synapsecns/synapse-node/testutils/backends" + "github.com/synapsecns/synapse-node/testutils/backends/preset" +) + +// DestinationSuite is the destination test suite. +type DestinationSuite struct { + *testutils.TestSuite + originContract *origin.OriginRef + destinationContract *destination.DestinationRef + destinationContractMetadata backends.DeployedContract + attestationHarness *attestationharness.AttestationHarnessRef + testBackendOrigin backends.SimulatedTestBackend + testBackendDestination backends.SimulatedTestBackend + wallet wallet.Wallet + signer signer.Signer +} + +// NewDestinationSuite creates a end-to-end test suite. +func NewDestinationSuite(tb testing.TB) *DestinationSuite { + tb.Helper() + return &DestinationSuite{ + TestSuite: testutils.NewTestSuite(tb), + } +} + +func (d *DestinationSuite) SetupTest() { + d.TestSuite.SetupTest() + + d.testBackendOrigin = preset.GetRinkeby().Geth(d.GetTestContext(), d.T()) + d.testBackendDestination = preset.GetBSCTestnet().Geth(d.GetTestContext(), d.T()) + deployManager := testutil.NewDeployManager(d.T()) + + _, d.originContract = deployManager.GetOrigin(d.GetTestContext(), d.testBackendOrigin) + _, d.attestationHarness = deployManager.GetAttestationHarness(d.GetTestContext(), d.testBackendOrigin) + d.destinationContractMetadata, d.destinationContract = deployManager.GetDestination(d.GetTestContext(), d.testBackendDestination) + + var err error + d.wallet, err = wallet.FromRandom() + if err != nil { + d.T().Fatal(err) + } + + _, notaryManager := deployManager.GetNotaryManager(d.GetTestContext(), d.testBackendOrigin) + owner, err := notaryManager.Owner(&bind.CallOpts{Context: d.GetTestContext()}) + if err != nil { + d.T().Fatal(err) + } + + d.signer = localsigner.NewSigner(d.wallet.PrivateKey()) + d.testBackendOrigin.FundAccount(d.GetTestContext(), d.signer.Address(), *big.NewInt(params.Ether)) + d.testBackendDestination.FundAccount(d.GetTestContext(), d.signer.Address(), *big.NewInt(params.Ether)) + + transactOpts := d.testBackendOrigin.GetTxContext(d.GetTestContext(), &owner) + + tx, err := notaryManager.SetNotary(transactOpts.TransactOpts, d.signer.Address()) + if err != nil { + d.T().Fatal(err) + } + + d.testBackendOrigin.WaitForConfirmation(d.GetTestContext(), tx) +} + +// TestDestinationSuite runs the integration test suite. +func TestDestinationSuite(t *testing.T) { + suite.Run(t, NewDestinationSuite(t)) +} diff --git a/core/contracts/destination/topics.go b/core/contracts/destination/topics.go new file mode 100644 index 0000000000..d3f89d9a49 --- /dev/null +++ b/core/contracts/destination/topics.go @@ -0,0 +1,43 @@ +package destination + +import ( + "bytes" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func init() { + // set topics + var err error + + parsedDestination, err := abi.JSON(strings.NewReader(DestinationABI)) + if err != nil { + panic(err) + } + + AttestationAcceptedTopic = parsedDestination.Events["AttestationAccepted"].ID +} + +// AttestationAcceptedTopic is the topic that gets emitted when the AttestationAccepted event is called. +var AttestationAcceptedTopic common.Hash + +// topicMap maps events to topics. +// this is returned as a function to assert immutability. +func topicMap() map[EventType]common.Hash { + return map[EventType]common.Hash{ + AttestationAcceptedEvent: AttestationAcceptedTopic, + } +} + +// eventTypeFromTopic gets the event type from the topic +// returns nil if the topic is not found. +func eventTypeFromTopic(ogTopic common.Hash) *EventType { + for eventType, topic := range topicMap() { + if bytes.Equal(ogTopic.Bytes(), topic.Bytes()) { + return &eventType + } + } + return nil +} diff --git a/core/db/datastore/sql/base/model.go b/core/db/datastore/sql/base/model.go index 3ee8111029..3c5d6c4f65 100644 --- a/core/db/datastore/sql/base/model.go +++ b/core/db/datastore/sql/base/model.go @@ -1,11 +1,12 @@ package base import ( + "math/big" + "time" + "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/core/types" "gorm.io/gorm" - "math/big" - "time" ) // define common field names. See package docs for an explanation of why we have to do this. @@ -78,6 +79,46 @@ type BlockEndModel struct { BlockNumber uint32 `gorm:"block_number"` } +// DispatchMessage is used to store information about dispatched messages from the Origin contract. +// Monitoring uses these messages' nonces to check for missing messages on destination chains. +type DispatchMessage struct { + // DMOrigin is the origin chainID of the message. + DMOrigin uint32 `gorm:"column:origin"` + // DMSender is the sender of the message. + DMSender string `gorm:"column:sender"` + // DMNonce is the nonce of the message. + DMNonce uint32 `gorm:"column:nonce"` + // DMDestination is the destination chainID of the message. + DMDestination uint32 `gorm:"column:destination"` + // DMRecipient is the recipient of the message. + DMRecipient string `gorm:"column:recipient"` + // DMOptimisticSeconds is the optimistic seconds of the message. + DMOptimisticSeconds uint32 `gorm:"column:optimistic_seconds"` + // DMNotaryTip is the notary tip of the message. + DMNotaryTip []byte `gorm:"column:notary_tip"` + // DMBroadcasterTip is the broadcaster tip of the message. + DMBroadcasterTip []byte `gorm:"column:broadcaster_tip"` + // DMProverTip is the prover tip of the message. + DMProverTip []byte `gorm:"column:prover_tip"` + // DMExecutorTip is the executor tip of the message. + DMExecutorTip []byte `gorm:"column:executor_tip"` + // DMBody is the body of the message. + DMBody []byte `gorm:"column:body"` +} + +// AcceptedAttestation is used to track every received accepted attestation over all mirrors. +// Monitoring uses these accepted attestations' nonces to check for missing messages on destination chains. +type AcceptedAttestation struct { + // AAOriginDomain is the chainID of the Origin contract. + AAOriginDomain uint32 `gorm:"column:origin_domain"` + // AANonce is the nonce of the attestation. + AANonce uint32 `gorm:"column:nonce"` + // AARoot is the root of the attestation. + AARoot string `gorm:"column:root"` + // AADestinationDomain is the chainID of the Destination contract. + AADestinationDomain uint32 `gorm:"column:destination_domain"` +} + // CommittedMessage is a committed message // it allows for querying on both the committed message and the underlying fields. type CommittedMessage struct { diff --git a/core/db/datastore/sql/base/monitor.go b/core/db/datastore/sql/base/monitor.go new file mode 100644 index 0000000000..c6101f5f8a --- /dev/null +++ b/core/db/datastore/sql/base/monitor.go @@ -0,0 +1,132 @@ +package base + +import ( + "context" + "encoding/hex" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/synapsecns/sanguine/core/types" + "gorm.io/gorm" +) + +// StoreDispatchMessage takes a message and stores the information. +func (s Store) StoreDispatchMessage(ctx context.Context, message types.Message) error { + dxTx := s.DB().WithContext(ctx).Create(&DispatchMessage{ + DMOrigin: message.OriginDomain(), + DMSender: message.Sender().String(), + DMNonce: message.Nonce(), + DMDestination: message.DestinationDomain(), + DMRecipient: message.Recipient().String(), + DMOptimisticSeconds: message.OptimisticSeconds(), + DMNotaryTip: message.Tips().NotaryTip().Bytes(), + DMBroadcasterTip: message.Tips().BroadcasterTip().Bytes(), + DMProverTip: message.Tips().ProverTip().Bytes(), + DMExecutorTip: message.Tips().ExecutorTip().Bytes(), + DMBody: message.Body(), + }) + if dxTx.Error != nil { + return fmt.Errorf("could not insert dispatch message: %w", dxTx.Error) + } + return nil +} + +// StoreAcceptedAttestation stores an accepted attestation from a destination. +func (s Store) StoreAcceptedAttestation(ctx context.Context, destinationDomain uint32, attestation types.Attestation) error { + root := attestation.Root() + dxTx := s.DB().WithContext(ctx).Create(&AcceptedAttestation{ + AAOriginDomain: attestation.Domain(), + AANonce: attestation.Nonce(), + AARoot: "0x" + hex.EncodeToString(root[:]), + AADestinationDomain: destinationDomain, + }) + if dxTx.Error != nil { + return fmt.Errorf("could not insert accepted attestation: %w", dxTx.Error) + } + return nil +} + +// GetDelinquentMessages gets messages that were sent on the origin chain, +// but never received on the destination chain. +func (s Store) GetDelinquentMessages(ctx context.Context, destinationDomain uint32) ([]types.Message, error) { + var delinquentMessages []types.Message + var res DispatchMessage + stmt := &gorm.Statement{DB: s.DB().WithContext(ctx)} + // Get the SQL table name of the DispatchMessage table. + err := stmt.Parse(&DispatchMessage{}) + if err != nil { + return nil, fmt.Errorf("could not parse dispatch message table name: %w", err) + } + dmTable := stmt.Schema.Table + // Get the SQL table name of the AcceptedAttestation table. + err = stmt.Parse(&AcceptedAttestation{}) + if err != nil { + return nil, fmt.Errorf("could not parse accepted attestation table name: %w", err) + } + aaTable := stmt.Schema.Table + + // Format SQL query strings. + joinStatement := fmt.Sprintf( + "LEFT OUTER JOIN %s ON %s.%s = %s.%s", + aaTable, + aaTable, + NonceFieldName, + dmTable, + NonceFieldName, + ) + whereStatement := fmt.Sprintf( + "%s.%s IS NULL AND %s.destination = %d", + aaTable, + NonceFieldName, + dmTable, + destinationDomain, + ) + + // Run an inverse join on the nonces between dispatched messages and accepted attestations on a given destination domain. + rows, err := s.DB().WithContext(ctx). + Model(&DispatchMessage{}). + Select(dmTable + ".*"). + Joins(joinStatement). + Where(whereStatement). + Rows() + if err != nil { + return []types.Message{}, fmt.Errorf("could not get rows: %w", err) + } + if rows.Err() != nil { + return []types.Message{}, fmt.Errorf("could not get rows: %w", rows.Err()) + } + for rows.Next() { + err = s.DB().ScanRows(rows, &res) + if err != nil { + return []types.Message{}, fmt.Errorf("could not scan rows: %w", err) + } + // Create a new Message based on the data, and append to the returned list. + delinquentMessage := dispatchMessageToMessage(res) + delinquentMessages = append(delinquentMessages, delinquentMessage) + } + return delinquentMessages, nil +} + +// Take a DispatchMessage and convert it into a Message. +func dispatchMessageToMessage(d DispatchMessage) types.Message { + header := types.NewHeader( + d.DMOrigin, + common.HexToHash(d.DMSender), + d.DMNonce, + d.DMDestination, + common.HexToHash(d.DMRecipient), + d.DMOptimisticSeconds, + ) + tips := types.NewTips( + new(big.Int).SetBytes(d.DMNotaryTip), + new(big.Int).SetBytes(d.DMBroadcasterTip), + new(big.Int).SetBytes(d.DMProverTip), + new(big.Int).SetBytes(d.DMExecutorTip), + ) + return types.NewMessage( + header, + tips, + d.DMBody, + ) +} diff --git a/core/db/datastore/sql/base/txqueue.go b/core/db/datastore/sql/base/txqueue.go index 649ec1f2bc..5f9ff689c5 100644 --- a/core/db/datastore/sql/base/txqueue.go +++ b/core/db/datastore/sql/base/txqueue.go @@ -4,11 +4,12 @@ import ( "context" "database/sql" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core/db" "gorm.io/gorm" - "math/big" ) // Store is the sqlite store. It extends the base store for sqlite specific queries. @@ -30,7 +31,7 @@ func (s Store) DB() *gorm.DB { //see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time func GetAllModels() (allModels []interface{}) { allModels = append(allModels, - &RawEthTX{}, &ProcessedEthTx{}, &BlockEndModel{}, &CommittedMessage{}, &SignedAttestation{}) + &RawEthTX{}, &ProcessedEthTx{}, &BlockEndModel{}, &CommittedMessage{}, &SignedAttestation{}, &DispatchMessage{}, &AcceptedAttestation{}) return allModels } diff --git a/core/db/db_test.go b/core/db/db_test.go index f212997460..15c1239613 100644 --- a/core/db/db_test.go +++ b/core/db/db_test.go @@ -1,12 +1,15 @@ package db_test import ( + "math/big" + "math/rand" + "time" + "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/common" . "github.com/stretchr/testify/assert" "github.com/synapsecns/sanguine/core/db" "github.com/synapsecns/sanguine/core/types" - "math/big" ) func (t *DBSuite) TestRetrieveLatestNonce() { @@ -40,3 +43,87 @@ func (t *DBSuite) TestRetrieveLatestNonce() { } }) } + +func (t *DBSuite) TestStoreMonitoring() { + t.RunOnAllDBs(func(testDB db.SynapseDB) { + header := types.NewHeader(gofakeit.Uint32(), common.BigToHash(big.NewInt(gofakeit.Int64())), gofakeit.Uint32(), gofakeit.Uint32(), common.BigToHash(big.NewInt(gofakeit.Int64())), gofakeit.Uint32()) + tips := types.NewTips(big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)) + message := types.NewMessage(header, tips, []byte(gofakeit.Sentence(10))) + + err := testDB.StoreDispatchMessage(t.GetTestContext(), message) + Nil(t.T(), err) + + attestation := types.NewAttestation(1, gofakeit.Uint32(), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64()))) + + err = testDB.StoreAcceptedAttestation(t.GetTestContext(), gofakeit.Uint32(), attestation) + Nil(t.T(), err) + }) +} + +func (t *DBSuite) TestGetDelinquentMessage() { + t.RunOnAllDBs(func(testDB db.SynapseDB) { + tips := types.NewTips(big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)) + var nonceRange = uint32(gofakeit.Uint8()) + var destinationDomain = gofakeit.Uint32() + var targetedDomain uint32 + var delinquentNonces []uint32 + var otherDelinquentNonces []uint32 + var header types.Header + var message types.Message + var attestation types.Attestation + var storeAttestation bool + + for nonce := uint32(0); nonce <= nonceRange; nonce++ { + // Populate the databases of DispatchMessages and AcceptedAttestations. + // Use random cases for different scenarios of domains and if an attestation is stored. + rand.Seed(time.Now().UnixNano()) + //nolint:gosec + random := rand.Intn(4) + switch random { + // Case 0 is where we use destinationDomain and store the accepted attestation + case 0: + targetedDomain = destinationDomain + storeAttestation = true + // Case 1 is where we use destinationDomain and do not store the accepted attestation + case 1: + targetedDomain = destinationDomain + storeAttestation = false + // Keep track of what message nonces will be delinquent + delinquentNonces = append(delinquentNonces, nonce) + // Case 2 is where we use destinationDomain+1 and store the accepted attestation + case 2: + targetedDomain = destinationDomain + 1 + storeAttestation = true + // Case 3 is where we use destinationDomain+1 and do not store the accepted attestation + case 3: + targetedDomain = destinationDomain + 1 + storeAttestation = false + // Keep track of what message nonces will be delinquent + otherDelinquentNonces = append(otherDelinquentNonces, nonce) + } + var err error + header = types.NewHeader(gofakeit.Uint32(), common.BigToHash(big.NewInt(gofakeit.Int64())), nonce, targetedDomain, common.BigToHash(big.NewInt(gofakeit.Int64())), gofakeit.Uint32()) + message = types.NewMessage(header, tips, []byte(gofakeit.Sentence(10))) + err = testDB.StoreDispatchMessage(t.GetTestContext(), message) + Nil(t.T(), err) + if storeAttestation { + attestation = types.NewAttestation(targetedDomain, nonce, common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64()))) + err = testDB.StoreAcceptedAttestation(t.GetTestContext(), targetedDomain, attestation) + Nil(t.T(), err) + } + } + // Test to ensure the delinquent messages are successfully tracked. + delinquentMessages, err := testDB.GetDelinquentMessages(t.GetTestContext(), destinationDomain) + Nil(t.T(), err) + Equal(t.T(), len(delinquentMessages), len(delinquentNonces)) + for index, delinquentMessage := range delinquentMessages { + Equal(t.T(), delinquentMessage.Nonce(), delinquentNonces[index]) + } + otherDelinquentMessages, err := testDB.GetDelinquentMessages(t.GetTestContext(), destinationDomain+1) + Nil(t.T(), err) + Equal(t.T(), len(otherDelinquentMessages), len(otherDelinquentNonces)) + for index, otherDelinquentMessage := range otherDelinquentMessages { + Equal(t.T(), otherDelinquentMessage.Nonce(), otherDelinquentNonces[index]) + } + }) +} diff --git a/core/db/message.go b/core/db/message.go index fc5cbad8e6..86c743abd2 100644 --- a/core/db/message.go +++ b/core/db/message.go @@ -2,10 +2,11 @@ package db import ( "context" + "math/big" + "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core/types" - "math/big" ) // TxQueueDB contains an interface for storing transactions currently being processed. @@ -27,18 +28,29 @@ type MessageDB interface { // StoreMessageLatestBlockEnd stores the latest block end StoreMessageLatestBlockEnd(ctx context.Context, domainID uint32, blockNumber uint32) error // GetMessageLatestBlockEnd gets the message latest block - // returns ErrNoStoredBlockForChain when not precent + // returns ErrNoStoredBlockForChain when not present GetMessageLatestBlockEnd(ctx context.Context, domainID uint32) (height uint32, err error) // StoreCommittedMessage stores a raw committed message StoreCommittedMessage(ctx context.Context, domainID uint32, message types.CommittedMessage) error // StoreSignedAttestations stores a signed attestation StoreSignedAttestations(ctx context.Context, attestation types.SignedAttestation) error - // RetrieveSignedAttestationByNonce retreives a signed attestation by nonce + // RetrieveSignedAttestationByNonce retrieves a signed attestation by nonce RetrieveSignedAttestationByNonce(ctx context.Context, domainID, nonce uint32) (attestation types.SignedAttestation, err error) } +// MonitorDB stores event data for monitoring. +type MonitorDB interface { + // StoreDispatchMessage stores a dispatch message + StoreDispatchMessage(ctx context.Context, message types.Message) error + // StoreAcceptedAttestation stores an accepted attestation + StoreAcceptedAttestation(ctx context.Context, destinationDomain uint32, attestation types.Attestation) error + // GetDelinquentMessage gets messages that were sent, but never received + GetDelinquentMessages(ctx context.Context, destinationDomain uint32) ([]types.Message, error) +} + // SynapseDB combines db types. type SynapseDB interface { MessageDB TxQueueDB + MonitorDB } diff --git a/core/testutil/contract_test.go b/core/testutil/contract_test.go index efd73edd2b..ab555d0472 100644 --- a/core/testutil/contract_test.go +++ b/core/testutil/contract_test.go @@ -37,7 +37,7 @@ func (s *SimulatedSuite) TestDependencies() { Equal(s.T(), dc.ChainID().String(), wrappedBackend.GetBigChainID().String()) deployedContracts := s.GetDeployedContractsFromRegistry(contractRegistry) - // make sure dependency count is equal (adding our own coract to there expected amount) + // make sure dependency count is equal (adding our own contract to there expected amount) Equal(s.T(), len(deployedContracts), len(contract.Dependencies())+1) for _, dep := range contract.Dependencies() { _, hasDep := deployedContracts[dep.ID()] diff --git a/core/testutil/contracttype.go b/core/testutil/contracttype.go index e7f4c5846a..06dbaf8e65 100644 --- a/core/testutil/contracttype.go +++ b/core/testutil/contracttype.go @@ -53,7 +53,7 @@ const ( AttestationHarnessType contractTypeImpl = iota // TipsHarnessType is the type of the tips harness. TipsHarnessType contractTypeImpl = iota - // HeaderHarnessType is the tyoe of the header harness. + // HeaderHarnessType is the type of the header harness. HeaderHarnessType contractTypeImpl = iota // DestinationHarnessType is the destination harness type. DestinationHarnessType contractTypeImpl = iota // DestinationHarness diff --git a/core/testutil/deployers.go b/core/testutil/deployers.go index 9bd93c0762..fbbf8ee2b9 100644 --- a/core/testutil/deployers.go +++ b/core/testutil/deployers.go @@ -3,6 +3,7 @@ package testutil import ( "context" "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -103,13 +104,13 @@ func (a AttestationCollectorDeployer) Deploy(ctx context.Context) (backends.Depl return a.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, interface{}, error) { attestationAddress, attestationTx, collector, err := attestationcollector.DeployAttestationCollector(transactOps, backend) if err != nil { - return common.Address{}, nil, nil, fmt.Errorf("could not deploy attesation collector: %w", err) + return common.Address{}, nil, nil, fmt.Errorf("could not deploy attestation collector: %w", err) } auth := a.Backend().GetTxContext(ctx, &transactOps.From) initTx, err := collector.Initialize(auth.TransactOpts) if err != nil { - return common.Address{}, nil, nil, fmt.Errorf("could not initialize attesation collector: %w", err) + return common.Address{}, nil, nil, fmt.Errorf("could not initialize attestation collector: %w", err) } a.Backend().WaitForConfirmation(ctx, initTx) @@ -130,10 +131,27 @@ func NewDestinationDeployer(registry deployer.GetOnlyContractRegistry, backend b } // Deploy deploys the destination. -func (r DestinationDeployer) Deploy(ctx context.Context) (backends.DeployedContract, error) { - return r.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, interface{}, error) { - return destination.DeployDestination(transactOps, backend, uint32(r.Backend().GetChainID())) +func (d DestinationDeployer) Deploy(ctx context.Context) (backends.DeployedContract, error) { + return d.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, interface{}, error) { + destinationAddress, destinationTx, destination, err := destination.DeployDestination(transactOps, backend, uint32(d.Backend().GetChainID())) + if err != nil { + return common.Address{}, nil, nil, fmt.Errorf("could not deploy destination: %w", err) + } + + auth := d.Backend().GetTxContext(ctx, &transactOps.From) + initTx, err := destination.Initialize(auth.TransactOpts, uint32(d.Registry().Get(ctx, OriginType).ChainID().Uint64()), common.Address{}) + if err != nil { + return common.Address{}, nil, nil, fmt.Errorf("could not initialize destination: %w", err) + } + d.Backend().WaitForConfirmation(ctx, initTx) + + return destinationAddress, destinationTx, destination, nil }, func(address common.Address, backend bind.ContractBackend) (interface{}, error) { return destination.NewDestinationRef(address, backend) }) } + +// Dependencies gets a list of dependencies used to deploy the destination contract. +func (d DestinationDeployer) Dependencies() []deployer.ContractType { + return []deployer.ContractType{OriginType, NotaryManagerType} +} diff --git a/core/types/attestation.go b/core/types/attestation.go index 4983836997..09bd5f1cb5 100644 --- a/core/types/attestation.go +++ b/core/types/attestation.go @@ -19,7 +19,7 @@ type attestation struct { root [32]byte } -// NewAttestation creates a new attesation. +// NewAttestation creates a new attestation. func NewAttestation(localDomain, nonce uint32, root [32]byte) Attestation { return attestation{ domain: localDomain, diff --git a/core/types/attestation_submitted.go b/core/types/attestation_submitted.go new file mode 100644 index 0000000000..83e3109c5f --- /dev/null +++ b/core/types/attestation_submitted.go @@ -0,0 +1,35 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/common" +) + +// AttestationSubmitted is the type emitted by +// the AttestationCollector when an attestation is submitted. +type AttestationSubmitted interface { + // Notary gets the notary of the attestation. + Notary() common.Hash + // Attestation gets the raw bytes of the attestation. + Attestation() []byte +} + +type attestationSubmitted struct { + notary common.Hash + attestation []byte +} + +// NewAttestationSubmitted creates a new attestation submitted type. +func NewAttestationSubmitted(notary common.Hash, attestation []byte) AttestationSubmitted { + return attestationSubmitted{ + notary: notary, + attestation: attestation, + } +} + +func (a attestationSubmitted) Notary() common.Hash { + return a.notary +} + +func (a attestationSubmitted) Attestation() []byte { + return a.attestation +} diff --git a/core/types/message.go b/core/types/message.go index 3494e87159..6e5f0ee858 100644 --- a/core/types/message.go +++ b/core/types/message.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/gob" "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" )