diff --git a/go.mod b/go.mod index aab57c817a..c06b509f8c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/gin-gonic/gin v1.7.0 github.com/go-resty/resty/v2 v2.6.0 github.com/gorilla/websocket v1.5.0 - github.com/iotaledger/hive.go v0.0.0-20220403215612-08d431c95225 + github.com/iotaledger/hive.go v0.0.0-20220404122403-41e4f67ac4c1 github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 github.com/libp2p/go-libp2p v0.15.0 diff --git a/go.sum b/go.sum index b1a51f7f25..4ce5091bac 100644 --- a/go.sum +++ b/go.sum @@ -539,6 +539,12 @@ github.com/iotaledger/hive.go v0.0.0-20220401233028-f652ea015d76 h1:cqo5VpUgSE/F github.com/iotaledger/hive.go v0.0.0-20220401233028-f652ea015d76/go.mod h1:WAw2y5dTB4Z4YMjK98BL3OJJVcGj3fBgcHxoMCYLJac= github.com/iotaledger/hive.go v0.0.0-20220403215612-08d431c95225 h1:HliGrzi7yC1lyeyXJTU9+1rK2i24ljA01ZVyU7CQn6A= github.com/iotaledger/hive.go v0.0.0-20220403215612-08d431c95225/go.mod h1:WAw2y5dTB4Z4YMjK98BL3OJJVcGj3fBgcHxoMCYLJac= +github.com/iotaledger/hive.go v0.0.0-20220404105134-a01f4f4bd6f6 h1:SVJ8XWsAXHidRMCT616bAT3+0NuuM+cpfaYrdaNgJtQ= +github.com/iotaledger/hive.go v0.0.0-20220404105134-a01f4f4bd6f6/go.mod h1:WAw2y5dTB4Z4YMjK98BL3OJJVcGj3fBgcHxoMCYLJac= +github.com/iotaledger/hive.go v0.0.0-20220404120832-8df46debbc19 h1:gQ6IWUY64ZsafjDFMRmUXxFwlvddGmadmPlAN4/BE8U= +github.com/iotaledger/hive.go v0.0.0-20220404120832-8df46debbc19/go.mod h1:WAw2y5dTB4Z4YMjK98BL3OJJVcGj3fBgcHxoMCYLJac= +github.com/iotaledger/hive.go v0.0.0-20220404122403-41e4f67ac4c1 h1:N9voEMa7pfYoV055TzIyOEJkG2dVSx4mfX97vstPx3Y= +github.com/iotaledger/hive.go v0.0.0-20220404122403-41e4f67ac4c1/go.mod h1:WAw2y5dTB4Z4YMjK98BL3OJJVcGj3fBgcHxoMCYLJac= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= diff --git a/packages/ledger/branchdag/types.go b/packages/ledger/branchdag/types.go index e86cde0ad3..3aae1057f2 100644 --- a/packages/ledger/branchdag/types.go +++ b/packages/ledger/branchdag/types.go @@ -37,6 +37,10 @@ var MasterBranchID BranchID const BranchIDLength = types.IdentifierLength +func init() { + MasterBranchID.RegisterAlias("MasterBranch") +} + // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // region BranchIDs //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/ledger/dataflow.go b/packages/ledger/dataflow.go index b51ee938b8..626ae8fc56 100644 --- a/packages/ledger/dataflow.go +++ b/packages/ledger/dataflow.go @@ -52,6 +52,7 @@ func (d *dataFlow) checkTransaction() *dataflow.DataFlow[*dataFlowParams] { func newDataFlowParams(tx *Transaction) (new *dataFlowParams) { return &dataFlowParams{ Transaction: tx, + InputIDs: utxo.NewOutputIDs(), } } diff --git a/packages/ledger/ledger_test.go b/packages/ledger/ledger_test.go index 44d01c5ab9..42ac9fd986 100644 --- a/packages/ledger/ledger_test.go +++ b/packages/ledger/ledger_test.go @@ -375,3 +375,312 @@ func TestLedger_SetBranchConfirmed(t *testing.T) { assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXG", "TXH"))) } } + +func TestLedger_SolidifyMultiThreaded(t *testing.T) { + testFramework := NewTestFramework() + + // Step 1: Bottom Layer + testFramework.CreateTransaction("G", 3, "Genesis") + testFramework.CreateTransaction("TXA", 1, "G.0") + testFramework.CreateTransaction("TXC", 1, "G.1") + testFramework.CreateTransaction("TXH", 1, "G.2") + // Step 2: Middle Layer + testFramework.CreateTransaction("TXE", 1, "TXA.0", "TXC.0") + // Step 4: Top Layer + testFramework.CreateTransaction("TXG", 1, "TXE.0") + // Step 5: TopTop Layer + testFramework.CreateTransaction("TXL", 1, "TXG.0", "TXH.0") + // Step 6: TopTopTOP Layer + testFramework.CreateTransaction("TXM", 1, "TXL.0") + + // forks + //testFramework.CreateTransaction("TXD", 1, "G.1") + //testFramework.CreateTransaction("TXD", 1, "G.1") + //testFramework.CreateTransaction("TXI", 1, "G.2") + // Step 3: Top Layer + //testFramework.CreateTransaction("TXF", 1, "TXE.0") + + // Issue first batch of Transactions + { + for _, txAlias := range []string{"G", "TXA", "TXH"} { + assert.NoError(t, testFramework.IssueTransaction(txAlias)) + } + + for _, txAlias := range []string{"TXE", "TXG", "TXL", "TXM"} { + assert.ErrorIs(t, testFramework.IssueTransaction(txAlias), ErrTransactionUnsolid) + } + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + }) + + testFramework.AssertBooked(t, map[string]bool{ + "G": true, + "TXA": true, + "TXE": false, + "TXH": true, + "TXG": false, + "TXL": false, + "TXM": false, + }) + } + + // A bunch of transactions should now become solid + { + assert.NoError(t, testFramework.IssueTransaction("TXC")) + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + "TXE": {"MasterBranch"}, + "TXG": {"MasterBranch"}, + "TXL": {"MasterBranch"}, + "TXM": {"MasterBranch"}, + }) + + testFramework.AssertBooked(t, map[string]bool{ + "G": true, + "TXA": true, + "TXE": true, + "TXH": true, + "TXG": true, + "TXL": true, + "TXM": true, + }) + } + + return + + // Mark A as Confirmed + { + for _, txAlias := range []string{"G", "TXA", "TXB", "TXC", "TXD", "TXH", "TXI"} { + assert.NoError(t, testFramework.IssueTransaction(txAlias)) + } + require.True(t, testFramework.Ledger.BranchDAG.SetBranchConfirmed(branchdag.NewBranchID(testFramework.Transaction("TXA").ID()))) + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"TXA"}, + "TXB": {"TXB"}, + "TXC": {"TXC"}, + "TXD": {"TXD"}, + "TXH": {"TXH"}, + "TXI": {"TXI"}, + }) + + testFramework.AssertBranchDAG(t, map[string][]string{ + "TXA": {"MasterBranch"}, + "TXB": {"MasterBranch"}, + "TXC": {"MasterBranch"}, + "TXD": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + "TXI": {"MasterBranch"}, + }) + + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXA"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXB"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXC"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXD"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXH"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXI"))) + } + + // When creating the middle layer the new transaction E should be booked only under its Pending parent C + { + assert.NoError(t, testFramework.IssueTransaction("TXE")) + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"TXA"}, + "TXB": {"TXB"}, + "TXC": {"TXC"}, + "TXD": {"TXD"}, + "TXH": {"TXH"}, + "TXI": {"TXI"}, + "TXE": {"TXC"}, + }) + + testFramework.AssertBranchDAG(t, map[string][]string{ + "TXA": {"MasterBranch"}, + "TXB": {"MasterBranch"}, + "TXC": {"MasterBranch"}, + "TXD": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + "TXI": {"MasterBranch"}, + }) + + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXA"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXB"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXC"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXD"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXH"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXI"))) + } + + // When creating the first transaction (F) of top layer it should be booked under the Pending parent C + { + for _, txAlias := range []string{"TXF"} { + assert.NoError(t, testFramework.IssueTransaction(txAlias)) + } + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"TXA"}, + "TXB": {"TXB"}, + "TXC": {"TXC"}, + "TXD": {"TXD"}, + "TXH": {"TXH"}, + "TXI": {"TXI"}, + // Branches F & G are spawned by the fork of G + "TXF": {"TXC"}, + }) + + testFramework.AssertBranchDAG(t, map[string][]string{ + "TXA": {"MasterBranch"}, + "TXB": {"MasterBranch"}, + "TXC": {"MasterBranch"}, + "TXD": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + "TXI": {"MasterBranch"}, + }) + + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXA"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXB"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXC"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXD"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXH"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXI"))) + } + + // When creating the conflicting TX (G) of the top layer branches F & G are spawned by the fork of G + { + for _, txAlias := range []string{"TXG"} { + assert.NoError(t, testFramework.IssueTransaction(txAlias)) + } + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"TXA"}, + "TXB": {"TXB"}, + "TXC": {"TXC"}, + "TXD": {"TXD"}, + "TXH": {"TXH"}, + "TXI": {"TXI"}, + // Branches F & G are spawned by the fork of G + "TXF": {"TXF"}, + "TXG": {"TXG"}, + }) + + testFramework.AssertBranchDAG(t, map[string][]string{ + "TXA": {"MasterBranch"}, + "TXB": {"MasterBranch"}, + "TXC": {"MasterBranch"}, + "TXD": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + "TXI": {"MasterBranch"}, + "TXF": {"TXC"}, + "TXG": {"TXC"}, + }) + + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXA"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXB"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXC"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXD"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXH"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXI"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXF"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXG"))) + } + + require.True(t, testFramework.Ledger.BranchDAG.SetBranchConfirmed(branchdag.NewBranchID(testFramework.Transaction("TXD").ID()))) + + // TX L combines a child (G) of a Rejected branch (C) and a pending branch H, resulting in (G,H) + { + for _, txAlias := range []string{"TXL"} { + assert.NoError(t, testFramework.IssueTransaction(txAlias)) + } + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"TXA"}, + "TXB": {"TXB"}, + "TXC": {"TXC"}, + "TXD": {"TXD"}, + "TXH": {"TXH"}, + "TXI": {"TXI"}, + // Branches F & G are spawned by the fork of G + "TXF": {"TXF"}, + "TXG": {"TXG"}, + "TXL": {"TXG", "TXH"}, + }) + + testFramework.AssertBranchDAG(t, map[string][]string{ + "TXA": {"MasterBranch"}, + "TXB": {"MasterBranch"}, + "TXC": {"MasterBranch"}, + "TXD": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + "TXI": {"MasterBranch"}, + "TXF": {"TXC"}, + "TXG": {"TXC"}, + }) + + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXA"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXB"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXC"))) + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXD"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXH"))) + assert.Equal(t, branchdag.Pending, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXI"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXF"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXG"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXG", "TXH"))) + } + + require.True(t, testFramework.Ledger.BranchDAG.SetBranchConfirmed(branchdag.NewBranchID(testFramework.Transaction("TXH").ID()))) + + // The new TX M should be now booked under G, as branch H confirmed, just G because we don't propagate H further. + { + for _, txAlias := range []string{"TXM"} { + assert.NoError(t, testFramework.IssueTransaction(txAlias)) + } + + testFramework.AssertBranchIDs(t, map[string][]string{ + "G": {"MasterBranch"}, + "TXA": {"TXA"}, + "TXB": {"TXB"}, + "TXC": {"TXC"}, + "TXD": {"TXD"}, + "TXH": {"TXH"}, + "TXI": {"TXI"}, + // Branches F & G are spawned by the fork of G + "TXF": {"TXF"}, + "TXG": {"TXG"}, + "TXL": {"TXG", "TXH"}, + "TXM": {"TXG"}, + }) + + testFramework.AssertBranchDAG(t, map[string][]string{ + "TXA": {"MasterBranch"}, + "TXB": {"MasterBranch"}, + "TXC": {"MasterBranch"}, + "TXD": {"MasterBranch"}, + "TXH": {"MasterBranch"}, + "TXI": {"MasterBranch"}, + "TXF": {"TXC"}, + "TXG": {"TXC"}, + }) + + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXA"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXB"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXC"))) + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXD"))) + assert.Equal(t, branchdag.Confirmed, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXH"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXI"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXF"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXG"))) + assert.Equal(t, branchdag.Rejected, testFramework.Ledger.BranchDAG.InclusionState(testFramework.BranchIDs("TXG", "TXH"))) + } +} diff --git a/packages/ledger/models.go b/packages/ledger/models.go index dc7249bdf4..bfc2e9ff4c 100644 --- a/packages/ledger/models.go +++ b/packages/ledger/models.go @@ -52,20 +52,20 @@ var _ objectstorage.StorableObject = new(Transaction) // TransactionMetadata contains additional information about a Transaction that is derived from the local perception of // a node. type TransactionMetadata struct { - id utxo.TransactionID - branchIDs branchdag.BranchIDs - branchIDsMutex sync.RWMutex - solid bool - solidMutex sync.RWMutex - solidificationTime time.Time - solidificationTimeMutex sync.RWMutex - lazyBooked bool - lazyBookedMutex sync.RWMutex - outputIDs utxo.OutputIDs - outputIDsMutex sync.RWMutex - gradeOfFinality gof.GradeOfFinality - gradeOfFinalityTime time.Time - gradeOfFinalityMutex sync.RWMutex + id utxo.TransactionID + branchIDs branchdag.BranchIDs + branchIDsMutex sync.RWMutex + booked bool + bookingMutex sync.RWMutex + bookingTime time.Time + bookingTimeMutex sync.RWMutex + lazyBooked bool + lazyBookedMutex sync.RWMutex + outputIDs utxo.OutputIDs + outputIDsMutex sync.RWMutex + gradeOfFinality gof.GradeOfFinality + gradeOfFinalityTime time.Time + gradeOfFinalityMutex sync.RWMutex objectstorage.StorableObjectFlags } @@ -75,6 +75,7 @@ func NewTransactionMetadata(transactionID utxo.TransactionID) (newTransactionMet newTransactionMetadata = &TransactionMetadata{ id: transactionID, branchIDs: branchdag.NewBranchIDs(), + outputIDs: utxo.NewOutputIDs(), } newTransactionMetadata.SetModified() newTransactionMetadata.Persist() @@ -106,7 +107,10 @@ func (t *TransactionMetadata) FromBytes(bytes []byte) (transactionMetadata *Tran // FromMarshalUtil unmarshals an TransactionMetadata object using a MarshalUtil (for easier unmarshalling). func (t *TransactionMetadata) FromMarshalUtil(marshalUtil *marshalutil.MarshalUtil) (transactionMetadata *TransactionMetadata, err error) { if transactionMetadata = t; transactionMetadata == nil { - transactionMetadata = &TransactionMetadata{} + transactionMetadata = &TransactionMetadata{ + branchIDs: branchdag.NewBranchIDs(), + outputIDs: utxo.NewOutputIDs(), + } } if err = transactionMetadata.id.FromMarshalUtil(marshalUtil); err != nil { @@ -115,10 +119,10 @@ func (t *TransactionMetadata) FromMarshalUtil(marshalUtil *marshalutil.MarshalUt if err = transactionMetadata.branchIDs.FromMarshalUtil(marshalUtil); err != nil { return nil, errors.Errorf("failed to parse BranchID: %w", err) } - if transactionMetadata.solid, err = marshalUtil.ReadBool(); err != nil { + if transactionMetadata.booked, err = marshalUtil.ReadBool(); err != nil { return nil, errors.Errorf("failed to parse solid flag (%v): %w", err, cerrors.ErrParseBytesFailed) } - if transactionMetadata.solidificationTime, err = marshalUtil.ReadTime(); err != nil { + if transactionMetadata.bookingTime, err = marshalUtil.ReadTime(); err != nil { return nil, errors.Errorf("failed to parse solidify time (%v): %w", err, cerrors.ErrParseBytesFailed) } if transactionMetadata.lazyBooked, err = marshalUtil.ReadBool(); err != nil { @@ -184,41 +188,41 @@ func (t *TransactionMetadata) AddBranchID(branchID branchdag.BranchID) (modified // Booked returns true if the Transaction has been marked as solid. func (t *TransactionMetadata) Booked() bool { - t.solidMutex.RLock() - defer t.solidMutex.RUnlock() + t.bookingMutex.RLock() + defer t.bookingMutex.RUnlock() - return t.solid + return t.booked } // SetBooked updates the solid flag of the Transaction. It returns true if the solid flag was modified and updates -// the solidification time if the Transaction was marked as solid. -func (t *TransactionMetadata) SetBooked(solid bool) (modified bool) { - t.solidMutex.Lock() - defer t.solidMutex.Unlock() +// the booking time if the Transaction was marked as solid. +func (t *TransactionMetadata) SetBooked(booked bool) (modified bool) { + t.bookingMutex.Lock() + defer t.bookingMutex.Unlock() - if t.solid == solid { + if t.booked == booked { return } - if solid { - t.solidificationTimeMutex.Lock() - t.solidificationTime = time.Now() - t.solidificationTimeMutex.Unlock() + if booked { + t.bookingTimeMutex.Lock() + t.bookingTime = time.Now() + t.bookingTimeMutex.Unlock() } - t.solid = solid + t.booked = booked t.SetModified() modified = true return } -// SolidificationTime returns the time when the Transaction was marked as solid. -func (t *TransactionMetadata) SolidificationTime() time.Time { - t.solidificationTimeMutex.RLock() - defer t.solidificationTimeMutex.RUnlock() +// BookingTime returns the time when the Transaction was marked as booked. +func (t *TransactionMetadata) BookingTime() time.Time { + t.bookingTimeMutex.RLock() + defer t.bookingTimeMutex.RUnlock() - return t.solidificationTime + return t.bookingTime } // LazyBooked returns a boolean flag that indicates if the Transaction has been analyzed regarding the conflicting @@ -251,7 +255,7 @@ func (t *TransactionMetadata) OutputIDs() utxo.OutputIDs { t.outputIDsMutex.RLock() defer t.outputIDsMutex.RUnlock() - return t.outputIDs + return t.outputIDs.Clone() } // SetOutputIDs sets the OutputIDs that this Transaction created. @@ -312,7 +316,7 @@ func (t *TransactionMetadata) String() string { stringify.StructField("id", t.ID()), stringify.StructField("branchID", t.BranchIDs()), stringify.StructField("processed", t.Booked()), - stringify.StructField("solidificationTime", t.SolidificationTime()), + stringify.StructField("solidificationTime", t.BookingTime()), stringify.StructField("lazyBooked", t.LazyBooked()), stringify.StructField("outputIDs", t.OutputIDs()), stringify.StructField("gradeOfFinality", t.GradeOfFinality()), @@ -332,7 +336,7 @@ func (t *TransactionMetadata) ObjectStorageValue() []byte { return marshalutil.New(). Write(t.BranchIDs()). WriteBool(t.Booked()). - WriteTime(t.SolidificationTime()). + WriteTime(t.BookingTime()). WriteBool(t.LazyBooked()). Write(t.OutputIDs()). WriteUint8(uint8(t.GradeOfFinality())). @@ -435,19 +439,15 @@ func (o Outputs) UTXOOutputs() (slice []utxo.Output) { // OutputMetadata contains additional Output information that are derived from the local perception of the node. type OutputMetadata struct { - id utxo.OutputID - branchIDs branchdag.BranchIDs - branchIDsMutex sync.RWMutex - solid bool - solidMutex sync.RWMutex - solidificationTime time.Time - solidificationTimeMutex sync.RWMutex - firstConsumer utxo.TransactionID - firstConsumerForked bool - firstConsumerMutex sync.RWMutex - gradeOfFinality gof.GradeOfFinality - gradeOfFinalityTime time.Time - gradeOfFinalityMutex sync.RWMutex + id utxo.OutputID + branchIDs branchdag.BranchIDs + branchIDsMutex sync.RWMutex + firstConsumer utxo.TransactionID + firstConsumerForked bool + firstConsumerMutex sync.RWMutex + gradeOfFinality gof.GradeOfFinality + gradeOfFinalityTime time.Time + gradeOfFinalityMutex sync.RWMutex objectstorage.StorableObjectFlags } @@ -492,12 +492,6 @@ func (o *OutputMetadata) FromMarshalUtil(marshalUtil *marshalutil.MarshalUtil) ( if err = outputMetadata.branchIDs.FromMarshalUtil(marshalUtil); err != nil { return nil, errors.Errorf("failed to parse BranchIDs: %w", err) } - if outputMetadata.solid, err = marshalUtil.ReadBool(); err != nil { - return nil, errors.Errorf("failed to parse solid flag (%v): %w", err, cerrors.ErrParseBytesFailed) - } - if outputMetadata.solidificationTime, err = marshalUtil.ReadTime(); err != nil { - return nil, errors.Errorf("failed to parse solidification time (%v): %w", err, cerrors.ErrParseBytesFailed) - } if err = outputMetadata.firstConsumer.FromMarshalUtil(marshalUtil); err != nil { return nil, errors.Errorf("failed to parse first consumer (%v): %w", err, cerrors.ErrParseBytesFailed) } @@ -558,45 +552,6 @@ func (o *OutputMetadata) AddBranchID(branchID branchdag.BranchID) (modified bool return } -// Solid returns true if the Output has been marked as solid. -func (o *OutputMetadata) Solid() bool { - o.solidMutex.RLock() - defer o.solidMutex.RUnlock() - - return o.solid -} - -// SetSolid updates the solid flag of the Output. It returns true if the solid flag was modified and updates the -// solidification time if the Output was marked as solid. -func (o *OutputMetadata) SetSolid(solid bool) (modified bool) { - o.solidMutex.Lock() - defer o.solidMutex.Unlock() - - if o.solid == solid { - return - } - - if solid { - o.solidificationTimeMutex.Lock() - o.solidificationTime = time.Now() - o.solidificationTimeMutex.Unlock() - } - - o.solid = solid - o.SetModified() - modified = true - - return -} - -// SolidificationTime returns the time when the Output was marked as solid. -func (o *OutputMetadata) SolidificationTime() time.Time { - o.solidificationTimeMutex.RLock() - defer o.solidificationTimeMutex.RUnlock() - - return o.solidificationTime -} - // Spent returns true if the Output has been spent already. func (o *OutputMetadata) Spent() bool { o.firstConsumerMutex.RLock() @@ -673,8 +628,6 @@ func (o *OutputMetadata) String() string { return stringify.Struct("OutputMetadata", stringify.StructField("id", o.ID()), stringify.StructField("branchIDs", o.BranchIDs()), - stringify.StructField("solid", o.Solid()), - stringify.StructField("solidificationTime", o.SolidificationTime()), stringify.StructField("firstConsumer", o.FirstConsumer()), stringify.StructField("gradeOfFinality", o.GradeOfFinality()), stringify.StructField("gradeOfFinalityTime", o.GradeOfFinalityTime()), @@ -692,8 +645,6 @@ func (o *OutputMetadata) ObjectStorageKey() []byte { func (o *OutputMetadata) ObjectStorageValue() []byte { return marshalutil.New(). Write(o.BranchIDs()). - WriteBool(o.Solid()). - WriteTime(o.SolidificationTime()). Write(o.FirstConsumer()). WriteUint8(uint8(o.GradeOfFinality())). WriteTime(o.GradeOfFinalityTime()). diff --git a/packages/ledger/testframework.go b/packages/ledger/testframework.go index e4c8e1cdde..a339546c84 100644 --- a/packages/ledger/testframework.go +++ b/packages/ledger/testframework.go @@ -38,7 +38,6 @@ func NewTestFramework(options ...Option) (new *TestFramework) { genesisOutput := NewOutput(NewMockedOutput(utxo.EmptyTransactionID, 0)) genesisOutputMetadata := NewOutputMetadata(genesisOutput.ID()) - genesisOutputMetadata.SetSolid(true) genesisOutputMetadata.SetGradeOfFinality(gof.High) genesisOutput.ID().RegisterAlias("Genesis") @@ -136,6 +135,21 @@ func (t *TestFramework) AssertBranchIDs(testing *testing.T, expectedBranches map } } +func (t *TestFramework) AssertBooked(testing *testing.T, expectedBookedMap map[string]bool) { + for txAlias, expectedBooked := range expectedBookedMap { + currentTx := t.Transaction(txAlias) + assert.Truef(testing, t.Ledger.Storage.CachedTransactionMetadata(currentTx.ID()).Consume(func(txMetadata *TransactionMetadata) { + assert.Equalf(testing, expectedBooked, txMetadata.Booked(), "Transaction(%s): expected booked(%s) but has booked(%s)", txAlias, expectedBooked, txMetadata.Booked()) + _ = txMetadata.OutputIDs().ForEach(func(outputID utxo.OutputID) (err error) { + assert.Equalf(testing, expectedBooked, t.Ledger.Storage.CachedOutputMetadata(outputID).Consume(func(_ *OutputMetadata) {}), + "Output(%s): expected booked(%s) but has booked(%s)", outputID, expectedBooked, txMetadata.Booked()) + return nil + }) + }), "failed to load metadata of %s", currentTx.ID()) + + } +} + func (t *TestFramework) IssueTransaction(txAlias string) (err error) { transaction, exists := t.transactionsByAlias[txAlias] if !exists { @@ -145,6 +159,10 @@ func (t *TestFramework) IssueTransaction(txAlias string) (err error) { return t.Ledger.StoreAndProcessTransaction(transaction) } +func (t *TestFramework) MockOutputFromTx(tx *MockedTransaction, outputIndex uint16) (mockedOutputID utxo.OutputID) { + return utxo.NewOutputID(tx.ID(), outputIndex, []byte("")) +} + // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // region MockedInput //////////////////////////////////////////////////////////////////////////////////////////////////