diff --git a/dapps/valuetransfers/packages/tangle/errors.go b/dapps/valuetransfers/packages/tangle/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..3ada3b8ae0f57de974ed9bcc4a11012a6db3b5c4 --- /dev/null +++ b/dapps/valuetransfers/packages/tangle/errors.go @@ -0,0 +1,14 @@ +package tangle + +import "errors" + +var ( + // ErrFatal represents an error that is not "expected". + ErrFatal = errors.New("fatal error") + + // ErrTransactionInvalid represents an error type that is triggered when an invalid transaction is detected. + ErrTransactionInvalid = errors.New("transaction invalid") + + // ErrPayloadInvalid represents an error type that is triggered when an invalid payload is detected. + ErrPayloadInvalid = errors.New("payload invalid") +) diff --git a/dapps/valuetransfers/packages/tangle/events.go b/dapps/valuetransfers/packages/tangle/events.go index 8194f08f1e9d8e2fe71bc17b0627e41cd276cf84..17207555c01f1fd714c7fdef352e46fbab7cd994 100644 --- a/dapps/valuetransfers/packages/tangle/events.go +++ b/dapps/valuetransfers/packages/tangle/events.go @@ -19,7 +19,6 @@ type Events struct { MissingPayloadReceived *events.Event PayloadMissing *events.Event PayloadInvalid *events.Event - PayloadUnsolidifiable *events.Event // TransactionReceived gets triggered whenever a transaction was received for the first time (not solid yet). TransactionReceived *events.Event @@ -81,7 +80,6 @@ func newEvents() *Events { MissingPayloadReceived: events.NewEvent(cachedPayloadEvent), PayloadMissing: events.NewEvent(payloadIDEvent), PayloadInvalid: events.NewEvent(cachedPayloadErrorEvent), - PayloadUnsolidifiable: events.NewEvent(payloadIDEvent), TransactionReceived: events.NewEvent(cachedTransactionAttachmentEvent), TransactionInvalid: events.NewEvent(cachedTransactionErrorEvent), TransactionSolid: events.NewEvent(cachedTransactionEvent), diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go index 6b0fa17d077f11125bf4e75a429178b1c3b1d0fa..e87870868934de9320f5e05aba1884585b10b24d 100644 --- a/dapps/valuetransfers/packages/tangle/tangle.go +++ b/dapps/valuetransfers/packages/tangle/tangle.go @@ -36,7 +36,7 @@ type Tangle struct { outputStorage *objectstorage.ObjectStorage consumerStorage *objectstorage.ObjectStorage - Events Events + Events *Events workerPool async.WorkerPool } @@ -58,7 +58,7 @@ func New(store kvstore.KVStore) (tangle *Tangle) { outputStorage: osFactory.New(osOutput, osOutputFactory, OutputKeyPartitions, objectstorage.CacheTime(cacheTime), osLeakDetectionOption), consumerStorage: osFactory.New(osConsumer, osConsumerFactory, ConsumerPartitionKeys, objectstorage.CacheTime(cacheTime), osLeakDetectionOption), - Events: *newEvents(), + Events: newEvents(), } tangle.setupDAGSynchronization() @@ -194,13 +194,13 @@ func (tangle *Tangle) Fork(transactionID transaction.ID, conflictingInputs []tra tx := cachedTransaction.Unwrap() if tx == nil { - err = fmt.Errorf("failed to load transaction '%s'", transactionID) + err = fmt.Errorf("failed to load transaction '%s': %w", transactionID, ErrFatal) return } txMetadata := cachedTransactionMetadata.Unwrap() if txMetadata == nil { - err = fmt.Errorf("failed to load metadata of transaction '%s'", transactionID) + err = fmt.Errorf("failed to load metadata of transaction '%s': %w", transactionID, ErrFatal) return } @@ -235,7 +235,7 @@ func (tangle *Tangle) Fork(transactionID transaction.ID, conflictingInputs []tra } // trigger events + set result - tangle.Events.Fork.Trigger(cachedTransaction, cachedTransactionMetadata) + tangle.Events.Fork.Trigger(cachedTransaction, cachedTransactionMetadata, cachedTargetBranch, conflictingInputs) forked = true return @@ -1033,7 +1033,7 @@ func (tangle *Tangle) solidifyPayload(cachedPayload *payload.CachedPayload, cach // deleteTransactionFutureCone removes a transaction and its whole future cone from the database (including all of the // reference models). -func (tangle *Tangle) deleteTransactionFutureCone(transactionID transaction.ID) { +func (tangle *Tangle) deleteTransactionFutureCone(transactionID transaction.ID, cause error) { // initialize stack with current transaction deleteStack := list.New() deleteStack.PushBack(transactionID) @@ -1046,7 +1046,7 @@ func (tangle *Tangle) deleteTransactionFutureCone(transactionID transaction.ID) currentTransactionID := currentTransactionIDEntry.Value.(transaction.ID) // delete the transaction - consumers, attachments := tangle.deleteTransaction(currentTransactionID) + consumers, attachments := tangle.deleteTransaction(currentTransactionID, cause) // queue consumers to also be deleted for _, consumer := range consumers { @@ -1055,7 +1055,7 @@ func (tangle *Tangle) deleteTransactionFutureCone(transactionID transaction.ID) // remove payload future cone for _, attachingPayloadID := range attachments { - tangle.deletePayloadFutureCone(attachingPayloadID) + tangle.deletePayloadFutureCone(attachingPayloadID, cause) } } } @@ -1063,13 +1063,21 @@ func (tangle *Tangle) deleteTransactionFutureCone(transactionID transaction.ID) // deleteTransaction deletes a single transaction and all of its related models from the database. // Note: We do not immediately remove the attachments as this is related to the Payloads and is therefore left to the // caller to clean this up. -func (tangle *Tangle) deleteTransaction(transactionID transaction.ID) (consumers []transaction.ID, attachments []payload.ID) { +func (tangle *Tangle) deleteTransaction(transactionID transaction.ID, cause error) (consumers []transaction.ID, attachments []payload.ID) { // create result consumers = make([]transaction.ID, 0) attachments = make([]payload.ID, 0) + cachedTransaction := tangle.Transaction(transactionID) + cachedTransactionMetadata := tangle.TransactionMetadata(transactionID) + // process transaction and its models - tangle.Transaction(transactionID).Consume(func(tx *transaction.Transaction) { + cachedTransaction.Consume(func(tx *transaction.Transaction) { + // if the removal was triggered by an invalid Transaction + if errors.Is(cause, ErrTransactionInvalid) { + tangle.Events.TransactionInvalid.Trigger(cachedTransaction, cachedTransactionMetadata, cause) + } + // mark transaction as deleted tx.Delete() @@ -1107,7 +1115,9 @@ func (tangle *Tangle) deleteTransaction(transactionID transaction.ID) (consumers }) // delete transaction metadata - tangle.transactionMetadataStorage.Delete(transactionID.Bytes()) + cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { + metadata.Delete() + }) // process attachments tangle.Attachments(transactionID).Consume(func(attachment *Attachment) { @@ -1119,7 +1129,7 @@ func (tangle *Tangle) deleteTransaction(transactionID transaction.ID) (consumers // deletePayloadFutureCone removes a payload and its whole future cone from the database (including all of the reference // models). -func (tangle *Tangle) deletePayloadFutureCone(payloadID payload.ID) { +func (tangle *Tangle) deletePayloadFutureCone(payloadID payload.ID, cause error) { // initialize stack with current transaction deleteStack := list.New() deleteStack.PushBack(payloadID) @@ -1131,8 +1141,16 @@ func (tangle *Tangle) deletePayloadFutureCone(payloadID payload.ID) { deleteStack.Remove(currentTransactionIDEntry) currentPayloadID := currentTransactionIDEntry.Value.(payload.ID) + cachedPayload := tangle.Payload(currentPayloadID) + cachedPayloadMetadata := tangle.PayloadMetadata(currentPayloadID) + // process payload - tangle.Payload(currentPayloadID).Consume(func(currentPayload *payload.Payload) { + cachedPayload.Consume(func(currentPayload *payload.Payload) { + // trigger payload invalid if it was called with an "invalid cause" + if errors.Is(cause, ErrPayloadInvalid) || errors.Is(cause, ErrTransactionInvalid) { + tangle.Events.PayloadInvalid.Trigger(cachedPayload, cachedPayloadMetadata, cause) + } + // delete payload currentPayload.Delete() @@ -1147,12 +1165,14 @@ func (tangle *Tangle) deletePayloadFutureCone(payloadID payload.ID) { // if this was the last attachment of the transaction then we also delete the transaction if !tangle.Attachments(currentPayload.Transaction().ID()).Consume(func(attachment *Attachment) {}) { - tangle.deleteTransaction(currentPayload.Transaction().ID()) + tangle.deleteTransaction(currentPayload.Transaction().ID(), nil) } }) // delete payload metadata - tangle.payloadMetadataStorage.Delete(currentPayloadID.Bytes()) + cachedPayloadMetadata.Consume(func(payloadMetadata *PayloadMetadata) { + payloadMetadata.Delete() + }) // queue approvers tangle.Approvers(currentPayloadID).Consume(func(approver *PayloadApprover) { @@ -1176,9 +1196,7 @@ func (tangle *Tangle) processSolidificationStackEntry(solidificationStack *list. // abort if the transaction is not solid or invalid transactionSolid, consumedBranches, transactionSolidityErr := tangle.checkTransactionSolidity(currentTransaction, currentTransactionMetadata) if transactionSolidityErr != nil { - tangle.Events.TransactionInvalid.Trigger(solidificationStackEntry.CachedTransaction, solidificationStackEntry.CachedTransactionMetadata, transactionSolidityErr) - - tangle.deleteTransactionFutureCone(currentTransaction.ID()) + tangle.deleteTransactionFutureCone(currentTransaction.ID(), transactionSolidityErr) return } @@ -1189,9 +1207,7 @@ func (tangle *Tangle) processSolidificationStackEntry(solidificationStack *list. // abort if the payload is not solid or invalid payloadSolid, payloadSolidityErr := tangle.payloadBecameNewlySolid(currentPayload, currentPayloadMetadata, consumedBranches) if payloadSolidityErr != nil { - tangle.Events.PayloadInvalid.Trigger(solidificationStackEntry.CachedPayload, solidificationStackEntry.CachedPayloadMetadata, payloadSolidityErr) - - tangle.deletePayloadFutureCone(currentPayload.ID()) + tangle.deletePayloadFutureCone(currentPayload.ID(), payloadSolidityErr) return } @@ -1275,7 +1291,7 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans // abort if the output could not be found output := cachedOutput.Unwrap() if output == nil { - err = fmt.Errorf("could not load output '%s'", outputID) + err = fmt.Errorf("could not load output '%s': %w", outputID, ErrFatal) return false } @@ -1493,7 +1509,7 @@ func (tangle *Tangle) payloadBecameNewlySolid(p *payload.Payload, payloadMetadat return } if branchesConflicting { - err = fmt.Errorf("the payload '%s' combines conflicting versions of the ledger state", p.ID()) + err = fmt.Errorf("the payload '%s' combines conflicting versions of the ledger state: %w", p.ID(), ErrPayloadInvalid) return false, err } @@ -1527,7 +1543,7 @@ func (tangle *Tangle) checkTransactionSolidity(tx *transaction.Transaction, meta // abort if the outputs are not matching the inputs if !tangle.checkTransactionOutputs(consumedBalances, tx.Outputs()) { - err = fmt.Errorf("the outputs do not match the inputs in transaction with id '%s'", tx.ID()) + err = fmt.Errorf("the outputs do not match the inputs in transaction with id '%s': %w", tx.ID(), ErrTransactionInvalid) return } @@ -1539,7 +1555,7 @@ func (tangle *Tangle) checkTransactionSolidity(tx *transaction.Transaction, meta return } if branchesConflicting { - err = fmt.Errorf("the transaction '%s' spends conflicting inputs", tx.ID()) + err = fmt.Errorf("the transaction '%s' spends conflicting inputs: %w", tx.ID(), ErrTransactionInvalid) return } @@ -1582,7 +1598,7 @@ func (tangle *Tangle) retrieveConsumedInputDetails(tx *transaction.Transaction) // check overflows in the numbers if inputBalance.Value() > math.MaxInt64-currentBalance { // TODO: make it an explicit error var - err = fmt.Errorf("buffer overflow in balances of inputs") + err = fmt.Errorf("buffer overflow in balances of inputs: %w", ErrTransactionInvalid) cachedInputs.Release() @@ -1783,7 +1799,7 @@ func (tangle *Tangle) moveTransactionToBranch(cachedTransaction *transaction.Cac // unwrap output output := cachedOutput.Unwrap() if output == nil { - err = fmt.Errorf("failed to load output '%s'", outputID) + err = fmt.Errorf("failed to load output '%s': %w", outputID, ErrFatal) return false } @@ -1905,7 +1921,7 @@ func (tangle *Tangle) calculateBranchOfTransaction(currentTransaction *transacti transactionOutput := cachedTransactionOutput.Unwrap() if transactionOutput == nil { - err = fmt.Errorf("failed to load output '%s'", outputId) + err = fmt.Errorf("failed to load output '%s': %w", outputId, ErrFatal) return false } diff --git a/dapps/valuetransfers/packages/tangle/tangle_event_test.go b/dapps/valuetransfers/packages/tangle/tangle_event_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bdd3f1dfc42386262c342e644c8d298852563958 --- /dev/null +++ b/dapps/valuetransfers/packages/tangle/tangle_event_test.go @@ -0,0 +1,211 @@ +package tangle + +import ( + "reflect" + "testing" + + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" + "github.com/iotaledger/hive.go/events" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// eventTangle is a wrapper around Tangle used to test the triggered events. +type eventTangle struct { + mock.Mock + *Tangle + + attached []struct { + *events.Event + *events.Closure + } +} + +func newEventTangle(t *testing.T, tangle *Tangle) *eventTangle { + e := &eventTangle{Tangle: tangle} + e.Test(t) + + // attach all events + e.attach(tangle.Events.PayloadAttached, e.PayloadAttached) + e.attach(tangle.Events.PayloadSolid, e.PayloadSolid) + e.attach(tangle.Events.PayloadLiked, e.PayloadLiked) + e.attach(tangle.Events.PayloadConfirmed, e.PayloadConfirmed) + e.attach(tangle.Events.PayloadRejected, e.PayloadRejected) + e.attach(tangle.Events.PayloadDisliked, e.PayloadDisliked) + e.attach(tangle.Events.MissingPayloadReceived, e.MissingPayloadReceived) + e.attach(tangle.Events.PayloadMissing, e.PayloadMissing) + e.attach(tangle.Events.PayloadInvalid, e.PayloadInvalid) + e.attach(tangle.Events.TransactionReceived, e.TransactionReceived) + e.attach(tangle.Events.TransactionInvalid, e.TransactionInvalid) + e.attach(tangle.Events.TransactionSolid, e.TransactionSolid) + e.attach(tangle.Events.TransactionBooked, e.TransactionBooked) + e.attach(tangle.Events.TransactionPreferred, e.TransactionPreferred) + e.attach(tangle.Events.TransactionUnpreferred, e.TransactionUnpreferred) + e.attach(tangle.Events.TransactionLiked, e.TransactionLiked) + e.attach(tangle.Events.TransactionDisliked, e.TransactionDisliked) + e.attach(tangle.Events.TransactionFinalized, e.TransactionFinalized) + e.attach(tangle.Events.TransactionConfirmed, e.TransactionConfirmed) + e.attach(tangle.Events.TransactionRejected, e.TransactionRejected) + e.attach(tangle.Events.Fork, e.Fork) + e.attach(tangle.Events.Error, e.Error) + + // assure that all available events are mocked + numEvents := reflect.ValueOf(tangle.Events).Elem().NumField() + assert.Equalf(t, len(e.attached), numEvents, "not all events in Tangle.Events have been attached") + + return e +} + +// DetachAll detaches all attached event mocks. +func (e *eventTangle) DetachAll() { + for _, a := range e.attached { + a.Event.Detach(a.Closure) + } +} + +func (e *eventTangle) attach(event *events.Event, f interface{}) { + closure := events.NewClosure(f) + event.Attach(closure) + e.attached = append(e.attached, struct { + *events.Event + *events.Closure + }{event, closure}) +} + +// Expect starts a description of an expectation of the specified event being triggered. +func (e *eventTangle) Expect(eventName string, arguments ...interface{}) { + e.On(eventName, arguments...).Once() +} + +func (e *eventTangle) PayloadAttached(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap()) +} + +func (e *eventTangle) PayloadSolid(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap()) +} + +func (e *eventTangle) PayloadLiked(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap()) +} + +func (e *eventTangle) PayloadConfirmed(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap()) +} + +func (e *eventTangle) PayloadRejected(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap()) +} + +func (e *eventTangle) PayloadDisliked(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap()) +} + +func (e *eventTangle) MissingPayloadReceived(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap()) +} + +func (e *eventTangle) PayloadMissing(id payload.ID) { + e.Called(id) +} + +func (e *eventTangle) PayloadInvalid(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata, err error) { + defer payload.Release() + defer payloadMetadata.Release() + e.Called(payload.Unwrap(), payloadMetadata.Unwrap(), err) +} + +func (e *eventTangle) TransactionReceived(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, attachment *CachedAttachment) { + defer transaction.Release() + defer transactionMetadata.Release() + defer attachment.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap(), attachment.Unwrap()) +} + +func (e *eventTangle) TransactionInvalid(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, err error) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap(), err) +} + +func (e *eventTangle) TransactionSolid(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) TransactionBooked(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, decisionPending bool) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap(), decisionPending) +} + +func (e *eventTangle) TransactionPreferred(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) TransactionUnpreferred(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) TransactionLiked(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) TransactionDisliked(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) TransactionFinalized(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) TransactionConfirmed(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) TransactionRejected(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) { + defer transaction.Release() + defer transactionMetadata.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap()) +} + +func (e *eventTangle) Fork(transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, branch *branchmanager.CachedBranch, outputIDs []transaction.OutputID) { + defer transaction.Release() + defer transactionMetadata.Release() + defer branch.Release() + e.Called(transaction.Unwrap(), transactionMetadata.Unwrap(), branch.Unwrap(), outputIDs) +} + +// TODO: Error is never tested +func (e *eventTangle) Error(err error) { + e.Called(err) +} diff --git a/dapps/valuetransfers/packages/tangle/tangle_scenario_test.go b/dapps/valuetransfers/packages/tangle/tangle_scenario_test.go index 63a8889e3eb4bd02e0a2a7de51cf75599643f3a3..5d185f9637fef1a7bef59f8e8b69138936f2ac37 100644 --- a/dapps/valuetransfers/packages/tangle/tangle_scenario_test.go +++ b/dapps/valuetransfers/packages/tangle/tangle_scenario_test.go @@ -12,6 +12,7 @@ import ( "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" "github.com/iotaledger/hive.go/kvstore/mapdb" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -33,9 +34,9 @@ const ( // TODO: clean up create scenario with some helper functions: DRY! // preparePropagationScenario1 creates a tangle according to `img/scenario1.png`. -func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction.Transaction, map[string]*payload.Payload, map[string]branchmanager.BranchID, *wallet.Seed) { +func preparePropagationScenario1(t *testing.T) (*eventTangle, map[string]*transaction.Transaction, map[string]*payload.Payload, map[string]branchmanager.BranchID, *wallet.Seed) { // create tangle - tangle := New(mapdb.NewMapDB()) + tangle := newEventTangle(t, New(mapdb.NewMapDB())) // create seed for testing seed := wallet.NewSeed() @@ -80,6 +81,12 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-GENESIS, A+, B+, C+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything, true) + // attach payload tangle.AttachPayloadSync(valueObjects["[-GENESIS, A+, B+, C+]"]) @@ -148,6 +155,12 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-A, D+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-A, D+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-A, D+]"], mock.Anything, true) + // attach payload tangle.AttachPayloadSync(valueObjects["[-A, D+]"]) @@ -202,6 +215,12 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-B, -C, E+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-B, -C, E+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-B, -C, E+]"], mock.Anything, true) + // attach payload tangle.AttachPayloadSync(valueObjects["[-B, -C, E+]"]) @@ -247,6 +266,9 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // create payload valueObjects["[-B, -C, E+] (Reattachment)"] = payload.New(valueObjects["[-B, -C, E+]"].ID(), valueObjects["[-GENESIS, A+, B+, C+]"].ID(), transactions["[-B, -C, E+]"]) + tangle.Expect("PayloadAttached", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + // attach payload tangle.AttachPayloadSync(valueObjects["[-B, -C, E+] (Reattachment)"]) @@ -290,9 +312,10 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // [-A, F+] { // create transaction + payload + outputA := transaction.NewOutputID(seed.Address(A), transactions["[-GENESIS, A+, B+, C+]"].ID()) transactions["[-A, F+]"] = transaction.New( transaction.NewInputs( - transaction.NewOutputID(seed.Address(A), transactions["[-GENESIS, A+, B+, C+]"].ID()), + outputA, ), transaction.NewOutputs(map[address.Address][]*balance.Balance{ @@ -307,6 +330,13 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-A, F+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-A, F+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-A, F+]"], mock.Anything, true) + tangle.Expect("Fork", transactions["[-A, D+]"], mock.Anything, mock.Anything, []transaction.OutputID{outputA}) + // attach payload tangle.AttachPayloadSync(valueObjects["[-A, F+]"]) @@ -384,6 +414,12 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-E, -F, G+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-E, -F, G+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-E, -F, G+]"], mock.Anything, true) + // attach payload tangle.AttachPayloadSync(valueObjects["[-E, -F, G+]"]) @@ -446,6 +482,11 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-F, -D, Y+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-F, -D, Y+]"], mock.Anything) + tangle.Expect("PayloadInvalid", valueObjects["[-F, -D, Y+]"], mock.Anything, mock.MatchedBy(func(err error) bool { return assert.Error(t, err) })) + tangle.Expect("TransactionReceived", transactions["[-F, -D, Y+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionInvalid", transactions["[-F, -D, Y+]"], mock.Anything, mock.MatchedBy(func(err error) bool { return assert.Error(t, err) })) + // attach payload tangle.AttachPayloadSync(valueObjects["[-F, -D, Y+]"]) @@ -469,6 +510,9 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction { valueObjects["[-B, -C, E+] (2nd Reattachment)"] = payload.New(valueObjects["[-A, F+]"].ID(), valueObjects["[-A, D+]"].ID(), transactions["[-B, -C, E+]"]) + tangle.Expect("PayloadAttached", valueObjects["[-B, -C, E+] (2nd Reattachment)"], mock.Anything) + tangle.Expect("PayloadInvalid", valueObjects["[-B, -C, E+] (2nd Reattachment)"], mock.Anything, mock.MatchedBy(func(err error) bool { return assert.Error(t, err) })) + // attach payload tangle.AttachPayloadSync(valueObjects["[-B, -C, E+] (2nd Reattachment)"]) @@ -498,15 +542,16 @@ func preparePropagationScenario1(t *testing.T) (*Tangle, map[string]*transaction } // preparePropagationScenario1 creates a tangle according to `img/scenario2.png`. -func preparePropagationScenario2(t *testing.T) (*Tangle, map[string]*transaction.Transaction, map[string]*payload.Payload, map[string]branchmanager.BranchID, *wallet.Seed) { +func preparePropagationScenario2(t *testing.T) (*eventTangle, map[string]*transaction.Transaction, map[string]*payload.Payload, map[string]branchmanager.BranchID, *wallet.Seed) { tangle, transactions, valueObjects, branches, seed := preparePropagationScenario1(t) // [-C, H+] { // create transaction + payload + outputC := transaction.NewOutputID(seed.Address(C), transactions["[-GENESIS, A+, B+, C+]"].ID()) transactions["[-C, H+]"] = transaction.New( transaction.NewInputs( - transaction.NewOutputID(seed.Address(C), transactions["[-GENESIS, A+, B+, C+]"].ID()), + outputC, ), transaction.NewOutputs(map[address.Address][]*balance.Balance{ @@ -521,6 +566,13 @@ func preparePropagationScenario2(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-C, H+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-C, H+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-C, H+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-C, H+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-C, H+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-C, H+]"], mock.Anything, true) + tangle.Expect("Fork", transactions["[-B, -C, E+]"], mock.Anything, mock.Anything, []transaction.OutputID{outputC}) + // attach payload tangle.AttachPayloadSync(valueObjects["[-C, H+]"]) @@ -620,6 +672,12 @@ func preparePropagationScenario2(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-H, -D, I+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-H, -D, I+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-H, -D, I+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-H, -D, I+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-H, -D, I+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-H, -D, I+]"], mock.Anything, true) + // attach payload tangle.AttachPayloadSync(valueObjects["[-H, -D, I+]"]) @@ -683,6 +741,12 @@ func preparePropagationScenario2(t *testing.T) (*Tangle, map[string]*transaction // check if signatures are valid assert.True(t, transactions["[-B, J+]"].SignaturesValid()) + tangle.Expect("PayloadAttached", valueObjects["[-B, J+]"], mock.Anything) + tangle.Expect("PayloadSolid", valueObjects["[-B, J+]"], mock.Anything) + tangle.Expect("TransactionReceived", transactions["[-B, J+]"], mock.Anything, mock.Anything) + tangle.Expect("TransactionSolid", transactions["[-B, J+]"], mock.Anything) + tangle.Expect("TransactionBooked", transactions["[-B, J+]"], mock.Anything, true) + // attach payload tangle.AttachPayloadSync(valueObjects["[-B, J+]"]) @@ -736,6 +800,7 @@ func TestPropagationScenario1(t *testing.T) { // test past cone monotonicity - all value objects MUST be confirmed { tangle, transactions, valueObjects, _, _ := preparePropagationScenario1(t) + defer tangle.DetachAll() // initialize debugger for this test debugger.ResetAliases() @@ -746,42 +811,79 @@ func TestPropagationScenario1(t *testing.T) { debugger.RegisterAlias(tx.ID(), "TransactionID"+name) } + // preferring [-GENESIS, A+, B+, C+] will get it liked + tangle.Expect("TransactionPreferred", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadLiked", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + setTransactionPreferredWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"], true) verifyInclusionState(t, tangle, valueObjects["[-GENESIS, A+, B+, C+]"], true, false, true, false, false) - // should not be confirmed because [-GENESIS, A+, B+, C+] is not confirmed + // finalizing [-B, -C, E+] will not get it confirmed, as [-GENESIS, A+, B+, C+] is not yet confirmed + tangle.Expect("TransactionPreferred", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + setTransactionPreferredWithCheck(t, tangle, transactions["[-B, -C, E+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-B, -C, E+]"]) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+]"], true, true, true, false, false) - // now finalize [-GENESIS, A+, B+, C+] + // finalize [-GENESIS, A+, B+, C+] to also get [-B, -C, E+] as well as [-B, -C, E+] (Reattachment) confirmed + tangle.Expect("TransactionFinalized", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + setTransactionFinalizedWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"]) verifyInclusionState(t, tangle, valueObjects["[-GENESIS, A+, B+, C+]"], true, true, true, true, false) - - // and [-B, -C, E+] should be confirmed now too verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+]"], true, true, true, true, false) - // as well as the reattachment verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+] (Reattachment)"], true, true, true, true, false) + + tangle.AssertExpectations(t) } // test future cone monotonicity simple - everything MUST be rejected and finalized if spending funds from rejected tx { tangle, transactions, valueObjects, _, _ := preparePropagationScenario1(t) + defer tangle.DetachAll() + + // finalizing [-GENESIS, A+, B+, C+] will get the entire future cone finalized and rejected + tangle.Expect("TransactionFinalized", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-E, -F, G+]"], mock.Anything) setTransactionFinalizedWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"]) verifyInclusionState(t, tangle, valueObjects["[-GENESIS, A+, B+, C+]"], false, true, false, false, true) - - // check future cone to be rejected verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+]"], false, true, false, false, true) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+] (Reattachment)"], false, true, false, false, true) verifyInclusionState(t, tangle, valueObjects["[-A, D+]"], false, true, false, false, true) verifyInclusionState(t, tangle, valueObjects["[-A, F+]"], false, true, false, false, true) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], false, true, false, false, true) + + tangle.AssertExpectations(t) } // test future cone monotonicity more complex - everything MUST be rejected and finalized if spending funds from rejected tx { tangle, transactions, valueObjects, branches, _ := preparePropagationScenario1(t) + defer tangle.DetachAll() // initialize debugger for this test debugger.ResetAliases() @@ -792,10 +894,26 @@ func TestPropagationScenario1(t *testing.T) { debugger.RegisterAlias(tx.ID(), "TransactionID"+name) } + tangle.Expect("TransactionPreferred", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadLiked", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + setTransactionPreferredWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"]) verifyInclusionState(t, tangle, valueObjects["[-GENESIS, A+, B+, C+]"], true, true, true, true, false) + tangle.Expect("PayloadRejected", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-E, -F, G+]"], mock.Anything) + // finalize & reject //debugger.Enable() setTransactionFinalizedWithCheck(t, tangle, transactions["[-B, -C, E+]"]) @@ -816,11 +934,21 @@ func TestPropagationScenario1(t *testing.T) { // [-A, D+] should be unchanged verifyInclusionState(t, tangle, valueObjects["[-A, D+]"], false, false, false, false, false) verifyBranchState(t, tangle, branches["A"], false, false, false, false) + + tangle.AssertExpectations(t) } // simulate vote on [-A, F+] -> Branch A becomes rejected, Branch B confirmed { tangle, transactions, valueObjects, branches, _ := preparePropagationScenario1(t) + defer tangle.DetachAll() + + tangle.Expect("PayloadLiked", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) setTransactionPreferredWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"]) @@ -833,55 +961,135 @@ func TestPropagationScenario1(t *testing.T) { verifyInclusionState(t, tangle, valueObjects["[-A, F+]"], false, false, false, false, false) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], false, false, false, false, false) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-B, -C, E+]"], mock.Anything) + // confirm [-B, -C, E+] setTransactionPreferredWithCheck(t, tangle, transactions["[-B, -C, E+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-B, -C, E+]"]) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+]"], true, true, true, true, false) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+] (Reattachment)"], true, true, true, true, false) + tangle.Expect("PayloadLiked", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-A, D+]"], mock.Anything) + // prefer [-A, D+] setTransactionPreferredWithCheck(t, tangle, transactions["[-A, D+]"], true) verifyInclusionState(t, tangle, valueObjects["[-A, D+]"], true, false, true, false, false) verifyBranchState(t, tangle, branches["A"], false, true, false, false) + tangle.Expect("PayloadLiked", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("PayloadDisliked", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionUnpreferred", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionDisliked", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-A, D+]"], mock.Anything) + // simulate vote result to like [-A, F+] -> [-A, F+] becomes confirmed and [-A, D+] rejected setTransactionPreferredWithCheck(t, tangle, transactions["[-A, F+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-A, F+]"]) verifyInclusionState(t, tangle, valueObjects["[-A, F+]"], true, true, true, true, false) verifyBranchState(t, tangle, branches["B"], true, true, true, false) + // [-A, D+] should be rejected + verifyInclusionState(t, tangle, valueObjects["[-A, D+]"], false, true, false, false, true) + verifyBranchState(t, tangle, branches["A"], true, false, false, true) + + tangle.Expect("PayloadLiked", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-E, -F, G+]"], mock.Anything) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], false, false, false, false, false) setTransactionPreferredWithCheck(t, tangle, transactions["[-E, -F, G+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-E, -F, G+]"]) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], true, true, true, true, false) - // [-A, D+] should be rejected - verifyInclusionState(t, tangle, valueObjects["[-A, D+]"], false, true, false, false, true) - verifyBranchState(t, tangle, branches["A"], true, false, false, true) + tangle.AssertExpectations(t) } // simulate vote on [-A, D+] -> Branch B becomes rejected, Branch A confirmed { tangle, transactions, valueObjects, branches, _ := preparePropagationScenario1(t) + defer tangle.DetachAll() + + tangle.Expect("PayloadLiked", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) // confirm [-GENESIS, A+, B+, C+] setTransactionPreferredWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"]) verifyInclusionState(t, tangle, valueObjects["[-GENESIS, A+, B+, C+]"], true, true, true, true, false) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-B, -C, E+]"], mock.Anything) + // confirm [-B, -C, E+] setTransactionPreferredWithCheck(t, tangle, transactions["[-B, -C, E+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-B, -C, E+]"]) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+]"], true, true, true, true, false) + tangle.Expect("PayloadLiked", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-A, F+]"], mock.Anything) + // prefer [-A, F+] and thus Branch B setTransactionPreferredWithCheck(t, tangle, transactions["[-A, F+]"], true) verifyInclusionState(t, tangle, valueObjects["[-A, F+]"], true, false, true, false, false) verifyBranchState(t, tangle, branches["B"], false, true, false, false) + + tangle.Expect("PayloadLiked", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-E, -F, G+]"], mock.Anything) + // prefer [-E, -F, G+] setTransactionPreferredWithCheck(t, tangle, transactions["[-E, -F, G+]"], true) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], true, false, true, false, false) + tangle.Expect("PayloadLiked", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-A, D+]"], mock.Anything) + + tangle.Expect("PayloadRejected", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadDisliked", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionUnpreferred", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionDisliked", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("PayloadDisliked", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionUnpreferred", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionDisliked", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-E, -F, G+]"], mock.Anything) + // simulate vote result to like [-A, D+] -> [-A, D+] becomes confirmed and [-A, F+], [-E, -F, G+] rejected setTransactionPreferredWithCheck(t, tangle, transactions["[-A, D+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-A, D+]"]) @@ -892,12 +1100,15 @@ func TestPropagationScenario1(t *testing.T) { verifyInclusionState(t, tangle, valueObjects["[-A, F+]"], false, true, false, false, true) verifyBranchState(t, tangle, branches["B"], true, false, false, true) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], false, true, false, false, true) + + tangle.AssertExpectations(t) } } func TestPropagationScenario2(t *testing.T) { // img/scenario2.png tangle, transactions, valueObjects, branches, _ := preparePropagationScenario2(t) + defer tangle.DetachAll() // initialize debugger for this test debugger.ResetAliases() @@ -908,22 +1119,42 @@ func TestPropagationScenario2(t *testing.T) { debugger.RegisterAlias(tx.ID(), "TransactionID"+name) } + tangle.Expect("PayloadLiked", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-GENESIS, A+, B+, C+]"], mock.Anything) + // confirm [-GENESIS, A+, B+, C+] setTransactionPreferredWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"]) verifyInclusionState(t, tangle, valueObjects["[-GENESIS, A+, B+, C+]"], true, true, true, true, false) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadLiked", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-B, -C, E+]"], mock.Anything) + // prefer [-B, -C, E+] and thus Branch D setTransactionPreferredWithCheck(t, tangle, transactions["[-B, -C, E+]"], true) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+]"], true, false, true, false, false) verifyBranchState(t, tangle, branches["D"], false, true, false, false) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+] (Reattachment)"], true, false, true, false, false) + tangle.Expect("PayloadLiked", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-A, F+]"], mock.Anything) + // prefer [-A, F+] and thus Branch B setTransactionPreferredWithCheck(t, tangle, transactions["[-A, F+]"], true) verifyInclusionState(t, tangle, valueObjects["[-A, F+]"], true, false, true, false, false) verifyBranchState(t, tangle, branches["B"], false, true, false, false) + tangle.Expect("PayloadLiked", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-E, -F, G+]"], mock.Anything) + // prefer [-E, -F, G+] setTransactionPreferredWithCheck(t, tangle, transactions["[-E, -F, G+]"], true) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], true, false, true, false, false) @@ -945,10 +1176,34 @@ func TestPropagationScenario2(t *testing.T) { verifyBranchState(t, tangle, branches["E"], false, false, false, false) verifyBranchState(t, tangle, branches["ACE"], false, false, false, false) + tangle.Expect("PayloadLiked", valueObjects["[-H, -D, I+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-H, -D, I+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-H, -D, I+]"], mock.Anything) + // prefer [-H, -D, I+] - should be liked after votes on [-A, D+] and [-C, H+] setTransactionPreferredWithCheck(t, tangle, transactions["[-H, -D, I+]"], true) verifyInclusionState(t, tangle, valueObjects["[-H, -D, I+]"], true, false, false, false, false) + tangle.Expect("PayloadLiked", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, D+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-A, D+]"], mock.Anything) + + tangle.Expect("PayloadRejected", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadDisliked", valueObjects["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionUnpreferred", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionDisliked", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-A, F+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("PayloadDisliked", valueObjects["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionUnpreferred", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionDisliked", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-E, -F, G+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-E, -F, G+]"], mock.Anything) + // simulate vote result to like [-A, D+] -> [-A, D+] becomes confirmed and [-A, F+], [-E, -F, G+] rejected setTransactionPreferredWithCheck(t, tangle, transactions["[-A, D+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-A, D+]"]) @@ -959,6 +1214,22 @@ func TestPropagationScenario2(t *testing.T) { verifyBranchState(t, tangle, branches["B"], true, false, false, true) verifyInclusionState(t, tangle, valueObjects["[-E, -F, G+]"], false, true, false, false, true) + tangle.Expect("PayloadLiked", valueObjects["[-C, H+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-C, H+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-C, H+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-C, H+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-C, H+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-C, H+]"], mock.Anything) + + tangle.Expect("PayloadRejected", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadDisliked", valueObjects["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionUnpreferred", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionDisliked", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("TransactionRejected", transactions["[-B, -C, E+]"], mock.Anything) + tangle.Expect("PayloadRejected", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + tangle.Expect("PayloadDisliked", valueObjects["[-B, -C, E+] (Reattachment)"], mock.Anything) + // simulate vote result to like [-C, H+] -> [-C, H+] becomes confirmed and [-B, -C, E+], [-B, -C, E+] (Reattachment) rejected setTransactionPreferredWithCheck(t, tangle, transactions["[-C, H+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-C, H+]"]) @@ -973,23 +1244,37 @@ func TestPropagationScenario2(t *testing.T) { verifyBranchState(t, tangle, branches["BD"], true, false, false, true) // TODO: BD is not finalized + // [-H, -D, I+] is already preferred + tangle.Expect("PayloadConfirmed", valueObjects["[-H, -D, I+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-H, -D, I+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-H, -D, I+]"], mock.Anything) + // [-H, -D, I+] should now be liked verifyInclusionState(t, tangle, valueObjects["[-H, -D, I+]"], true, false, true, false, false) setTransactionFinalizedWithCheck(t, tangle, transactions["[-H, -D, I+]"]) verifyInclusionState(t, tangle, valueObjects["[-H, -D, I+]"], true, true, true, true, false) - // [-B, J+] should be unchanged verifyInclusionState(t, tangle, valueObjects["[-B, J+]"], false, false, false, false, false) + + tangle.Expect("PayloadLiked", valueObjects["[-B, J+]"], mock.Anything) + tangle.Expect("PayloadConfirmed", valueObjects["[-B, J+]"], mock.Anything) + tangle.Expect("TransactionPreferred", transactions["[-B, J+]"], mock.Anything) + tangle.Expect("TransactionLiked", transactions["[-B, J+]"], mock.Anything) + tangle.Expect("TransactionFinalized", transactions["[-B, J+]"], mock.Anything) + tangle.Expect("TransactionConfirmed", transactions["[-B, J+]"], mock.Anything) + // [-B, J+] should become confirmed after preferring and finalizing setTransactionPreferredWithCheck(t, tangle, transactions["[-B, J+]"], true) setTransactionFinalizedWithCheck(t, tangle, transactions["[-B, J+]"]) verifyInclusionState(t, tangle, valueObjects["[-B, J+]"], true, true, true, true, false) verifyBranchState(t, tangle, branches["E"], true, true, true, false) verifyBranchState(t, tangle, branches["ACE"], true, true, true, false) + + tangle.AssertExpectations(t) } // verifyBranchState verifies the the branch state according to the given parameters. -func verifyBranchState(t *testing.T, tangle *Tangle, id branchmanager.BranchID, finalized, liked, confirmed, rejected bool) { +func verifyBranchState(t *testing.T, tangle *eventTangle, id branchmanager.BranchID, finalized, liked, confirmed, rejected bool) { assert.True(t, tangle.branchManager.Branch(id).Consume(func(branch *branchmanager.Branch) { assert.Equalf(t, finalized, branch.Finalized(), "branch finalized state does not match") assert.Equalf(t, liked, branch.Liked(), "branch liked state does not match") @@ -1000,7 +1285,7 @@ func verifyBranchState(t *testing.T, tangle *Tangle, id branchmanager.BranchID, } // verifyInclusionState verifies the inclusion state of outputs and transaction according to the given parameters. -func verifyTransactionInclusionState(t *testing.T, tangle *Tangle, valueObject *payload.Payload, preferred, finalized, liked, confirmed, rejected bool) { +func verifyTransactionInclusionState(t *testing.T, tangle *eventTangle, valueObject *payload.Payload, preferred, finalized, liked, confirmed, rejected bool) { tx := valueObject.Transaction() // check outputs @@ -1025,7 +1310,7 @@ func verifyTransactionInclusionState(t *testing.T, tangle *Tangle, valueObject * } // verifyValueObjectInclusionState verifies the inclusion state of a value object according to the given parameters. -func verifyValueObjectInclusionState(t *testing.T, tangle *Tangle, valueObject *payload.Payload, liked, confirmed, rejected bool) { +func verifyValueObjectInclusionState(t *testing.T, tangle *eventTangle, valueObject *payload.Payload, liked, confirmed, rejected bool) { assert.True(t, tangle.PayloadMetadata(valueObject.ID()).Consume(func(payloadMetadata *PayloadMetadata) { assert.Equalf(t, liked, payloadMetadata.Liked(), "value object liked state does not match") assert.Equalf(t, confirmed, payloadMetadata.Confirmed(), "value object confirmed state does not match") @@ -1034,20 +1319,20 @@ func verifyValueObjectInclusionState(t *testing.T, tangle *Tangle, valueObject * } // verifyInclusionState verifies the inclusion state of outputs, transaction and value object according to the given parameters. -func verifyInclusionState(t *testing.T, tangle *Tangle, valueObject *payload.Payload, preferred, finalized, liked, confirmed, rejected bool) { +func verifyInclusionState(t *testing.T, tangle *eventTangle, valueObject *payload.Payload, preferred, finalized, liked, confirmed, rejected bool) { verifyTransactionInclusionState(t, tangle, valueObject, preferred, finalized, liked, confirmed, rejected) verifyValueObjectInclusionState(t, tangle, valueObject, liked, confirmed, rejected) } // setTransactionPreferredWithCheck sets the transaction to preferred and makes sure that no error occurred and it's modified. -func setTransactionPreferredWithCheck(t *testing.T, tangle *Tangle, tx *transaction.Transaction, preferred bool) { +func setTransactionPreferredWithCheck(t *testing.T, tangle *eventTangle, tx *transaction.Transaction, preferred bool) { modified, err := tangle.SetTransactionPreferred(tx.ID(), preferred) require.NoError(t, err) assert.True(t, modified) } // setTransactionFinalizedWithCheck sets the transaction to finalized and makes sure that no error occurred and it's modified. -func setTransactionFinalizedWithCheck(t *testing.T, tangle *Tangle, tx *transaction.Transaction) { +func setTransactionFinalizedWithCheck(t *testing.T, tangle *eventTangle, tx *transaction.Transaction) { modified, err := tangle.SetTransactionFinalized(tx.ID()) require.NoError(t, err) assert.True(t, modified) diff --git a/dapps/valuetransfers/packages/tangle/tangle_test.go b/dapps/valuetransfers/packages/tangle/tangle_test.go index 0849dc3ff9a58ea3f837bb8b455b941a5878b99b..b00f07ee80c5240ef88c693706faad102c72d32c 100644 --- a/dapps/valuetransfers/packages/tangle/tangle_test.go +++ b/dapps/valuetransfers/packages/tangle/tangle_test.go @@ -5,30 +5,34 @@ import ( "math" "testing" - "github.com/google/go-cmp/cmp" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" - - "github.com/iotaledger/hive.go/events" "github.com/iotaledger/hive.go/kvstore/mapdb" "github.com/iotaledger/hive.go/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestSetTransactionPreferred(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + tx := createDummyTransaction() valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx) tangle.storeTransactionModels(valueObject) + event.Expect("TransactionPreferred", tx, mock.Anything) + modified, err := tangle.SetTransactionPreferred(tx.ID(), true) require.NoError(t, err) assert.True(t, modified) + + event.AssertExpectations(t) } // TestBookTransaction tests the following cases: @@ -41,19 +45,32 @@ func TestBookTransaction(t *testing.T) { // CASE: missing output t.Run("CASE: missing output", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + tx := createDummyTransaction() valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx) - cachedTransaction, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) + cachedTransaction, cachedTransactionMetadata, _, transactionIsNew := tangle.storeTransactionModels(valueObject) + assert.True(t, transactionIsNew) + + event.Expect("TransactionSolid", tx, mock.Anything) + + // manually trigger a booking: tx will be marked solid, but it cannot be book as its inputs are unavailable transactionBooked, decisionPending, err := tangle.bookTransaction(cachedTransaction, cachedTransactionMetadata) assert.False(t, transactionBooked) assert.False(t, decisionPending) assert.Error(t, err) + + event.AssertExpectations(t) }) // CASE: transaction already booked by another process t.Run("CASE: transaction already booked by another process", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + tx := createDummyTransaction() valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx) cachedTransaction, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) @@ -65,11 +82,15 @@ func TestBookTransaction(t *testing.T) { require.NoError(t, err) assert.False(t, transactionBooked) assert.False(t, decisionPending) + + event.AssertExpectations(t) }) // CASE: booking first spend t.Run("CASE: booking first spend", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() // prepare snapshot color1 := [32]byte{1} @@ -85,7 +106,7 @@ func TestBookTransaction(t *testing.T) { inputIDs := loadSnapshotFromOutputs(tangle, outputs) // build first spending - tx := transaction.New( + tx1 := transaction.New( transaction.NewInputs(inputIDs...), // outputs transaction.NewOutputs(map[address.Address][]*balance.Balance{ @@ -96,13 +117,16 @@ func TestBookTransaction(t *testing.T) { }), ) - valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx) + valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx1) cachedTransaction, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() // assert that branchID is undefined before being booked assert.Equal(t, branchmanager.UndefinedBranchID, txMetadata.BranchID()) + event.Expect("TransactionSolid", tx1, mock.Anything) + // TransactionBooked is triggered outside of bookTransaction + transactionBooked, decisionPending, err := tangle.bookTransaction(cachedTransaction, cachedTransactionMetadata) require.NoError(t, err) assert.True(t, transactionBooked, "transactionBooked") @@ -114,7 +138,7 @@ func TestBookTransaction(t *testing.T) { // CASE: booking double spend t.Run("CASE: booking double spend", func(t *testing.T) { // build second spending - tx := transaction.New( + tx2 := transaction.New( transaction.NewInputs(inputIDs...), // outputs transaction.NewOutputs(map[address.Address][]*balance.Balance{ @@ -125,13 +149,17 @@ func TestBookTransaction(t *testing.T) { }), ) - valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx) + valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx2) cachedTransaction, cachedTransactionMetadata, _, _ = tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() // assert that branchID is undefined before being booked assert.Equal(t, branchmanager.UndefinedBranchID, txMetadata.BranchID()) + // manually book the double spending tx2, this will mark it as solid and trigger a fork + event.Expect("TransactionSolid", tx2, mock.Anything) + event.Expect("Fork", tx1, mock.Anything, mock.Anything, inputIDs) + transactionBooked, decisionPending, err := tangle.bookTransaction(cachedTransaction, cachedTransactionMetadata) require.NoError(t, err) assert.True(t, transactionBooked, "transactionBooked") @@ -140,6 +168,8 @@ func TestBookTransaction(t *testing.T) { // assert that first spend and double spend have different BranchIDs assert.NotEqual(t, branchmanager.MasterBranchID, txMetadata.BranchID(), "BranchID") }) + + event.AssertExpectations(t) }) } @@ -230,6 +260,9 @@ func TestFork(t *testing.T) { // CASE: already finalized t.Run("CASE: already finalized", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + // prepare snapshot color1 := [32]byte{1} outputs := map[address.Address][]*balance.Balance{ @@ -259,14 +292,20 @@ func TestFork(t *testing.T) { txMetadata.setFinalized(true) + // no fork created so no event should be triggered forked, finalized, err := tangle.Fork(tx.ID(), []transaction.OutputID{}) require.NoError(t, err) assert.False(t, forked) assert.True(t, finalized) + + event.AssertExpectations(t) }) t.Run("CASE: normal fork", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + // prepare snapshot color1 := [32]byte{1} outputs := map[address.Address][]*balance.Balance{ @@ -293,24 +332,30 @@ func TestFork(t *testing.T) { valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx) tangle.storeTransactionModels(valueObject) + event.Expect("Fork", tx, mock.Anything, mock.Anything, []transaction.OutputID{}) + forked, finalized, err := tangle.Fork(tx.ID(), []transaction.OutputID{}) require.NoError(t, err) assert.True(t, forked, "forked") assert.False(t, finalized, "finalized") t.Run("CASE: branch existed already", func(t *testing.T) { + // no fork created so no event should be triggered forked, finalized, err = tangle.Fork(tx.ID(), []transaction.OutputID{}) require.NoError(t, err) assert.False(t, forked, "forked") assert.False(t, finalized, "finalized") }) - }) + event.AssertExpectations(t) + }) } func TestBookPayload(t *testing.T) { t.Run("CASE: undefined branchID", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() valueObject := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction()) cachedPayload, cachedMetadata, _ := tangle.storePayload(valueObject) @@ -325,10 +370,14 @@ func TestBookPayload(t *testing.T) { require.NoError(t, err) assert.False(t, payloadBooked, "payloadBooked") + + event.AssertExpectations(t) }) t.Run("CASE: successfully book", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() valueObject := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction()) cachedPayload, cachedMetadata, _ := tangle.storePayload(valueObject) @@ -341,6 +390,7 @@ func TestBookPayload(t *testing.T) { txMetadata := cachedTransactionMetadata.Unwrap() txMetadata.setBranchID(branchmanager.BranchID{1}) + event.Expect("PayloadSolid", valueObject, mock.Anything) payloadBooked, err := tangle.bookPayload(cachedPayload.Retain(), cachedMetadata.Retain(), cachedTransactionMetadata.Retain()) defer func() { cachedPayload.Release() @@ -350,10 +400,14 @@ func TestBookPayload(t *testing.T) { require.NoError(t, err) assert.True(t, payloadBooked, "payloadBooked") + + event.AssertExpectations(t) }) t.Run("CASE: not booked", func(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() valueObject := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction()) cachedPayload, cachedMetadata, _ := tangle.storePayload(valueObject) @@ -366,6 +420,7 @@ func TestBookPayload(t *testing.T) { txMetadata := cachedTransactionMetadata.Unwrap() txMetadata.setBranchID(branchmanager.BranchID{1}) + event.Expect("PayloadSolid", valueObject, mock.Anything) payloadBooked, err := tangle.bookPayload(cachedPayload.Retain(), cachedMetadata.Retain(), cachedTransactionMetadata.Retain()) defer func() { cachedPayload.Release() @@ -375,6 +430,8 @@ func TestBookPayload(t *testing.T) { require.NoError(t, err) assert.False(t, payloadBooked, "payloadBooked") + + event.AssertExpectations(t) }) } @@ -382,6 +439,8 @@ func TestBookPayload(t *testing.T) { // TestStorePayload checks whether a value object is correctly stored. func TestStorePayload(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() tx := createDummyTransaction() valueObject := payload.New(payload.GenesisID, payload.GenesisID, tx) @@ -415,6 +474,8 @@ func TestStorePayload(t *testing.T) { assert.Equal(t, valueObject.ID(), payloadMetadata.PayloadID()) }) } + + event.AssertExpectations(t) } // TestStoreTransactionModels checks whether all models corresponding to a transaction are correctly created. @@ -925,7 +986,7 @@ func TestRetrieveConsumedInputDetails(t *testing.T) { cachedInputs.Consume(func(input *Output) { assert.ElementsMatch(t, outputs[input.Address()], input.Balances()) }) - assert.True(t, cmp.Equal(sumOutputsByColor(outputs), consumedBalances)) + assert.Equal(t, sumOutputsByColor(outputs), consumedBalances) assert.Len(t, consumedBranches, 1) assert.Contains(t, consumedBranches, branchmanager.MasterBranchID) } @@ -967,7 +1028,7 @@ func TestRetrieveConsumedInputDetails(t *testing.T) { cachedInputs.Consume(func(input *Output) { assert.ElementsMatch(t, outputs[input.Address()], input.Balances()) }) - assert.True(t, cmp.Equal(sumOutputsByColor(outputs), consumedBalances)) + assert.Equal(t, sumOutputsByColor(outputs), consumedBalances) assert.Len(t, consumedBranches, 1) assert.Contains(t, consumedBranches, branchmanager.MasterBranchID) } @@ -1032,7 +1093,7 @@ func TestRetrieveConsumedInputDetails(t *testing.T) { cachedInputs.Consume(func(input *Output) { assert.ElementsMatch(t, outputs[input.Address()], input.Balances()) }) - assert.True(t, cmp.Equal(sumOutputsByColor(outputs), consumedBalances)) + assert.Equal(t, sumOutputsByColor(outputs), consumedBalances) assert.Len(t, consumedBranches, 2) assert.Contains(t, consumedBranches, branchmanager.MasterBranchID) assert.Contains(t, consumedBranches, newBranch) @@ -1201,6 +1262,8 @@ func TestCheckTransactionSolidity(t *testing.T) { func TestPayloadBranchID(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() { branchID := tangle.payloadBranchID(payload.GenesisID) @@ -1226,19 +1289,19 @@ func TestPayloadBranchID(t *testing.T) { // test missing value object { valueObject := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction()) - missing := 0 - tangle.Events.PayloadMissing.Attach(events.NewClosure(func(payloadID payload.ID) { - missing++ - })) + event.Expect("PayloadMissing", valueObject.ID()) branchID := tangle.payloadBranchID(valueObject.ID()) assert.Equal(t, branchmanager.UndefinedBranchID, branchID) - assert.Equal(t, 1, missing) } + + event.AssertExpectations(t) } func TestCheckPayloadSolidity(t *testing.T) { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() // check with already solid payload { @@ -1344,12 +1407,17 @@ func TestCheckPayloadSolidity(t *testing.T) { assert.False(t, solid) assert.Error(t, err) } + + event.AssertExpectations(t) } func TestCreateValuePayloadFutureConeIterator(t *testing.T) { // check with new payload -> should be added to stack { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + solidificationStack := list.New() processedPayloads := make(map[payload.ID]types.Empty) iterator := tangle.createValuePayloadFutureConeIterator(solidificationStack, processedPayloads) @@ -1378,11 +1446,16 @@ func TestCreateValuePayloadFutureConeIterator(t *testing.T) { currentSolidificationEntry.CachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { assert.Equal(t, tx.ID(), metadata.ID()) }) + + event.AssertExpectations(t) } // check with already processed payload -> should not be added to stack { tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + solidificationStack := list.New() processedPayloads := make(map[payload.ID]types.Empty) iterator := tangle.createValuePayloadFutureConeIterator(solidificationStack, processedPayloads) @@ -1400,6 +1473,8 @@ func TestCreateValuePayloadFutureConeIterator(t *testing.T) { iterator(cachedPayload, cachedMetadata, cachedTransaction, cachedTransactionMetadata) assert.Equal(t, 0, solidificationStack.Len()) + + event.AssertExpectations(t) } } @@ -1498,6 +1573,55 @@ func TestForeachApprovers(t *testing.T) { assert.Equal(t, 2, counter) } +func TestMissingPayloadReceived(t *testing.T) { + tangle := New(mapdb.NewMapDB()) + event := newEventTangle(t, tangle) + defer event.DetachAll() + + // prepare snapshot + unspentOutputs := loadSnapshotFromOutputs( + tangle, + map[address.Address][]*balance.Balance{ + address.Random(): { + balance.New(balance.ColorIOTA, 3), + }, + }, + ) + + // create transaction spending those snapshot outputs + tx := transaction.New( + transaction.NewInputs(unspentOutputs...), + transaction.NewOutputs(map[address.Address][]*balance.Balance{ + address.Random(): { + balance.New(balance.ColorIOTA, 3), + }, + }), + ) + + // create two value objects for this transaction referencing each other + parent := payload.New(payload.GenesisID, payload.GenesisID, tx) + child := payload.New(parent.ID(), parent.ID(), tx) + + event.Expect("PayloadAttached", child, mock.Anything) + event.Expect("PayloadMissing", parent.ID(), mock.Anything) + event.Expect("TransactionReceived", tx, mock.Anything, mock.Anything) + + // submit the child first; it cannot be solidified + tangle.AttachPayloadSync(child) + + event.Expect("PayloadAttached", parent, mock.Anything) + event.Expect("PayloadSolid", parent, mock.Anything) + event.Expect("MissingPayloadReceived", parent, mock.Anything) + event.Expect("PayloadSolid", child, mock.Anything) + event.Expect("TransactionSolid", tx, mock.Anything, mock.Anything) + event.Expect("TransactionBooked", tx, mock.Anything, true) + + // submitting the parent makes everything solid + tangle.AttachPayloadSync(parent) + + event.AssertExpectations(t) +} + func storeParentPayloadWithMetadataFunc(t *testing.T, tangle *Tangle, consume func(*PayloadMetadata)) payload.ID { parent1 := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction()) cachedPayload, cachedMetadata, stored := tangle.storePayload(parent1) diff --git a/go.mod b/go.mod index c2392c60fefc4b66be7d848ed2769ce2101bc68a..7d5fd68a00f5d8f3fc24eb3808b65dec480f82f5 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/drand/kyber v1.0.1-0.20200331114745-30e90cc60f99 github.com/gobuffalo/packr/v2 v2.7.1 github.com/golang/protobuf v1.3.5 - github.com/google/go-cmp v0.4.0 github.com/gorilla/websocket v1.4.1 github.com/iotaledger/hive.go v0.0.0-20200617164933-c48b4401b814 github.com/iotaledger/iota.go v1.0.0-beta.14 @@ -22,7 +21,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.6.2 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 github.com/valyala/fasttemplate v1.1.0 // indirect go.dedis.ch/kyber/v3 v3.0.12 go.uber.org/atomic v1.6.0 diff --git a/go.sum b/go.sum index 575831135fb95140a69b8b1210e0f51e7f29808a..4586afd00fcc72e9328807b0277c02e60df952f5 100644 --- a/go.sum +++ b/go.sum @@ -297,6 +297,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -491,6 +493,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= diff --git a/tools/integration-tests/tester/go.mod b/tools/integration-tests/tester/go.mod index 3d3c66376488f6ea025677cc2be9824f3f3bc784..6fa168b7f05b59cb11712ac49db923f5cc286f70 100644 --- a/tools/integration-tests/tester/go.mod +++ b/tools/integration-tests/tester/go.mod @@ -12,7 +12,7 @@ require ( github.com/iotaledger/goshimmer v0.1.3 github.com/iotaledger/hive.go v0.0.0-20200617164933-c48b4401b814 github.com/opencontainers/go-digest v1.0.0-rc1 // indirect - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 ) replace github.com/iotaledger/goshimmer => ../../.. diff --git a/tools/integration-tests/tester/go.sum b/tools/integration-tests/tester/go.sum index 949a92ace7064e1be5d5990ed3b925e708dcd0f2..701c02c13d3ff0bcfe99eb6d4f922e8297be14c5 100644 --- a/tools/integration-tests/tester/go.sum +++ b/tools/integration-tests/tester/go.sum @@ -287,6 +287,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -478,6 +480,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=