diff --git a/dapps/valuetransfers/packages/tangle/debugger.go b/dapps/valuetransfers/packages/tangle/debugger.go new file mode 100644 index 0000000000000000000000000000000000000000..976c5270f839abafc15fdb8bb36518ea4ee813de --- /dev/null +++ b/dapps/valuetransfers/packages/tangle/debugger.go @@ -0,0 +1,115 @@ +package tangle + +import ( + "fmt" + "strings" +) + +// Debugger represents a utility that allows us to print debug messages and function calls. +type Debugger struct { + aliases map[interface{}]string + enabled bool + indent int +} + +// NewDebugger is the constructor of a debugger instance. +func NewDebugger() *Debugger { + return (&Debugger{}).ResetAliases() +} + +// Enable sets the debugger to print the debug information. +func (debugger *Debugger) Enable() { + debugger.enabled = true + + fmt.Println("[DEBUGGER::ENABLED]") +} + +// Disable sets the debugger to not print any debug information. +func (debugger *Debugger) Disable() { + fmt.Println("[DEBUGGER::DISABLED]") + debugger.enabled = false +} + +// ResetAliases removes any previously registered aliases. This can be useful if the same debugger instance is for +// example used in different tests or test cases. +func (debugger *Debugger) ResetAliases() *Debugger { + debugger.aliases = make(map[interface{}]string) + + return debugger +} + +// RegisterAlias registers a string representation for the given element. This can be used to create a string +// representation for things like ids in the form of byte slices. +func (debugger *Debugger) RegisterAlias(element interface{}, alias string) { + debugger.aliases[element] = alias +} + +// FunctionCall prints debug information about a function call. It automatically indents all following debug outputs +// until Return() is called. The best way to use this is by starting a function call with a construct like: +// +// defer debugger.FunctionCall("myFunction", param1, param2).Return() +func (debugger *Debugger) FunctionCall(identifier string, params ...interface{}) *Debugger { + if !debugger.enabled { + return debugger + } + + debugger.Print(identifier + "(" + debugger.paramsAsCommaSeparatedList(params...) + ") {") + debugger.indent++ + + return debugger +} + +// Return prints debug information about a FunctionCall() the was finished. It reduces the indentation for consecutive +// debug outputs. +func (debugger *Debugger) Return() *Debugger { + if !debugger.enabled { + return debugger + } + + debugger.indent-- + debugger.Print("}") + + return debugger +} + +// Print prints an arbitrary debug message that can for example be used to print an information when a certain part of +// the code is executed. +func (debugger *Debugger) Print(identifier string, params ...interface{}) { + if !debugger.enabled { + return + } + + if len(params) >= 1 { + debugger.print(identifier + " = " + debugger.paramsAsCommaSeparatedList(params...)) + } else { + debugger.print(identifier) + } +} + +// print is an internal utility function that actually prints the given string to stdout. +func (debugger *Debugger) print(stringToPrint string) { + fmt.Println("[DEBUGGER] " + strings.Repeat(" ", debugger.indent) + stringToPrint) +} + +// paramsAsCommaSeparatedList creates a comma separated list of the given parameters. +func (debugger *Debugger) paramsAsCommaSeparatedList(params ...interface{}) string { + paramsAsStrings := make([]string, len(params)) + for i, param := range params { + paramsAsStrings[i] = debugger.paramAsString(param) + } + + return strings.Join(paramsAsStrings, ", ") +} + +// paramAsString returns a string representation of an arbitrary parameter. +func (debugger *Debugger) paramAsString(param interface{}) string { + defer func() { recover() }() + if alias, aliasExists := debugger.aliases[param]; aliasExists { + return alias + } + + return fmt.Sprint(param) +} + +// debugger contains the default global debugger instance. +var debugger = NewDebugger() diff --git a/dapps/valuetransfers/packages/tangle/payloadmetadata.go b/dapps/valuetransfers/packages/tangle/payloadmetadata.go index cfa6e0133b5bb55445af29f2f517188cb9218399..11ce6e61e28a5c4010da0d102367f57707b796aa 100644 --- a/dapps/valuetransfers/packages/tangle/payloadmetadata.go +++ b/dapps/valuetransfers/packages/tangle/payloadmetadata.go @@ -4,11 +4,12 @@ import ( "sync" "time" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload" "github.com/iotaledger/hive.go/marshalutil" "github.com/iotaledger/hive.go/objectstorage" "github.com/iotaledger/hive.go/stringify" + + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload" ) // PayloadMetadata is a container for the metadata of a value transfer payload. diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go index 70bdf8dc52777dfc150aca26846778707432b479..dd61f57a48dda97cc7b99079fa448d347c9e5f6d 100644 --- a/dapps/valuetransfers/packages/tangle/tangle.go +++ b/dapps/valuetransfers/packages/tangle/tangle.go @@ -537,6 +537,8 @@ func (tangle *Tangle) propagateBranchConfirmedRejectedChangesToTangle(cachedBran // region PRIVATE UTILITY METHODS ////////////////////////////////////////////////////////////////////////////////////// func (tangle *Tangle) setTransactionFinalized(transactionID transaction.ID, eventSource EventSource) (modified bool, err error) { + defer debugger.FunctionCall("setTransactionFinalized", transactionID, eventSource).Return() + // retrieve metadata and consume cachedTransactionMetadata := tangle.TransactionMetadata(transactionID) cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { @@ -556,7 +558,7 @@ func (tangle *Tangle) setTransactionFinalized(transactionID transaction.ID, even tangle.Events.TransactionFinalized.Trigger(cachedTransaction, cachedTransactionMetadata) // propagate the rejected flag - if !metadata.Preferred() { + if !metadata.Preferred() && !metadata.Rejected() { tangle.propagateRejectedToTransactions(metadata.ID()) } @@ -586,6 +588,8 @@ func (tangle *Tangle) setTransactionFinalized(transactionID transaction.ID, even // propagateRejectedToTransactions propagates the rejected flag to a transaction, its outputs and to its consumers. func (tangle *Tangle) propagateRejectedToTransactions(transactionID transaction.ID) { + defer debugger.FunctionCall("propagateRejectedToTransactions", transactionID).Return() + // initialize stack with first transaction rejectedPropagationStack := list.New() rejectedPropagationStack.PushBack(transactionID) @@ -600,6 +604,8 @@ func (tangle *Tangle) propagateRejectedToTransactions(transactionID transaction. rejectedPropagationStack.Remove(firstElement) currentTransactionID := firstElement.Value.(transaction.ID) + debugger.Print("rejectedPropagationStack.Front()", currentTransactionID) + cachedTransactionMetadata := tangle.TransactionMetadata(currentTransactionID) cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { if !metadata.setRejected(true) { @@ -607,9 +613,12 @@ func (tangle *Tangle) propagateRejectedToTransactions(transactionID transaction. } metadata.setPreferred(false) - if _, err := tangle.setTransactionFinalized(metadata.ID(), EventSourceTangle); err != nil { - tangle.Events.Error.Trigger(err) - return + if !metadata.Finalized() { + if _, err := tangle.setTransactionFinalized(metadata.ID(), EventSourceTangle); err != nil { + tangle.Events.Error.Trigger(err) + + return + } } cachedTransaction := tangle.Transaction(currentTransactionID) @@ -645,6 +654,8 @@ func (tangle *Tangle) propagateRejectedToTransactions(transactionID transaction. // TODO: WRITE COMMENT func (tangle *Tangle) propagateValuePayloadConfirmedRejectedUpdates(transactionID transaction.ID, confirmed bool) { + defer debugger.FunctionCall("propagateValuePayloadConfirmedRejectedUpdates", transactionID, confirmed).Return() + // initiate stack with the attachments of the passed in transaction propagationStack := list.New() tangle.Attachments(transactionID).Consume(func(attachment *Attachment) { @@ -677,6 +688,8 @@ func (tangle *Tangle) propagateValuePayloadConfirmedRejectedUpdateStackEntry(pro return } + defer debugger.FunctionCall("propagateValuePayloadConfirmedRejectedUpdateStackEntry", currentPayload.ID(), currentTransaction.ID()).Return() + // perform different logic depending on the type of the change (liked vs dislike) switch confirmed { case true: diff --git a/dapps/valuetransfers/packages/tangle/tangle_test.go b/dapps/valuetransfers/packages/tangle/tangle_test.go index ea2a2a34a112ca2922444d1b336d95a73726a7d8..bdbee4c1ba91fd8d2c0192694fbe8c3d373380b9 100644 --- a/dapps/valuetransfers/packages/tangle/tangle_test.go +++ b/dapps/valuetransfers/packages/tangle/tangle_test.go @@ -2,13 +2,13 @@ package tangle import ( "container/list" - "fmt" "log" "math" "sync" "testing" "github.com/google/go-cmp/cmp" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" @@ -1967,7 +1967,7 @@ func preparePropagationScenario1() (*Tangle, map[string]*transaction.Transaction }, }), ) - valueObjects["[-A, F+]"] = payload.New(payload.GenesisID, valueObjects["[-GENESIS, A+, B+, C+]"].ID(), transactions["[-A, F+]"]) + valueObjects["[-A, F+]"] = payload.New(valueObjects["[-B, -C, E+]"].ID(), valueObjects["[-GENESIS, A+, B+, C+]"].ID(), transactions["[-A, F+]"]) // attach payload tangle.AttachPayloadSync(valueObjects["[-A, F+]"]) } @@ -2100,18 +2100,14 @@ func TestPropagationScenario1(t *testing.T) { // test future cone monotonicity more complex - everything MUST be rejected and finalized if spending funds from rejected tx { - transactionsSlice := []string{ - "[-GENESIS, A+, B+, C+]", - "[-A, D+]", - "[-B, -C, E+]", - "[-B, -C, E+] (Reattachment)", - "[-A, F+]", - "[-E, -F, G+]", - } tangle, transactions, valueObjects, _ := preparePropagationScenario1() - for _, name := range transactionsSlice { - fmt.Println(name, valueObjects[name].Transaction().ID()) + debugger.ResetAliases() + for name, valueObject := range valueObjects { + debugger.RegisterAlias(valueObject.ID(), "ValueObjectID"+name) + } + for name, tx := range transactions { + debugger.RegisterAlias(tx.ID(), "TransactionID"+name) } setTransactionPreferredWithCheck(t, tangle, transactions["[-GENESIS, A+, B+, C+]"], true) @@ -2119,8 +2115,10 @@ func TestPropagationScenario1(t *testing.T) { verifyInclusionState(t, tangle, valueObjects["[-GENESIS, A+, B+, C+]"], true, true, true, true, false) // finalize & reject + debugger.Enable() setTransactionFinalizedWithCheck(t, tangle, transactions["[-B, -C, E+]"]) verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+]"], false, true, false, false, true) + debugger.Disable() // check future cone to be rejected verifyInclusionState(t, tangle, valueObjects["[-B, -C, E+] (Reattachment)"], false, true, false, false, true) @@ -2624,7 +2622,7 @@ func TestLucasScenario(t *testing.T) { }), ) transactions["[-A, F+]"].Sign(signaturescheme.ED25519(*seed.KeyPair(A))) - valueObjects["[-A, F+]"] = payload.New(payload.GenesisID, valueObjects["[-GENESIS, A+, B+, C+]"].ID(), transactions["[-A, F+]"]) + valueObjects["[-A, F+]"] = payload.New(valueObjects["[-B, -C, E+]"].ID(), valueObjects["[-GENESIS, A+, B+, C+]"].ID(), transactions["[-A, F+]"]) // check if signatures are valid assert.True(t, transactions["[-A, F+]"].SignaturesValid())