diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f1160f1cccc88bbb554416c0d7950ac0678c03fb..cd08c1e7c9f756728e30525aea4b0a67c0d0f6b0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -143,3 +143,38 @@ jobs: with: name: ${{ env.TEST_NAME }} path: tools/integration-tests/logs + + + value: + name: value + env: + TEST_NAME: value + runs-on: ubuntu-latest + steps: + + - name: Check out code + uses: actions/checkout@v2 + + - name: Build GoShimmer image + run: docker build -t iotaledger/goshimmer . + + - name: Pull additional Docker images + run: | + docker pull angelocapossele/drand:latest + docker pull gaiaadm/pumba:latest + docker pull gaiadocker/iproute2:latest + + - name: Run integration tests + run: docker-compose -f tools/integration-tests/tester/docker-compose.yml up --abort-on-container-exit --exit-code-from tester --build + + - name: Create logs from tester + if: always() + run: | + docker logs tester &> tools/integration-tests/logs/tester.log + + - name: Save logs as artifacts + if: always() + uses: actions/upload-artifact@v1 + with: + name: ${{ env.TEST_NAME }} + path: tools/integration-tests/logs diff --git a/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go b/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go index e5a148f6efaa36c55bf160bb5e8d2a11a5f2702b..268499b7b446b9536a2784f0b7ba902b05b7eebe 100644 --- a/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go +++ b/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go @@ -1,10 +1,13 @@ package branchmanager import ( + "reflect" "testing" + "github.com/iotaledger/hive.go/events" "github.com/iotaledger/hive.go/kvstore/mapdb" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) type sampleTree struct { @@ -22,6 +25,92 @@ func (st *sampleTree) Release() { } } +// eventMock is a wrapper around mock.Mock used to test the triggered events. +type eventMock struct { + mock.Mock + + attached []struct { + *events.Event + *events.Closure + } +} + +func newEventMock(t *testing.T, mgr *BranchManager) *eventMock { + e := &eventMock{} + e.Test(t) + + // attach all events + e.attach(mgr.Events.BranchConfirmed, e.BranchConfirmed) + e.attach(mgr.Events.BranchDisliked, e.BranchDisliked) + e.attach(mgr.Events.BranchFinalized, e.BranchFinalized) + e.attach(mgr.Events.BranchLiked, e.BranchLiked) + e.attach(mgr.Events.BranchPreferred, e.BranchPreferred) + e.attach(mgr.Events.BranchRejected, e.BranchRejected) + e.attach(mgr.Events.BranchUnpreferred, e.BranchUnpreferred) + + // assure that all available events are mocked + numEvents := reflect.ValueOf(mgr.Events).Elem().NumField() + assert.Equalf(t, len(e.attached), numEvents, "not all events in BranchManager.Events have been attached") + + return e +} + +// DetachAll detaches all attached event mocks. +func (e *eventMock) DetachAll() { + for _, a := range e.attached { + a.Event.Detach(a.Closure) + } +} + +// Expect starts a description of an expectation of the specified event being triggered. +func (e *eventMock) Expect(eventName string, arguments ...interface{}) { + e.On(eventName, arguments...) +} + +func (e *eventMock) 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}) +} + +func (e *eventMock) BranchConfirmed(cachedBranch *CachedBranch) { + defer cachedBranch.Release() + e.Called(cachedBranch.Unwrap()) +} + +func (e *eventMock) BranchDisliked(cachedBranch *CachedBranch) { + defer cachedBranch.Release() + e.Called(cachedBranch.Unwrap()) +} + +func (e *eventMock) BranchFinalized(cachedBranch *CachedBranch) { + defer cachedBranch.Release() + e.Called(cachedBranch.Unwrap()) +} + +func (e *eventMock) BranchLiked(cachedBranch *CachedBranch) { + defer cachedBranch.Release() + e.Called(cachedBranch.Unwrap()) +} + +func (e *eventMock) BranchPreferred(cachedBranch *CachedBranch) { + defer cachedBranch.Release() + e.Called(cachedBranch.Unwrap()) +} + +func (e *eventMock) BranchRejected(cachedBranch *CachedBranch) { + defer cachedBranch.Release() + e.Called(cachedBranch.Unwrap()) +} + +func (e *eventMock) BranchUnpreferred(cachedBranch *CachedBranch) { + defer cachedBranch.Release() + e.Called(cachedBranch.Unwrap()) +} + // ./img/sample_tree.png func createSampleTree(branchManager *BranchManager) *sampleTree { st := &sampleTree{ @@ -857,6 +946,8 @@ func TestBranchManager_BranchesConflicting(t *testing.T) { func TestBranchManager_SetBranchPreferred(t *testing.T) { branchManager := New(mapdb.NewMapDB()) + event := newEventMock(t, branchManager) + defer event.DetachAll() cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) defer cachedBranch2.Release() @@ -882,6 +973,9 @@ func TestBranchManager_SetBranchPreferred(t *testing.T) { // lets assume branch 4 is preferred since its underlying transaction was longer // solid than the avg. network delay before the conflicting transaction which created // the conflict set was received + + event.Expect("BranchPreferred", branch4) + modified, err := branchManager.SetBranchPreferred(branch4.ID(), true) assert.NoError(t, err) assert.True(t, modified) @@ -894,6 +988,11 @@ func TestBranchManager_SetBranchPreferred(t *testing.T) { // now branch 2 becomes preferred via FPC, this causes branch 2 to be liked (since // the master branch is liked) and its liked state propagates to branch 4 (but not branch 5) + + event.Expect("BranchPreferred", branch2) + event.Expect("BranchLiked", branch2) + event.Expect("BranchLiked", branch4) + modified, err = branchManager.SetBranchPreferred(branch2.ID(), true) assert.NoError(t, err) assert.True(t, modified) @@ -907,6 +1006,12 @@ func TestBranchManager_SetBranchPreferred(t *testing.T) { // now the network decides that branch 5 is preferred (via FPC), thus branch 4 should lose its // preferred and liked state and branch 5 should instead become preferred and liked + + event.Expect("BranchPreferred", branch5) + event.Expect("BranchLiked", branch5) + event.Expect("BranchUnpreferred", branch4) + event.Expect("BranchDisliked", branch4) + modified, err = branchManager.SetBranchPreferred(branch5.ID(), true) assert.NoError(t, err) assert.True(t, modified) @@ -920,10 +1025,15 @@ func TestBranchManager_SetBranchPreferred(t *testing.T) { assert.False(t, branch4.Preferred(), "branch 4 should not be preferred") assert.True(t, branch5.Liked(), "branch 5 should be liked") assert.True(t, branch5.Preferred(), "branch 5 should be preferred") + + // check that all event have been triggered + event.AssertExpectations(t) } func TestBranchManager_SetBranchPreferred2(t *testing.T) { branchManager := New(mapdb.NewMapDB()) + event := newEventMock(t, branchManager) + defer event.DetachAll() cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) defer cachedBranch2.Release() @@ -949,6 +1059,11 @@ func TestBranchManager_SetBranchPreferred2(t *testing.T) { defer cachedBranch7.Release() branch7 := cachedBranch7.Unwrap() + event.Expect("BranchPreferred", branch2) + event.Expect("BranchLiked", branch2) + event.Expect("BranchPreferred", branch6) + event.Expect("BranchLiked", branch6) + // assume branch 2 preferred since solid longer than avg. network delay modified, err := branchManager.SetBranchPreferred(branch2.ID(), true) assert.NoError(t, err) @@ -1031,13 +1146,21 @@ func TestBranchManager_SetBranchPreferred2(t *testing.T) { // should also become liked, since branch 2 of which it spawns off is liked too. // simulate branch 3 being not preferred from FPC vote + // this does not trigger any events as branch 3 was never preferred modified, err = branchManager.SetBranchPreferred(branch3.ID(), false) assert.NoError(t, err) assert.False(t, modified) // simulate branch 7 being not preferred from FPC vote + // this does not trigger any events as branch 7 was never preferred modified, err = branchManager.SetBranchPreferred(branch7.ID(), false) assert.NoError(t, err) assert.False(t, modified) + + event.Expect("BranchPreferred", branch4) + event.Expect("BranchLiked", branch4) + event.Expect("BranchPreferred", aggrBranch8) + event.Expect("BranchLiked", aggrBranch8) + // simulate branch 4 being preferred by FPC vote modified, err = branchManager.SetBranchPreferred(branch4.ID(), true) assert.NoError(t, err) @@ -1049,4 +1172,7 @@ func TestBranchManager_SetBranchPreferred2(t *testing.T) { // of which it spawns off are. assert.True(t, aggrBranch8.Liked(), "aggr. branch 8 should be liked") assert.True(t, aggrBranch8.Preferred(), "aggr. branch 8 should be preferred") + + // check that all event have been triggered + event.AssertExpectations(t) } diff --git a/dapps/valuetransfers/packages/tangle/output.go b/dapps/valuetransfers/packages/tangle/output.go index faad3a5ca3db3f1d8ea9858477307a2413a8eb4b..5d19ec51f12cd50a29abc7de98e279a8b8d5cb8b 100644 --- a/dapps/valuetransfers/packages/tangle/output.go +++ b/dapps/valuetransfers/packages/tangle/output.go @@ -25,6 +25,8 @@ type Output struct { solidificationTime time.Time firstConsumer transaction.ID consumerCount int + preferred bool + finalized bool liked bool confirmed bool rejected bool @@ -34,6 +36,8 @@ type Output struct { solidMutex sync.RWMutex solidificationTimeMutex sync.RWMutex consumerMutex sync.RWMutex + preferredMutex sync.RWMutex + finalizedMutex sync.RWMutex likedMutex sync.RWMutex confirmedMutex sync.RWMutex rejectedMutex sync.RWMutex @@ -140,8 +144,8 @@ func (output *Output) BranchID() branchmanager.BranchID { return output.branchID } -// SetBranchID is the setter for the property that indicates in which ledger state branch the output is booked. -func (output *Output) SetBranchID(branchID branchmanager.BranchID) (modified bool) { +// setBranchID is the setter for the property that indicates in which ledger state branch the output is booked. +func (output *Output) setBranchID(branchID branchmanager.BranchID) (modified bool) { output.branchIDMutex.RLock() if output.branchID == branchID { output.branchIDMutex.RUnlock() @@ -232,6 +236,73 @@ func (output *Output) ConsumerCount() int { return output.consumerCount } +// Preferred returns true if the output belongs to a preferred transaction. +func (output *Output) Preferred() (result bool) { + output.preferredMutex.RLock() + defer output.preferredMutex.RUnlock() + + return output.preferred +} + +// setPreferred updates the preferred flag of the output. It is defined as a private setter because updating the +// preferred flag causes changes in other outputs and branches as well. This means that we need additional logic +// in the tangle. To update the preferred flag of a output, we need to use Tangle.SetTransactionPreferred(bool). +func (output *Output) setPreferred(preferred bool) (modified bool) { + output.preferredMutex.RLock() + if output.preferred == preferred { + output.preferredMutex.RUnlock() + + return + } + + output.preferredMutex.RUnlock() + output.preferredMutex.Lock() + defer output.preferredMutex.Unlock() + + if output.preferred == preferred { + return + } + + output.preferred = preferred + output.SetModified() + modified = true + + return +} + +// setFinalized allows us to set the finalized flag on the outputs. Finalized outputs will not be forked when +// a conflict arrives later. +func (output *Output) setFinalized(finalized bool) (modified bool) { + output.finalizedMutex.RLock() + if output.finalized == finalized { + output.finalizedMutex.RUnlock() + + return + } + + output.finalizedMutex.RUnlock() + output.finalizedMutex.Lock() + defer output.finalizedMutex.Unlock() + + if output.finalized == finalized { + return + } + + output.finalized = finalized + output.SetModified() + modified = true + + return +} + +// Finalized returns true, if the decision if this output is preferred or not has been finalized by consensus already. +func (output *Output) Finalized() bool { + output.finalizedMutex.RLock() + defer output.finalizedMutex.RUnlock() + + return output.finalized +} + // Liked returns true if the Output was marked as liked. func (output *Output) Liked() bool { output.likedMutex.RLock() @@ -357,12 +428,17 @@ func (output *Output) ObjectStorageValue() []byte { balanceCount := len(output.balances) // initialize helper - marshalUtil := marshalutil.New(branchmanager.BranchIDLength + marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + transaction.IDLength + marshalutil.UINT32_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length) + marshalUtil := marshalutil.New(branchmanager.BranchIDLength + 6*marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + transaction.IDLength + marshalutil.UINT32_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length) marshalUtil.WriteBytes(output.branchID.Bytes()) marshalUtil.WriteBool(output.solid) marshalUtil.WriteTime(output.solidificationTime) marshalUtil.WriteBytes(output.firstConsumer.Bytes()) marshalUtil.WriteUint32(uint32(output.consumerCount)) + marshalUtil.WriteBool(output.Preferred()) + marshalUtil.WriteBool(output.Finalized()) + marshalUtil.WriteBool(output.Liked()) + marshalUtil.WriteBool(output.Confirmed()) + marshalUtil.WriteBool(output.Rejected()) marshalUtil.WriteUint32(uint32(balanceCount)) for _, balanceToMarshal := range output.balances { marshalUtil.WriteBytes(balanceToMarshal.Bytes()) @@ -391,6 +467,21 @@ func (output *Output) UnmarshalObjectStorageValue(data []byte) (consumedBytes in if err != nil { return } + if output.preferred, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.finalized, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.liked, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.confirmed, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.rejected, err = marshalUtil.ReadBool(); err != nil { + return + } output.consumerCount = int(consumerCount) balanceCount, err := marshalUtil.ReadUint32() if err != nil { diff --git a/dapps/valuetransfers/packages/tangle/payloadmetadata.go b/dapps/valuetransfers/packages/tangle/payloadmetadata.go index 11ce6e61e28a5c4010da0d102367f57707b796aa..6c65c2ac5e1fa48f66dc64380021bef63525cd89 100644 --- a/dapps/valuetransfers/packages/tangle/payloadmetadata.go +++ b/dapps/valuetransfers/packages/tangle/payloadmetadata.go @@ -249,8 +249,8 @@ func (payloadMetadata *PayloadMetadata) BranchID() branchmanager.BranchID { return payloadMetadata.branchID } -// SetBranchID is the setter for the BranchID that the corresponding Payload is booked into. -func (payloadMetadata *PayloadMetadata) SetBranchID(branchID branchmanager.BranchID) (modified bool) { +// setBranchID is the setter for the BranchID that the corresponding Payload is booked into. +func (payloadMetadata *PayloadMetadata) setBranchID(branchID branchmanager.BranchID) (modified bool) { payloadMetadata.branchIDMutex.RLock() if branchID == payloadMetadata.branchID { payloadMetadata.branchIDMutex.RUnlock() diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go index cdfca6eaf489685b427b10120737ad92acf5fe73..6b0fa17d077f11125bf4e75a429178b1c3b1d0fa 100644 --- a/dapps/valuetransfers/packages/tangle/tangle.go +++ b/dapps/valuetransfers/packages/tangle/tangle.go @@ -38,8 +38,7 @@ type Tangle struct { Events Events - workerPool async.WorkerPool - cleanupWorkerPool async.WorkerPool + workerPool async.WorkerPool } // New is the constructor of a Tangle and creates a new Tangle object from the given details. @@ -63,6 +62,9 @@ func New(store kvstore.KVStore) (tangle *Tangle) { } tangle.setupDAGSynchronization() + // TODO: CHANGE BACK TO MULTI THREADING ONCE WE FIXED LOGICAL RACE CONDITIONS + tangle.workerPool.Tune(1) + return } @@ -170,7 +172,7 @@ func (tangle *Tangle) LoadSnapshot(snapshot map[transaction.ID]map[address.Addre for outputAddress, balances := range addressBalances { input := NewOutput(outputAddress, transactionID, branchmanager.MasterBranchID, balances) input.setSolid(true) - input.SetBranchID(branchmanager.MasterBranchID) + input.setBranchID(branchmanager.MasterBranchID) // store output and abort if the snapshot has already been loaded earlier (output exists in the database) cachedOutput, stored := tangle.outputStorage.StoreIfAbsent(input) @@ -267,7 +269,6 @@ func (tangle *Tangle) Prune() (err error) { // Shutdown stops the worker pools and shuts down the object storage instances. func (tangle *Tangle) Shutdown() *Tangle { tangle.workerPool.ShutdownGracefully() - tangle.cleanupWorkerPool.ShutdownGracefully() for _, storage := range []*objectstorage.ObjectStorage{ tangle.payloadStorage, @@ -543,10 +544,21 @@ func (tangle *Tangle) setTransactionFinalized(transactionID transaction.ID, even cachedTransactionMetadata := tangle.TransactionMetadata(transactionID) cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { // update the finalized flag of the transaction - modified = metadata.SetFinalized(true) + modified = metadata.setFinalized(true) // only propagate the changes if the flag was modified if modified { + // set outputs to be finalized as well + tangle.Transaction(transactionID).Consume(func(tx *transaction.Transaction) { + tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool { + tangle.TransactionOutput(transaction.NewOutputID(address, transactionID)).Consume(func(output *Output) { + output.setFinalized(true) + }) + + return true + }) + }) + // retrieve transaction from the database (for the events) cachedTransaction := tangle.Transaction(transactionID) defer cachedTransaction.Release() @@ -608,22 +620,36 @@ func (tangle *Tangle) propagateRejectedToTransactions(transactionID transaction. cachedTransactionMetadata := tangle.TransactionMetadata(currentTransactionID) cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { - if !metadata.setRejected(true) { - return - } - metadata.setPreferred(false) + cachedTransaction := tangle.Transaction(currentTransactionID) + cachedTransaction.Consume(func(tx *transaction.Transaction) { + if !metadata.setRejected(true) { + return + } - // if the transaction is not finalized, yet then we set it to finalized - if !metadata.Finalized() { - if _, err := tangle.setTransactionFinalized(metadata.ID(), EventSourceTangle); err != nil { - tangle.Events.Error.Trigger(err) + if metadata.setPreferred(false) { + // set outputs to be not preferred as well + tangle.Transaction(currentTransactionID).Consume(func(tx *transaction.Transaction) { + tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool { + tangle.TransactionOutput(transaction.NewOutputID(address, currentTransactionID)).Consume(func(output *Output) { + output.setPreferred(false) + }) - return + return true + }) + }) + + tangle.Events.TransactionUnpreferred.Trigger(cachedTransaction, cachedTransactionMetadata) + } + + // if the transaction is not finalized, yet then we set it to finalized + if !metadata.Finalized() { + if _, err := tangle.setTransactionFinalized(metadata.ID(), EventSourceTangle); err != nil { + tangle.Events.Error.Trigger(err) + + return + } } - } - cachedTransaction := tangle.Transaction(currentTransactionID) - cachedTransaction.Consume(func(tx *transaction.Transaction) { // process all outputs tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool { outputID := transaction.NewOutputID(address, currentTransactionID) @@ -740,6 +766,17 @@ func (tangle *Tangle) setTransactionPreferred(transactionID transaction.ID, pref // only do something if the flag was modified if modified { + // update outputs as well + tangle.Transaction(transactionID).Consume(func(tx *transaction.Transaction) { + tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool { + tangle.TransactionOutput(transaction.NewOutputID(address, transactionID)).Consume(func(output *Output) { + output.setPreferred(preferred) + }) + + return true + }) + }) + // retrieve transaction from the database (for the events) cachedTransaction := tangle.Transaction(transactionID) defer cachedTransaction.Release() @@ -1219,7 +1256,7 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans } // abort if transaction was marked as solid before - if !transactionMetadata.SetSolid(true) { + if !transactionMetadata.setSolid(true) { return } @@ -1230,6 +1267,7 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans conflictingInputs := make([]transaction.OutputID, 0) conflictingInputsOfFirstConsumers := make(map[transaction.ID][]transaction.OutputID) + finalizedConflictingSpenderFound := false if !transactionToBook.Inputs().ForEach(func(outputID transaction.OutputID) bool { cachedOutput := tangle.TransactionOutput(outputID) defer cachedOutput.Release() @@ -1260,6 +1298,17 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans conflictingInputsOfFirstConsumers[firstConsumerID] = append(conflictingInputsOfFirstConsumers[firstConsumerID], outputID) } + // check if any of the consumers were finalized already + if !finalizedConflictingSpenderFound { + tangle.Consumers(outputID).Consume(func(consumer *Consumer) { + if !finalizedConflictingSpenderFound { + tangle.TransactionMetadata(consumer.TransactionID()).Consume(func(metadata *TransactionMetadata) { + finalizedConflictingSpenderFound = metadata.Preferred() && metadata.Finalized() + }) + } + }) + } + // mark input as conflicting conflictingInputs = append(conflictingInputs, outputID) @@ -1295,7 +1344,7 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans } // book transaction into target branch - transactionMetadata.SetBranchID(targetBranch.ID()) + transactionMetadata.setBranchID(targetBranch.ID()) // create color for newly minted coins mintedColor, _, err := balance.ColorFromBytes(transactionToBook.ID().Bytes()) @@ -1323,16 +1372,19 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans return true }) - // fork the conflicting transactions into their own branch - for consumerID, conflictingInputs := range conflictingInputsOfFirstConsumers { - _, decisionFinalized, forkedErr := tangle.Fork(consumerID, conflictingInputs) - if forkedErr != nil { - err = forkedErr + // fork the conflicting transactions into their own branch if a decision is still pending + decisionPending = !finalizedConflictingSpenderFound + if decisionPending { + for consumerID, conflictingInputs := range conflictingInputsOfFirstConsumers { + _, decisionFinalized, forkedErr := tangle.Fork(consumerID, conflictingInputs) + if forkedErr != nil { + err = forkedErr - return - } + return + } - decisionPending = decisionPending || !decisionFinalized + decisionPending = decisionPending || !decisionFinalized + } } transactionBooked = true @@ -1379,7 +1431,7 @@ func (tangle *Tangle) bookPayload(cachedPayload *payload.CachedPayload, cachedPa return } - payloadBooked = valueObjectMetadata.SetBranchID(aggregatedBranch.ID()) + payloadBooked = valueObjectMetadata.setBranchID(aggregatedBranch.ID()) return } @@ -1712,7 +1764,7 @@ func (tangle *Tangle) moveTransactionToBranch(cachedTransaction *transaction.Cac } // abort if we did not modify the branch of the transaction - if !currentTransactionMetadata.SetBranchID(targetBranch.ID()) { + if !currentTransactionMetadata.setBranchID(targetBranch.ID()) { return nil } @@ -1737,7 +1789,7 @@ func (tangle *Tangle) moveTransactionToBranch(cachedTransaction *transaction.Cac } // abort if the output was moved already - if !output.SetBranchID(targetBranch.ID()) { + if !output.setBranchID(targetBranch.ID()) { return true } @@ -1813,7 +1865,7 @@ func (tangle *Tangle) updateBranchOfValuePayloadsAttachingTransaction(transactio // try to update the metadata of the payload and queue its approvers cachedAggregatedBranch.Consume(func(branch *branchmanager.Branch) { tangle.PayloadMetadata(currentPayload.ID()).Consume(func(payloadMetadata *PayloadMetadata) { - if !payloadMetadata.SetBranchID(branch.ID()) { + if !payloadMetadata.setBranchID(branch.ID()) { return } diff --git a/dapps/valuetransfers/packages/tangle/tangle_test.go b/dapps/valuetransfers/packages/tangle/tangle_test.go index dab6589cc4d360d0fa88d0651f74a08000749a05..0849dc3ff9a58ea3f837bb8b455b941a5878b99b 100644 --- a/dapps/valuetransfers/packages/tangle/tangle_test.go +++ b/dapps/valuetransfers/packages/tangle/tangle_test.go @@ -59,7 +59,7 @@ func TestBookTransaction(t *testing.T) { cachedTransaction, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) transactionMetadata := cachedTransactionMetadata.Unwrap() - transactionMetadata.SetSolid(true) + transactionMetadata.setSolid(true) transactionBooked, decisionPending, err := tangle.bookTransaction(cachedTransaction, cachedTransactionMetadata) require.NoError(t, err) @@ -106,7 +106,7 @@ func TestBookTransaction(t *testing.T) { transactionBooked, decisionPending, err := tangle.bookTransaction(cachedTransaction, cachedTransactionMetadata) require.NoError(t, err) assert.True(t, transactionBooked, "transactionBooked") - assert.False(t, decisionPending, "decisionPending") + assert.True(t, decisionPending, "decisionPending") // assert that branchID is the same as the MasterBranchID assert.Equal(t, branchmanager.MasterBranchID, txMetadata.BranchID()) @@ -257,7 +257,7 @@ func TestFork(t *testing.T) { _, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() - txMetadata.SetFinalized(true) + txMetadata.setFinalized(true) forked, finalized, err := tangle.Fork(tx.ID(), []transaction.OutputID{}) require.NoError(t, err) @@ -334,12 +334,12 @@ func TestBookPayload(t *testing.T) { cachedPayload, cachedMetadata, _ := tangle.storePayload(valueObject) metadata := cachedMetadata.Unwrap() - metadata.SetBranchID(branchmanager.BranchID{1}) - metadata.SetBranchID(branchmanager.BranchID{2}) + metadata.setBranchID(branchmanager.BranchID{1}) + metadata.setBranchID(branchmanager.BranchID{2}) _, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() - txMetadata.SetBranchID(branchmanager.BranchID{1}) + txMetadata.setBranchID(branchmanager.BranchID{1}) payloadBooked, err := tangle.bookPayload(cachedPayload.Retain(), cachedMetadata.Retain(), cachedTransactionMetadata.Retain()) defer func() { @@ -359,12 +359,12 @@ func TestBookPayload(t *testing.T) { cachedPayload, cachedMetadata, _ := tangle.storePayload(valueObject) metadata := cachedMetadata.Unwrap() - metadata.SetBranchID(branchmanager.BranchID{1}) - metadata.SetBranchID(branchmanager.BranchID{1}) + metadata.setBranchID(branchmanager.BranchID{1}) + metadata.setBranchID(branchmanager.BranchID{1}) _, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() - txMetadata.SetBranchID(branchmanager.BranchID{1}) + txMetadata.setBranchID(branchmanager.BranchID{1}) payloadBooked, err := tangle.bookPayload(cachedPayload.Retain(), cachedMetadata.Retain(), cachedTransactionMetadata.Retain()) defer func() { @@ -1045,8 +1045,8 @@ func TestCheckTransactionSolidity(t *testing.T) { tangle := New(mapdb.NewMapDB()) tx := createDummyTransaction() txMetadata := NewTransactionMetadata(tx.ID()) - txMetadata.SetSolid(true) - txMetadata.SetBranchID(branchmanager.MasterBranchID) + txMetadata.setSolid(true) + txMetadata.setBranchID(branchmanager.MasterBranchID) solid, consumedBranches, err := tangle.checkTransactionSolidity(tx, txMetadata) assert.True(t, solid) @@ -1216,7 +1216,7 @@ func TestPayloadBranchID(t *testing.T) { expectedBranchID := branchmanager.BranchID{1} cachedMetadata.Consume(func(metadata *PayloadMetadata) { metadata.setSolid(true) - metadata.SetBranchID(expectedBranchID) + metadata.setBranchID(expectedBranchID) }) branchID := tangle.payloadBranchID(valueObject.ID()) @@ -1245,7 +1245,7 @@ func TestCheckPayloadSolidity(t *testing.T) { valueObject := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction()) metadata := NewPayloadMetadata(valueObject.ID()) metadata.setSolid(true) - metadata.SetBranchID(branchmanager.MasterBranchID) + metadata.setBranchID(branchmanager.MasterBranchID) transactionBranches := []branchmanager.BranchID{branchmanager.MasterBranchID} solid, err := tangle.payloadBecameNewlySolid(valueObject, metadata, transactionBranches) @@ -1268,7 +1268,7 @@ func TestCheckPayloadSolidity(t *testing.T) { { setParent := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.MasterBranchID) + payloadMetadata.setBranchID(branchmanager.MasterBranchID) } valueObject := payload.New(storeParentPayloadWithMetadataFunc(t, tangle, setParent), storeParentPayloadWithMetadataFunc(t, tangle, setParent), createDummyTransaction()) @@ -1304,11 +1304,11 @@ func TestCheckPayloadSolidity(t *testing.T) { defer cachedBranch3.Release() setParent1 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.BranchID{2}) + payloadMetadata.setBranchID(branchmanager.BranchID{2}) } setParent2 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.BranchID{3}) + payloadMetadata.setBranchID(branchmanager.BranchID{3}) } valueObject := payload.New(storeParentPayloadWithMetadataFunc(t, tangle, setParent1), storeParentPayloadWithMetadataFunc(t, tangle, setParent2), createDummyTransaction()) @@ -1329,11 +1329,11 @@ func TestCheckPayloadSolidity(t *testing.T) { defer cachedBranch3.Release() setParent1 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.MasterBranchID) + payloadMetadata.setBranchID(branchmanager.MasterBranchID) } setParent2 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.BranchID{3}) + payloadMetadata.setBranchID(branchmanager.BranchID{3}) } valueObject := payload.New(storeParentPayloadWithMetadataFunc(t, tangle, setParent1), storeParentPayloadWithMetadataFunc(t, tangle, setParent2), createDummyTransaction()) diff --git a/dapps/valuetransfers/packages/tangle/transactionmetadata.go b/dapps/valuetransfers/packages/tangle/transactionmetadata.go index 01b161613ae30fcb8d4a98e2e337c3a9723f370e..106075bafe490af586673c960e379a8b8430a4dc 100644 --- a/dapps/valuetransfers/packages/tangle/transactionmetadata.go +++ b/dapps/valuetransfers/packages/tangle/transactionmetadata.go @@ -113,8 +113,8 @@ func (transactionMetadata *TransactionMetadata) BranchID() branchmanager.BranchI return transactionMetadata.branchID } -// SetBranchID is the setter for the branch id. It returns true if the value of the flag has been updated. -func (transactionMetadata *TransactionMetadata) SetBranchID(branchID branchmanager.BranchID) (modified bool) { +// setBranchID is the setter for the branch id. It returns true if the value of the flag has been updated. +func (transactionMetadata *TransactionMetadata) setBranchID(branchID branchmanager.BranchID) (modified bool) { transactionMetadata.branchIDMutex.RLock() if transactionMetadata.branchID == branchID { transactionMetadata.branchIDMutex.RUnlock() @@ -150,9 +150,9 @@ func (transactionMetadata *TransactionMetadata) Solid() (result bool) { return } -// SetSolid marks a Transaction as either solid or not solid. +// setSolid marks a Transaction as either solid or not solid. // It returns true if the solid flag was changes and automatically updates the solidificationTime as well. -func (transactionMetadata *TransactionMetadata) SetSolid(solid bool) (modified bool) { +func (transactionMetadata *TransactionMetadata) setSolid(solid bool) (modified bool) { transactionMetadata.solidMutex.RLock() if transactionMetadata.solid != solid { transactionMetadata.solidMutex.RUnlock() @@ -213,9 +213,9 @@ func (transactionMetadata *TransactionMetadata) setPreferred(preferred bool) (mo return } -// SetFinalized allows us to set the finalized flag on the transactions. Finalized transactions will not be forked when +// setFinalized allows us to set the finalized flag on the transactions. Finalized transactions will not be forked when // a conflict arrives later. -func (transactionMetadata *TransactionMetadata) SetFinalized(finalized bool) (modified bool) { +func (transactionMetadata *TransactionMetadata) setFinalized(finalized bool) (modified bool) { transactionMetadata.finalizedMutex.RLock() if transactionMetadata.finalized == finalized { transactionMetadata.finalizedMutex.RUnlock() diff --git a/packages/gossip/manager.go b/packages/gossip/manager.go index 9ec15421dc2bb233f4735ca962264e773c286b5d..5be3fe27562ea7c30a068c8cc738fe27dc192c59 100644 --- a/packages/gossip/manager.go +++ b/packages/gossip/manager.go @@ -9,6 +9,7 @@ import ( "github.com/iotaledger/goshimmer/packages/binary/messagelayer/message" pb "github.com/iotaledger/goshimmer/packages/gossip/proto" "github.com/iotaledger/goshimmer/packages/gossip/server" + "github.com/iotaledger/hive.go/async" "github.com/iotaledger/hive.go/autopeering/peer" "github.com/iotaledger/hive.go/events" "github.com/iotaledger/hive.go/identity" @@ -34,6 +35,9 @@ type Manager struct { mu sync.Mutex srv *server.TCP neighbors map[identity.ID]*Neighbor + + // inboxWorkerPool defines a worker pool where all incoming messages are processed. + inboxWorkerPool async.WorkerPool } // NewManager creates a new Manager. @@ -65,6 +69,8 @@ func (m *Manager) Start(srv *server.TCP) { func (m *Manager) Close() { m.stop() m.wg.Wait() + + m.inboxWorkerPool.ShutdownGracefully() } // Events returns the events related to the gossip protocol. @@ -206,9 +212,13 @@ func (m *Manager) addNeighbor(peer *peer.Peer, connectorFunc func(*peer.Peer) (n m.events.NeighborRemoved.Trigger(n) })) n.Events.ReceiveMessage.Attach(events.NewClosure(func(data []byte) { - if err := m.handlePacket(data, peer); err != nil { - m.log.Debugw("error handling packet", "err", err) - } + dataCopy := make([]byte, len(data)) + copy(dataCopy, data) + m.inboxWorkerPool.Submit(func() { + if err := m.handlePacket(dataCopy, peer); err != nil { + m.log.Debugw("error handling packet", "err", err) + } + }) })) m.neighbors[peer.ID()] = n diff --git a/pluginmgr/core/plugins.go b/pluginmgr/core/plugins.go index 1eba3f117348d559b68e2f677c8789f23f58a082..eb4ff3b619c6ea1cd48a61d4325d54ddf5dd7241 100644 --- a/pluginmgr/core/plugins.go +++ b/pluginmgr/core/plugins.go @@ -18,6 +18,7 @@ import ( "github.com/iotaledger/goshimmer/plugins/portcheck" "github.com/iotaledger/goshimmer/plugins/profiling" "github.com/iotaledger/goshimmer/plugins/sync" + "github.com/iotaledger/goshimmer/plugins/testsnapshots" "github.com/iotaledger/hive.go/node" ) @@ -40,4 +41,5 @@ var PLUGINS = node.Plugins( metrics.Plugin, drng.Plugin, valuetransfers.App, + testsnapshots.Plugin, ) diff --git a/plugins/testsnapshots/plugin.go b/plugins/testsnapshots/plugin.go new file mode 100644 index 0000000000000000000000000000000000000000..294e53378b9260a77104600485b5d513a3ec1927 --- /dev/null +++ b/plugins/testsnapshots/plugin.go @@ -0,0 +1,41 @@ +package testsnapshots + +import ( + "github.com/iotaledger/goshimmer/dapps/valuetransfers" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" + "github.com/iotaledger/hive.go/logger" + "github.com/iotaledger/hive.go/node" +) + +// FIXME: This plugin can be removed after snapshots is implemented +const ( + // PluginName is the plugin name of the TestSnapshots plugin. + PluginName = "TestSnapshots" +) + +var ( + // Plugin is the plugin instance of the TestSnapshots plugin. + Plugin = node.NewPlugin(PluginName, node.Disabled, configure, run) + log *logger.Logger + + // addresses for snapshots + address0, _ = address.FromBase58("JaMauTaTSVBNc13edCCvBK9fZxZ1KKW5fXegT1B7N9jY") +) + +func configure(_ *node.Plugin) { + log = logger.NewLogger(PluginName) + + valuetransfers.Tangle.LoadSnapshot(map[transaction.ID]map[address.Address][]*balance.Balance{ + transaction.GenesisID: { + address0: []*balance.Balance{ + balance.New(balance.ColorIOTA, 10000000), + }, + }, + }) + + log.Infof("load snapshots to tangle") +} + +func run(_ *node.Plugin) {} diff --git a/tools/integration-tests/runTests.sh b/tools/integration-tests/runTests.sh index 71a65da52f4cf9fb0441d8caeef39e2d578b8c0a..28cb55b9642c62fe3647e744645baa062ac4c97d 100755 --- a/tools/integration-tests/runTests.sh +++ b/tools/integration-tests/runTests.sh @@ -1,6 +1,6 @@ #!/bin/bash -TEST_NAMES='autopeering common drng message' +TEST_NAMES='autopeering common drng message value' echo "Build GoShimmer image" docker build -t iotaledger/goshimmer ../../. diff --git a/tools/integration-tests/tester/framework/docker.go b/tools/integration-tests/tester/framework/docker.go index a5c2cb2493fc6732b013f8d608caf8d8003e2926..67a65d5838ee769633e6df23e7ae77a0508b56d8 100644 --- a/tools/integration-tests/tester/framework/docker.go +++ b/tools/integration-tests/tester/framework/docker.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "strings" "time" "github.com/docker/docker/api/types" @@ -83,10 +84,16 @@ func (d *DockerContainer) CreateGoShimmerPeer(config GoShimmerConfig) error { "--logger.level=debug", fmt.Sprintf("--node.disablePlugins=%s", config.DisabledPlugins), fmt.Sprintf("--node.enablePlugins=%s", func() string { + var plugins []string + //TODO: remove this when snapshots is implemented + plugins = append(plugins, "testSnapshots") if config.Bootstrap { - return "Bootstrap" + plugins = append(plugins, "Bootstrap") } - return "" + if config.Faucet { + plugins = append(plugins, "faucet") + } + return strings.Join(plugins[:], ",") }()), fmt.Sprintf("--bootstrap.initialIssuance.timePeriodSec=%d", config.BootstrapInitialIssuanceTimePeriodSec), "--webapi.bindAddress=0.0.0.0:8080", diff --git a/tools/integration-tests/tester/framework/drngnetwork.go b/tools/integration-tests/tester/framework/drngnetwork.go index 9df42cdb73c4b856cc828e13095c72fc1b20a318..2f3031ceab001dc7d8e415bb41dce956673cf7b5 100644 --- a/tools/integration-tests/tester/framework/drngnetwork.go +++ b/tools/integration-tests/tester/framework/drngnetwork.go @@ -55,7 +55,7 @@ func (n *DRNGNetwork) CreatePeer(c GoShimmerConfig, publicKey ed25519.PublicKey) return nil, err } - peer, err := newPeer(name, identity.New(publicKey), container, n.network) + peer, err := newPeer(name, identity.New(publicKey), container, nil, n.network) if err != nil { return nil, err } diff --git a/tools/integration-tests/tester/framework/framework.go b/tools/integration-tests/tester/framework/framework.go index 449c2dc6d07993d3bc722f342c21731e0c6252f0..d4456d5740694f9ffb31fbe9f01d2f73715cb871 100644 --- a/tools/integration-tests/tester/framework/framework.go +++ b/tools/integration-tests/tester/framework/framework.go @@ -82,6 +82,7 @@ func (f *Framework) CreateNetwork(name string, peers int, minimumNeighbors int, config := GoShimmerConfig{ Bootstrap: i == 0, BootstrapInitialIssuanceTimePeriodSec: bootstrapInitialIssuanceTimePeriodSec, + Faucet: i == 0, } if _, err = network.CreatePeer(config); err != nil { return nil, err diff --git a/tools/integration-tests/tester/framework/network.go b/tools/integration-tests/tester/framework/network.go index c115fcb5a364e57a59183f05ae6b2cedc170c817..4580588fedf103c88770044e22cf1cb7c8b6babd 100644 --- a/tools/integration-tests/tester/framework/network.go +++ b/tools/integration-tests/tester/framework/network.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/client" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" "github.com/iotaledger/hive.go/crypto/ed25519" "github.com/iotaledger/hive.go/identity" ) @@ -100,6 +101,14 @@ func (n *Network) CreatePeer(c GoShimmerConfig) (*Peer, error) { config.EntryNodePublicKey = n.entryNodePublicKey() config.DisabledPlugins = disabledPluginsPeer + // create wallet + var nodeWallet *wallet.Wallet + if c.Faucet == true { + nodeWallet = wallet.New(faucetSeed) + } else { + nodeWallet = wallet.New() + } + // create Docker container container := NewDockerContainer(n.dockerClient) err = container.CreateGoShimmerPeer(config) @@ -115,7 +124,7 @@ func (n *Network) CreatePeer(c GoShimmerConfig) (*Peer, error) { return nil, err } - peer, err := newPeer(name, identity.New(publicKey), container, n) + peer, err := newPeer(name, identity.New(publicKey), container, nodeWallet, n) if err != nil { return nil, err } diff --git a/tools/integration-tests/tester/framework/parameters.go b/tools/integration-tests/tester/framework/parameters.go index 6e2ae8c4719bc7ffe388474f9d3094e6f6cba0bd..ddbb70469104b225da28d7e7fff964da3c06d7a0 100644 --- a/tools/integration-tests/tester/framework/parameters.go +++ b/tools/integration-tests/tester/framework/parameters.go @@ -13,7 +13,7 @@ const ( logsDir = "/tmp/logs/" - disabledPluginsEntryNode = "portcheck,dashboard,analysis-client,profiling,gossip,drng,issuer,sync,metrics,valuetransfers,messagelayer,webapi,webapibroadcastdataendpoint,webapifindtransactionhashesendpoint,webapigetneighborsendpoint,webapigettransactionobjectsbyhashendpoint,webapigettransactiontrytesbyhashendpoint" + disabledPluginsEntryNode = "portcheck,dashboard,analysis-client,profiling,gossip,drng,issuer,sync,metrics,valuetransfers,testsnapshots,messagelayer,webapi,webapibroadcastdataendpoint,webapifindtransactionhashesendpoint,webapigetneighborsendpoint,webapigettransactionobjectsbyhashendpoint,webapigettransactiontrytesbyhashendpoint" disabledPluginsPeer = "portcheck,dashboard,analysis-client,profiling" dockerLogsPrefixLen = 8 @@ -23,6 +23,11 @@ const ( exitStatusSuccessful = 0 ) +var ( + faucetSeed = []byte{251, 163, 190, 98, 92, 82, 164, 79, 74, 48, 203, 162, 247, 119, 140, 76, 33, 100, 148, 204, 244, 248, 232, 18, + 132, 217, 85, 31, 246, 83, 193, 193} +) + // GoShimmerConfig defines the config of a GoShimmer node. type GoShimmerConfig struct { Seed string @@ -38,6 +43,8 @@ type GoShimmerConfig struct { DRNGDistKey string DRNGInstance int DRNGThreshold int + + Faucet bool } // NetworkConfig defines the config of a GoShimmer Docker network. diff --git a/tools/integration-tests/tester/framework/peer.go b/tools/integration-tests/tester/framework/peer.go index 18c059e8d91d6d07b2131fbf620d0b35f45d39ea..8da31c20ef4634f2dc408034b5046a9b7b589712 100644 --- a/tools/integration-tests/tester/framework/peer.go +++ b/tools/integration-tests/tester/framework/peer.go @@ -6,6 +6,7 @@ import ( "time" "github.com/iotaledger/goshimmer/client" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" "github.com/iotaledger/goshimmer/plugins/webapi/autopeering" "github.com/iotaledger/hive.go/identity" ) @@ -24,13 +25,16 @@ type Peer struct { // the DockerContainer that this peer is running in *DockerContainer + // Wallet + *wallet.Wallet + chosen []autopeering.Neighbor accepted []autopeering.Neighbor } // newPeer creates a new instance of Peer with the given information. // dockerContainer needs to be started in order to determine the container's (and therefore peer's) IP correctly. -func newPeer(name string, identity *identity.Identity, dockerContainer *DockerContainer, network *Network) (*Peer, error) { +func newPeer(name string, identity *identity.Identity, dockerContainer *DockerContainer, wallet *wallet.Wallet, network *Network) (*Peer, error) { // after container is started we can get its IP ip, err := dockerContainer.IP(network.name) if err != nil { @@ -43,6 +47,7 @@ func newPeer(name string, identity *identity.Identity, dockerContainer *DockerCo Identity: identity, GoShimmerAPI: client.NewGoShimmerAPI(getWebAPIBaseURL(name), http.Client{Timeout: 30 * time.Second}), DockerContainer: dockerContainer, + Wallet: wallet, }, nil } diff --git a/tools/integration-tests/tester/tests/common/common_test.go b/tools/integration-tests/tester/tests/common/common_test.go index 7cdceef806dd0823a35e9e80ccbceadf25c0e15d..32f4fea32eea5fcbe1afd1b2ba9856fe258dbbf7 100644 --- a/tools/integration-tests/tester/tests/common/common_test.go +++ b/tools/integration-tests/tester/tests/common/common_test.go @@ -1,4 +1,4 @@ -package autopeering +package common import ( "testing" diff --git a/tools/integration-tests/tester/tests/common/main_test.go b/tools/integration-tests/tester/tests/common/main_test.go index bcc09c2e53590df061cbff5a2159071d567440f1..cdffe476461e98e312c15ef4dd3f001c67e1faf4 100644 --- a/tools/integration-tests/tester/tests/common/main_test.go +++ b/tools/integration-tests/tester/tests/common/main_test.go @@ -1,4 +1,4 @@ -package autopeering +package common import ( "os" diff --git a/tools/integration-tests/tester/tests/drng/drng_test.go b/tools/integration-tests/tester/tests/drng/drng_test.go index a9b59a9f94f84126257e0e7892119d12300f39bf..4db5911975cc4a2b89b6ab892faccfd30bea1f50 100644 --- a/tools/integration-tests/tester/tests/drng/drng_test.go +++ b/tools/integration-tests/tester/tests/drng/drng_test.go @@ -1,4 +1,4 @@ -package autopeering +package drng import ( "encoding/json" diff --git a/tools/integration-tests/tester/tests/drng/main_test.go b/tools/integration-tests/tester/tests/drng/main_test.go index bcc09c2e53590df061cbff5a2159071d567440f1..27877125211c397ac15776329b7a87ce5cb2c9f9 100644 --- a/tools/integration-tests/tester/tests/drng/main_test.go +++ b/tools/integration-tests/tester/tests/drng/main_test.go @@ -1,4 +1,4 @@ -package autopeering +package drng import ( "os" diff --git a/tools/integration-tests/tester/tests/message/main_test.go b/tools/integration-tests/tester/tests/message/main_test.go index bcc09c2e53590df061cbff5a2159071d567440f1..4d3e5451fbcbed76b446721062a23f6c515d1dbf 100644 --- a/tools/integration-tests/tester/tests/message/main_test.go +++ b/tools/integration-tests/tester/tests/message/main_test.go @@ -1,4 +1,4 @@ -package autopeering +package message import ( "os" diff --git a/tools/integration-tests/tester/tests/message/message_test.go b/tools/integration-tests/tester/tests/message/message_test.go index 37460d75389b960b89b302870f7bf32fa1026cde..11315ebb0804713a320a751d923445bf512b2659 100644 --- a/tools/integration-tests/tester/tests/message/message_test.go +++ b/tools/integration-tests/tester/tests/message/message_test.go @@ -1,4 +1,4 @@ -package autopeering +package message import ( "testing" diff --git a/tools/integration-tests/tester/tests/testutil.go b/tools/integration-tests/tester/tests/testutil.go index 9e11ee44d83c13f5f9108ce43998198d0e800bb8..b47c5c12b1a612eb5696bd8695d5fb9b77d9591f 100644 --- a/tools/integration-tests/tester/tests/testutil.go +++ b/tools/integration-tests/tester/tests/testutil.go @@ -4,13 +4,20 @@ import ( "fmt" "math/rand" "testing" + "time" + "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" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" "github.com/iotaledger/goshimmer/packages/binary/messagelayer/payload" "github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +const maxRetry = 50 + // DataMessageSent defines a struct to identify from which issuer a data message was sent. type DataMessageSent struct { number int @@ -47,7 +54,7 @@ func SendDataMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, numMess // SendDataMessage sends a data message on a given peer and returns the id and a DataMessageSent struct. func SendDataMessage(t *testing.T, peer *framework.Peer, data []byte, number int) (string, DataMessageSent) { id, err := peer.Data(data) - require.NoErrorf(t, err, "Could not send message on %s", peer.String()) + require.NoErrorf(t, err, "could not send message on %s", peer.String()) sent := DataMessageSent{ number: number, @@ -95,6 +102,280 @@ func CheckForMessageIds(t *testing.T, peers []*framework.Peer, ids map[string]Da } } +// SendTransactionFromFaucet sends funds to peers from the faucet, sends back the remainder to faucet, and returns the transaction ID. +func SendTransactionFromFaucet(t *testing.T, peers []*framework.Peer, sentValue int64) (txIds []string, addrBalance map[string]map[balance.Color]int64) { + // initiate addrBalance map + addrBalance = make(map[string]map[balance.Color]int64) + for _, p := range peers { + addr := p.Seed().Address(0).String() + addrBalance[addr] = make(map[balance.Color]int64) + } + + faucetPeer := peers[0] + faucetAddrStr := faucetPeer.Seed().Address(0).String() + + // get faucet balances + unspentOutputs, err := faucetPeer.GetUnspentOutputs([]string{faucetAddrStr}) + require.NoErrorf(t, err, "could not get unspent outputs on %s", faucetPeer.String()) + addrBalance[faucetAddrStr][balance.ColorIOTA] = unspentOutputs.UnspentOutputs[0].OutputIDs[0].Balances[0].Value + + // send funds to other peers + for i := 1; i < len(peers); i++ { + fail, txId := SendIotaTransaction(t, faucetPeer, peers[i], addrBalance, sentValue) + require.False(t, fail) + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(3 * time.Second) + } + + return +} + +// SendTransactionOnRandomPeer sends sentValue amount of IOTA tokens from/to a random peer, mutates the given balance map and returns the transaction IDs. +func SendTransactionOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int, sentValue int64) (txIds []string) { + counter := 0 + for i := 0; i < numMessages; i++ { + from := rand.Intn(len(peers)) + to := rand.Intn(len(peers)) + fail, txId := SendIotaTransaction(t, peers[from], peers[to], addrBalance, sentValue) + if fail { + i-- + counter++ + if counter >= maxRetry { + return + } + continue + } + + // attach tx id + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(3 * time.Second) + } + + return +} + +// SendIotaTransaction sends sentValue amount of IOTA tokens and remainders from and to a given peer and returns the fail flag and the transaction ID. +// Every peer sends and receives the transaction on the address of index 0. +func SendIotaTransaction(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64, sentValue int64) (fail bool, txId string) { + sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0)) + inputAddr := from.Seed().Address(0) + outputAddr := to.Seed().Address(0) + + // prepare inputs + resp, err := from.GetUnspentOutputs([]string{inputAddr.String()}) + require.NoErrorf(t, err, "could not get unspent outputs on %s", from.String()) + + // abort if no unspent outputs + if len(resp.UnspentOutputs[0].OutputIDs) == 0 { + return true, "" + } + availableValue := resp.UnspentOutputs[0].OutputIDs[0].Balances[0].Value + + // abort if the balance is not enough + if availableValue < sentValue { + return true, "" + } + + out, err := transaction.OutputIDFromBase58(resp.UnspentOutputs[0].OutputIDs[0].ID) + require.NoErrorf(t, err, "invalid unspent outputs ID on %s", from.String()) + inputs := transaction.NewInputs([]transaction.OutputID{out}...) + + // prepare outputs + outmap := map[address.Address][]*balance.Balance{} + if inputAddr == outputAddr { + sentValue = availableValue + } + + // set balances + outmap[outputAddr] = []*balance.Balance{balance.New(balance.ColorIOTA, sentValue)} + outputs := transaction.NewOutputs(outmap) + + // handle remainder address + if availableValue > sentValue { + outputs.Add(inputAddr, []*balance.Balance{balance.New(balance.ColorIOTA, availableValue-sentValue)}) + } + + // sign transaction + txn := transaction.New(inputs, outputs).Sign(sigScheme) + + // send transaction + txId, err = from.SendTransaction(txn.Bytes()) + require.NoErrorf(t, err, "could not send transaction on %s", from.String()) + + addrBalance[inputAddr.String()][balance.ColorIOTA] -= sentValue + addrBalance[outputAddr.String()][balance.ColorIOTA] += sentValue + + return false, txId +} + +// SendColoredTransactionOnRandomPeer sends colored tokens on a random peer, saves the sent token amount to a map, and returns transaction IDs. +func SendColoredTransactionOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int) (txIds []string) { + counter := 0 + for i := 0; i < numMessages; i++ { + from := rand.Intn(len(peers)) + to := rand.Intn(len(peers)) + fail, txId := SendColoredTransaction(t, peers[from], peers[to], addrBalance) + if fail { + i-- + counter++ + if counter >= maxRetry { + return + } + continue + } + + // attach tx id + txIds = append(txIds, txId) + + // let the transaction propagate + time.Sleep(3 * time.Second) + } + + return +} + +// SendColoredTransaction sends IOTA and colored tokens from and to a given peer and returns the fail flag and the transaction ID. +// 1. Get the first unspent outputs of `from` +// 2. Accumulate the token amount of the first unspent output +// 3. Send 50 IOTA tokens + [accumalate token amount - 50] new minted tokens to `to` +func SendColoredTransaction(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64) (fail bool, txId string) { + var sentValue int64 = 50 + var balanceList []*balance.Balance + sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0)) + inputAddr := from.Seed().Address(0) + outputAddr := to.Seed().Address(0) + + // prepare inputs + resp, err := from.GetUnspentOutputs([]string{inputAddr.String()}) + require.NoErrorf(t, err, "could not get unspent outputs on %s", from.String()) + + // abort if no unspent outputs + if len(resp.UnspentOutputs[0].OutputIDs) == 0 { + return true, "" + } + + // calculate available token in the unspent output + var availableValue int64 = 0 + for _, b := range resp.UnspentOutputs[0].OutputIDs[0].Balances { + availableValue += b.Value + balanceList = append(balanceList, balance.New(getColorFromString(b.Color), (-1)*b.Value)) + } + + // abort if not enough tokens + if availableValue < sentValue { + return true, "" + } + + out, err := transaction.OutputIDFromBase58(resp.UnspentOutputs[0].OutputIDs[0].ID) + require.NoErrorf(t, err, "invalid unspent outputs ID on %s", from.String()) + inputs := transaction.NewInputs([]transaction.OutputID{out}...) + + // prepare outputs + outmap := map[address.Address][]*balance.Balance{} + + // set balances + outmap[outputAddr] = []*balance.Balance{balance.New(balance.ColorIOTA, sentValue)} + if availableValue > sentValue { + outmap[outputAddr] = append(outmap[outputAddr], balance.New(balance.ColorNew, availableValue-sentValue)) + } + outputs := transaction.NewOutputs(outmap) + + // sign transaction + txn := transaction.New(inputs, outputs).Sign(sigScheme) + + // send transaction + txId, err = from.SendTransaction(txn.Bytes()) + require.NoErrorf(t, err, "could not send transaction on %s", from.String()) + + // update balance list + balanceList = append(balanceList, outmap[outputAddr]...) + updateBalanceList(addrBalance, balanceList, inputAddr.String(), outputAddr.String(), txId) + + return false, txId +} + +// updateBalanceList updates the token amount map with given peers and balances. +// If the value of balance is negative, it is the balance to be deducted from peer from, else it is deposited to peer to. +// If the color is balance.ColorNew, it should be recolored with txId. +func updateBalanceList(addrBalance map[string]map[balance.Color]int64, balances []*balance.Balance, from, to, txId string) { + for _, b := range balances { + color := b.Color() + value := b.Value() + if value < 0 { + // deduct + addrBalance[from][color] += value + continue + } + // deposit + if color == balance.ColorNew { + addrBalance[to][getColorFromString(txId)] = value + continue + } + addrBalance[to][color] += value + } + return +} + +func getColorFromString(colorStr string) (color balance.Color) { + if colorStr == "IOTA" { + color = balance.ColorIOTA + } else { + t, _ := transaction.IDFromBase58(colorStr) + color, _, _ = balance.ColorFromBytes(t.Bytes()) + } + return +} + +// CheckBalances performs checks to make sure that all peers have the same ledger state. +func CheckBalances(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64) { + for _, peer := range peers { + for addr, b := range addrBalance { + sum := make(map[balance.Color]int64) + resp, err := peer.GetUnspentOutputs([]string{addr}) + require.NoError(t, err) + assert.Equal(t, addr, resp.UnspentOutputs[0].Address) + + // calculate the balances of each colored coin + for _, unspents := range resp.UnspentOutputs[0].OutputIDs { + for _, respBalance := range unspents.Balances { + color := getColorFromString(respBalance.Color) + sum[color] += respBalance.Value + } + } + + // check balances + for color, value := range sum { + assert.Equal(t, b[color], value) + } + } + } +} + +// CheckTransactions performs checks to make sure that all peers have received all transactions . +func CheckTransactions(t *testing.T, peers []*framework.Peer, transactionIDs []string, checkSynchronized bool) { + for _, peer := range peers { + if checkSynchronized { + // check that the peer sees itself as synchronized + info, err := peer.Info() + require.NoError(t, err) + require.True(t, info.Synced) + } + + for _, txId := range transactionIDs { + resp, err := peer.GetTransactionByID(txId) + require.NoError(t, err) + + // check inclusion state + assert.True(t, resp.InclusionState.Confirmed) + assert.False(t, resp.InclusionState.Rejected) + } + } +} + // ShutdownNetwork shuts down the network and reports errors. func ShutdownNetwork(t *testing.T, n Shutdowner) { err := n.Shutdown() diff --git a/tools/integration-tests/tester/tests/value/main_test.go b/tools/integration-tests/tester/tests/value/main_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f5d00e21e7f402c843a19a25175355c7cead2ab2 --- /dev/null +++ b/tools/integration-tests/tester/tests/value/main_test.go @@ -0,0 +1,23 @@ +package value + +import ( + "os" + "testing" + + "github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework" +) + +var f *framework.Framework + +// TestMain gets called by the test utility and is executed before any other test in this package. +// It is therefore used to initialize the integration testing framework. +func TestMain(m *testing.M) { + var err error + f, err = framework.Instance() + if err != nil { + panic(err) + } + + // call the tests + os.Exit(m.Run()) +} diff --git a/tools/integration-tests/tester/tests/value/value_test.go b/tools/integration-tests/tester/tests/value/value_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a5fd1ea0492ed3a877956631ee7be8cc80fc06be --- /dev/null +++ b/tools/integration-tests/tester/tests/value/value_test.go @@ -0,0 +1,122 @@ +package value + +import ( + "testing" + "time" + + "github.com/iotaledger/goshimmer/dapps/valuetransfers" + "github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests" + "github.com/stretchr/testify/require" +) + +// TestTransactionPersistence issues messages on random peers, restarts them and checks for persistence after restart. +func TestTransactionPersistence(t *testing.T) { + n, err := f.CreateNetwork("transaction_TestPersistence", 4, 2) + require.NoError(t, err) + defer tests.ShutdownNetwork(t, n) + + // wait for peers to change their state to synchronized + time.Sleep(5 * time.Second) + + // faucet node sends 100 IOTA tokens to all peers in the network + txIds, addrBalance := tests.SendTransactionFromFaucet(t, n.Peers(), 100) + + // wait for messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether the first issued transaction is available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // send value message randomly + randomTxIds := tests.SendTransactionOnRandomPeer(t, n.Peers(), addrBalance, 10, 100) + txIds = append(txIds, randomTxIds...) + + // wait for messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether all issued transactions are available on all nodes and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // 3. stop all nodes + for _, peer := range n.Peers() { + err = peer.Stop() + require.NoError(t, err) + } + + // 4. start all nodes + for _, peer := range n.Peers() { + err = peer.Start() + require.NoError(t, err) + } + + // wait for peers to start + time.Sleep(20 * time.Second) + + // check whether all issued transactions are available on all nodes and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // 5. check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) +} + +// TestValueColoredPersistence issues colored tokens on random peers, restarts them and checks for persistence after restart. +func TestValueColoredPersistence(t *testing.T) { + n, err := f.CreateNetwork("valueColor_TestPersistence", 4, 2) + require.NoError(t, err) + defer tests.ShutdownNetwork(t, n) + + // wait for peers to change their state to synchronized + time.Sleep(5 * time.Second) + + // master node sends funds to all peers in the network + txIds, addrBalance := tests.SendTransactionFromFaucet(t, n.Peers(), 100) + + // wait for messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether the transactions are available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // send funds around + randomTxIds := tests.SendColoredTransactionOnRandomPeer(t, n.Peers(), addrBalance, 10) + txIds = append(txIds, randomTxIds...) + + // wait for value messages to be gossiped + time.Sleep(2 * valuetransfers.AverageNetworkDelay) + + // check whether all issued transactions are persistently available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) + + // stop all nodes + for _, peer := range n.Peers() { + err = peer.Stop() + require.NoError(t, err) + } + + // start all nodes + for _, peer := range n.Peers() { + err = peer.Start() + require.NoError(t, err) + } + + // wait for peers to start + time.Sleep(20 * time.Second) + + // check whether all issued transactions are persistently available on all nodes, and confirmed + tests.CheckTransactions(t, n.Peers(), txIds, true) + + // 5. check ledger state + tests.CheckBalances(t, n.Peers(), addrBalance) +}