From a3bded3a4e0fd86bb12999c3cb53d2b5b2859613 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Sun, 20 Mar 2022 01:54:59 +0100 Subject: [PATCH] Feat: started working on executor and booker --- packages/refactored/generics/utils.go | 11 +++ packages/refactored/ledger/booker.go | 74 ++++++++++++++++++ packages/refactored/ledger/executor.go | 18 +++++ packages/refactored/ledger/ledger.go | 8 +- .../{availabilitymanager.go => solidifier.go} | 4 +- ...litymanager_test.go => solidifier_test.go} | 0 packages/refactored/ledger/utils.go | 8 +- packages/refactored/ledger/validator.go | 6 +- packages/refactored/old/utxo_dag.go | 76 ------------------- 9 files changed, 118 insertions(+), 87 deletions(-) create mode 100644 packages/refactored/ledger/booker.go create mode 100644 packages/refactored/ledger/executor.go rename packages/refactored/ledger/{availabilitymanager.go => solidifier.go} (86%) rename packages/refactored/ledger/{availabilitymanager_test.go => solidifier_test.go} (100%) diff --git a/packages/refactored/generics/utils.go b/packages/refactored/generics/utils.go index fa0496a8d8..cedb1c03b6 100644 --- a/packages/refactored/generics/utils.go +++ b/packages/refactored/generics/utils.go @@ -66,3 +66,14 @@ func Keys[K comparable, V any](in map[K]V) []K { return result } + +// Values creates an array of the map values. +func Values[K comparable, V any](in map[K]V) []V { + result := make([]V, 0, len(in)) + + for _, v := range in { + result = append(result, v) + } + + return result +} diff --git a/packages/refactored/ledger/booker.go b/packages/refactored/ledger/booker.go new file mode 100644 index 0000000000..698df890d3 --- /dev/null +++ b/packages/refactored/ledger/booker.go @@ -0,0 +1,74 @@ +package ledger + +import ( + "fmt" + + "github.com/cockroachdb/errors" + "github.com/iotaledger/hive.go/generics/dataflow" + "github.com/iotaledger/hive.go/generics/walker" + "github.com/iotaledger/hive.go/types" + + "github.com/iotaledger/goshimmer/packages/refactored/generics" + "github.com/iotaledger/goshimmer/packages/refactored/ledger/branchdag" + "github.com/iotaledger/goshimmer/packages/refactored/utxo" +) + +type Booker struct { + *Ledger +} + +func (b *Booker) bookTransactionCommand(params *params, next dataflow.Next[*params]) (err error) { + // check the double spends first because at this step we also create the consumers + conflictingInputIDs := generics.Keys(generics.FilterByValue(params.InputsMetadata, b.doubleSpendRegistered(params.Transaction.ID()))) + + inheritedBranchIDs, err := b.ResolvePendingBranchIDs(generics.Reduce(generics.Values(params.InputsMetadata), b.accumulateBranchIDs, branchdag.NewBranchIDs())) + if err != nil { + return errors.Errorf("failed to resolve pending branches: %w", err) + } + + if len(conflictingInputIDs) != 0 { + b.WalkConsumingTransactionID(conflictingInputIDs, func(txID utxo.TransactionID, _ *walker.Walker[utxo.OutputID]) { + b.forkConsumer(txID, conflictingInputIDs) + return + }) + + newBranchID := branchdag.NewBranchID(params.Transaction.ID()) + inheritedBranchIDs = branchdag.NewBranchIDs(newBranchID) + cachedBranch, _, err := b.CreateBranch(newBranchID, inheritedBranchIDs, conflictingInputIDs) + if err != nil { + panic(fmt.Errorf("failed to create Branch when booking Transaction with %s: %w", params.Transaction.ID(), err)) + } + cachedBranch.Release() + } + + b.bookTransaction(params.Transaction, params.TransactionMetadata, params.InputsMetadata, inheritedBranchIDs) + + return next(params) +} + +func (b *Booker) accumulateBranchIDs(accumulator branchdag.BranchIDs, inputMetadata *OutputMetadata) (result branchdag.BranchIDs) { + return accumulator.AddAll(inputMetadata.BranchIDs()) +} + +func (b *Booker) doubleSpendRegistered(txID utxo.TransactionID) func(*OutputMetadata) bool { + return func(outputMetadata *OutputMetadata) (conflicting bool) { + outputMetadata.RegisterConsumer(txID) + + b.bookConsumers(inputsMetadata, transaction.ID(), types.True) + + return false + } +} + +// bookNonConflictingTransaction is an internal utility function that books the Transaction into the Branch that is +// determined by aggregating the Branches of the consumed Inputs. +func (u *Booker) bookTransaction(transaction utxo.Transaction, transactionMetadata *TransactionMetadata, inputsMetadata map[utxo.OutputID]*OutputMetadata, branchIDs branchdag.BranchIDs) (targetBranchIDs branchdag.BranchIDs) { + u.bookOutputs(transaction, branchIDs) + + transactionMetadata.SetBranchIDs(branchIDs) + transactionMetadata.SetSolid(true) + + return branchIDs +} + +// diff --git a/packages/refactored/ledger/executor.go b/packages/refactored/ledger/executor.go new file mode 100644 index 0000000000..ec7c468b5c --- /dev/null +++ b/packages/refactored/ledger/executor.go @@ -0,0 +1,18 @@ +package ledger + +import ( + "github.com/cockroachdb/errors" + "github.com/iotaledger/hive.go/generics/dataflow" +) + +type Executor struct { + *Ledger +} + +func (e *Executor) executeTransactionCommand(params *params, next dataflow.Next[*params]) (err error) { + if params.Outputs, err = e.vm.ExecuteTransaction(params.Transaction, params.Inputs); err != nil { + return errors.Errorf("failed to execute transaction with %s: %w", params.Transaction.ID(), ErrTransactionInvalid) + } + + return next(params) +} diff --git a/packages/refactored/ledger/ledger.go b/packages/refactored/ledger/ledger.go index eab1d667e6..0c3323f738 100644 --- a/packages/refactored/ledger/ledger.go +++ b/packages/refactored/ledger/ledger.go @@ -5,6 +5,7 @@ import ( "github.com/iotaledger/hive.go/generics/event" "github.com/iotaledger/hive.go/kvstore" + "github.com/iotaledger/goshimmer/packages/refactored/ledger/branchdag" "github.com/iotaledger/goshimmer/packages/refactored/syncutils" "github.com/iotaledger/goshimmer/packages/refactored/utxo" ) @@ -21,7 +22,9 @@ type Ledger struct { *Storage *Solidifier *Validator + *Executor *Utils + *branchdag.BranchDAG syncutils.DAGMutex[[32]byte] @@ -85,8 +88,8 @@ func (l *Ledger) processTransaction(tx utxo.Transaction, txMeta *TransactionMeta err := dataflow.New[*params]( l.checkSolidityCommand, l.checkOutputsCausallyRelatedCommand, + l.executeTransactionCommand, /* - l.ExecuteTransaction, l.BookTransaction, */ l.notifyConsumersCommand, @@ -125,6 +128,7 @@ func (l *Ledger) notifyConsumersCommand(params *params, next dataflow.Next[*para type params struct { Transaction utxo.Transaction TransactionMetadata *TransactionMetadata - Inputs map[utxo.OutputID]utxo.Output + Inputs []utxo.Output InputsMetadata map[utxo.OutputID]*OutputMetadata + Outputs []utxo.Output } diff --git a/packages/refactored/ledger/availabilitymanager.go b/packages/refactored/ledger/solidifier.go similarity index 86% rename from packages/refactored/ledger/availabilitymanager.go rename to packages/refactored/ledger/solidifier.go index 6dda994596..4055e7309b 100644 --- a/packages/refactored/ledger/availabilitymanager.go +++ b/packages/refactored/ledger/solidifier.go @@ -18,7 +18,7 @@ func NewSolidifier(ledger *Ledger) (newAvailabilityManager *Solidifier) { } } -func (s *Solidifier) checkSolidity(transaction utxo.Transaction, metadata *TransactionMetadata) (success bool, inputs map[utxo.OutputID]utxo.Output) { +func (s *Solidifier) checkSolidity(transaction utxo.Transaction, metadata *TransactionMetadata) (success bool, inputs []utxo.Output) { if metadata.Solid() { return false, nil } @@ -26,7 +26,7 @@ func (s *Solidifier) checkSolidity(transaction utxo.Transaction, metadata *Trans cachedInputs := objectstorage.CachedObjects[utxo.Output](generics.Map(generics.Map(transaction.Inputs(), s.vm.ResolveInput), s.CachedOutput)) defer cachedInputs.Release() - inputs = generics.KeyBy[utxo.OutputID, utxo.Output](cachedInputs.Unwrap(true), utxo.Output.ID) + inputs = cachedInputs.Unwrap(true) if len(inputs) != len(cachedInputs) { return false, nil } diff --git a/packages/refactored/ledger/availabilitymanager_test.go b/packages/refactored/ledger/solidifier_test.go similarity index 100% rename from packages/refactored/ledger/availabilitymanager_test.go rename to packages/refactored/ledger/solidifier_test.go diff --git a/packages/refactored/ledger/utils.go b/packages/refactored/ledger/utils.go index 5a8bd5f20d..f7bbb8127f 100644 --- a/packages/refactored/ledger/utils.go +++ b/packages/refactored/ledger/utils.go @@ -11,7 +11,7 @@ type Utils struct { *Ledger } -func (u *Utils) WalkConsumingTransactionID(callback func(consumingTxID utxo.TransactionID, walker *walker.Walker[utxo.OutputID]), entryPoints []utxo.OutputID) { +func (u *Utils) WalkConsumingTransactionID(entryPoints []utxo.OutputID, callback func(consumingTxID utxo.TransactionID, walker *walker.Walker[utxo.OutputID])) { if len(entryPoints) == 0 { return } @@ -29,10 +29,10 @@ func (u *Utils) WalkConsumingTransactionID(callback func(consumingTxID utxo.Tran } } -func (u *Utils) WalkConsumingTransactionMetadata(callback func(txMetadata *TransactionMetadata, walker *walker.Walker[utxo.OutputID]), entryPoints []utxo.OutputID) { - u.WalkConsumingTransactionID(func(consumingTxID utxo.TransactionID, walker *walker.Walker[utxo.OutputID]) { +func (u *Utils) WalkConsumingTransactionMetadata(entryPoints []utxo.OutputID, callback func(txMetadata *TransactionMetadata, walker *walker.Walker[utxo.OutputID])) { + u.WalkConsumingTransactionID(entryPoints, func(consumingTxID utxo.TransactionID, walker *walker.Walker[utxo.OutputID]) { u.CachedTransactionMetadata(consumingTxID).Consume(func(txMetadata *TransactionMetadata) { callback(txMetadata, walker) }) - }, entryPoints) + }) } diff --git a/packages/refactored/ledger/validator.go b/packages/refactored/ledger/validator.go index 5b8de6d46c..e3793e3837 100644 --- a/packages/refactored/ledger/validator.go +++ b/packages/refactored/ledger/validator.go @@ -15,7 +15,7 @@ type Validator struct { } func (v *Validator) checkOutputsCausallyRelatedCommand(params *params, next dataflow.Next[*params]) (err error) { - cachedOutputsMetadata := objectstorage.CachedObjects[*OutputMetadata](generics.Map(generics.Keys(params.Inputs), v.CachedOutputMetadata)) + cachedOutputsMetadata := objectstorage.CachedObjects[*OutputMetadata](generics.Map(generics.Map(params.Inputs, utxo.Output.ID), v.CachedOutputMetadata)) defer cachedOutputsMetadata.Release() params.InputsMetadata = generics.KeyBy[utxo.OutputID, *OutputMetadata](cachedOutputsMetadata.Unwrap(), (*OutputMetadata).ID) @@ -33,7 +33,7 @@ func (v *Validator) outputsCausallyRelated(outputsMetadata map[utxo.OutputID]*Ou return false } - v.WalkConsumingTransactionMetadata(func(txMetadata *TransactionMetadata, walker *walker.Walker[utxo.OutputID]) { + v.WalkConsumingTransactionMetadata(spentOutputIDs, func(txMetadata *TransactionMetadata, walker *walker.Walker[utxo.OutputID]) { for _, outputID := range txMetadata.OutputIDs() { if _, related = outputsMetadata[outputID]; related { walker.StopWalk() @@ -42,7 +42,7 @@ func (v *Validator) outputsCausallyRelated(outputsMetadata map[utxo.OutputID]*Ou walker.Push(outputID) } - }, spentOutputIDs) + }) return related } diff --git a/packages/refactored/old/utxo_dag.go b/packages/refactored/old/utxo_dag.go index a1381679e0..6844f085b0 100644 --- a/packages/refactored/old/utxo_dag.go +++ b/packages/refactored/old/utxo_dag.go @@ -100,52 +100,6 @@ func (u *UTXODAG) CheckTransaction(transaction utxo.Transaction) (err error) { return nil } -// BookTransaction books a Transaction into the ledger state. -func (u *UTXODAG) BookTransaction(transaction *Transaction) (targetBranchIDs branchdag.BranchIDs, err error) { - // store TransactionMetadata - transactionMetadata := NewTransactionMetadata(transaction.ID()) - transactionMetadata.SetSolid(true) - newTransaction := false - cachedTransactionMetadata := u.transactionMetadataStorage.ComputeIfAbsent(transaction.ID().Bytes(), func(key []byte) *TransactionMetadata { - newTransaction = true - - transactionMetadata.Persist() - transactionMetadata.SetModified() - - return transactionMetadata - }) - if !newTransaction { - if !cachedTransactionMetadata.Consume(func(transactionMetadata *TransactionMetadata) { - targetBranchIDs = transactionMetadata.BranchIDs() - }) { - err = errors.Errorf("failed to load TransactionMetadata with %s: %w", transaction.ID(), cerrors.ErrFatal) - } - return - } - defer cachedTransactionMetadata.Release() - - // store Transaction - u.transactionStorage.Store(transaction).Release() - - // retrieve the metadata of the Inputs - cachedInputsMetadata := u.outputsMetadata(transaction) - defer cachedInputsMetadata.Release() - inputsMetadata := cachedInputsMetadata.Unwrap() - - // determine the booking details before we book - parentBranchIDs, conflictingInputs, err := u.determineBookingDetails(inputsMetadata) - if err != nil { - err = errors.Errorf("failed to determine book details of Transaction with %s: %w", transaction.ID(), err) - return - } - - if len(conflictingInputs) != 0 { - return u.bookConflictingTransaction(transaction, transactionMetadata, inputsMetadata, parentBranchIDs, conflictingInputs.ByID()), nil - } - - return u.bookNonConflictingTransaction(transaction, transactionMetadata, inputsMetadata, parentBranchIDs), nil -} - // TransactionBranchIDs returns the BranchIDs of the given Transaction. func (u *UTXODAG) TransactionBranchIDs(transactionID TransactionID) (branchIDs branchdag.BranchIDs, err error) { if !u.CachedTransactionMetadata(transactionID).Consume(func(transactionMetadata *TransactionMetadata) { @@ -275,17 +229,6 @@ func (u *UTXODAG) CachedAddressOutputMapping(address Address) (cachedAddressOutp // region booking functions //////////////////////////////////////////////////////////////////////////////////////////// -// bookNonConflictingTransaction is an internal utility function that books the Transaction into the Branch that is -// determined by aggregating the Branches of the consumed Inputs. -func (u *UTXODAG) bookNonConflictingTransaction(transaction *Transaction, transactionMetadata *TransactionMetadata, inputsMetadata OutputsMetadata, branchIDs branchdag.BranchIDs) (targetBranchIDs branchdag.BranchIDs) { - transactionMetadata.SetBranchIDs(branchIDs) - transactionMetadata.SetSolid(true) - u.bookConsumers(inputsMetadata, transaction.ID(), types.True) - u.bookOutputs(transaction, branchIDs) - - return branchIDs -} - // bookConflictingTransaction is an internal utility function that books a Transaction that uses Inputs that have // already been spent by another Transaction. It creates a new Branch for the new Transaction and "forks" the // existing consumers of the conflicting Inputs. @@ -435,25 +378,6 @@ func (u *UTXODAG) bookOutputs(transaction *Transaction, targetBranchIDs branchda } } -// determineBookingDetails is an internal utility function that determines the information that are required to fully -// book a newly arrived Transaction into the UTXODAG using the metadata of its referenced Inputs. -func (u *UTXODAG) determineBookingDetails(inputsMetadata OutputsMetadata) (inheritedBranchIDs branchdag.BranchIDs, conflictingInputs OutputsMetadata, err error) { - conflictingInputs = inputsMetadata.SpentOutputsMetadata() - inheritedBranchIDs = branchdag.NewBranchIDs() - for _, inputMetadata := range inputsMetadata { - inheritedBranchIDs.AddAll(inputMetadata.BranchIDs()) - } - - inheritedBranchIDs, err = u.ledgerstate.ResolvePendingBranchIDs(inheritedBranchIDs) - - if err != nil { - err = errors.Errorf("failed to resolve pending branches: %w", cerrors.ErrFatal) - return - } - - return -} - // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // region private utility functions ////////////////////////////////////////////////////////////////////////////////////