Skip to content

Commit

Permalink
Feat: commented Ledger (models missing)
Browse files Browse the repository at this point in the history
  • Loading branch information
hmoog committed Apr 7, 2022
1 parent 37def7c commit c0ec24e
Show file tree
Hide file tree
Showing 15 changed files with 567 additions and 339 deletions.
60 changes: 36 additions & 24 deletions packages/ledger/booker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@ import (
"github.com/iotaledger/goshimmer/packages/ledger/utxo"
)

// booker is a Ledger component that bundles the booking related API.
type booker struct {
*Ledger
// ledger contains a reference to the Ledger that created the booker.
ledger *Ledger
}

// newBooker returns a new booker instance for the given Ledger.
func newBooker(ledger *Ledger) (new *booker) {
return &booker{
Ledger: ledger,
ledger: ledger,
}
}

// checkAlreadyBookedCommand is a ChainedCommand that aborts the DataFlow if a Transaction has already been booked.
func (b *booker) checkAlreadyBookedCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) {
if params.TransactionMetadata == nil {
cachedTransactionMetadata := b.Storage.CachedTransactionMetadata(params.Transaction.ID())
cachedTransactionMetadata := b.ledger.Storage.CachedTransactionMetadata(params.Transaction.ID())
defer cachedTransactionMetadata.Release()

transactionMetadata, exists := cachedTransactionMetadata.Unwrap()
Expand All @@ -41,12 +45,14 @@ func (b *booker) checkAlreadyBookedCommand(params *dataFlowParams, next dataflow
return next(params)
}

// bookTransactionCommand is a ChainedCommand that books a Transaction.
func (b *booker) bookTransactionCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) {
b.bookTransaction(params.TransactionMetadata, params.InputsMetadata, params.Consumers, params.Outputs)

return next(params)
}

// bookTransaction books a Transaction in the Ledger and creates its Outputs.
func (b *booker) bookTransaction(txMetadata *TransactionMetadata, inputsMetadata OutputsMetadata, consumers []*Consumer, outputs Outputs) {
branchIDs := b.inheritBranchIDs(txMetadata.ID(), inputsMetadata)

Expand All @@ -58,23 +64,24 @@ func (b *booker) bookTransaction(txMetadata *TransactionMetadata, inputsMetadata
txMetadata.SetOutputIDs(outputs.IDs())
txMetadata.SetBooked(true)

b.Events.TransactionBooked.Trigger(&TransactionBookedEvent{
b.ledger.Events.TransactionBooked.Trigger(&TransactionBookedEvent{
TransactionID: txMetadata.ID(),
Outputs: outputs,
})
}

// inheritedBranchIDs determines the BranchIDs that a Transaction should inherit when being booked.
func (b *booker) inheritBranchIDs(txID utxo.TransactionID, inputsMetadata OutputsMetadata) (inheritedBranchIDs branchdag.BranchIDs) {
conflictingInputIDs, consumersToFork := b.determineConflictDetails(txID, inputsMetadata)
if conflictingInputIDs.Size() == 0 {
return b.BranchDAG.FilterPendingBranches(inputsMetadata.BranchIDs())
return b.ledger.BranchDAG.FilterPendingBranches(inputsMetadata.BranchIDs())
}

branchID := branchdag.NewBranchID(txID)
b.BranchDAG.CreateBranch(branchID, b.BranchDAG.FilterPendingBranches(inputsMetadata.BranchIDs()), branchdag.NewConflictIDs(lo.Map(conflictingInputIDs.Slice(), branchdag.NewConflictID)...))
b.ledger.BranchDAG.CreateBranch(branchID, b.ledger.BranchDAG.FilterPendingBranches(inputsMetadata.BranchIDs()), branchdag.NewConflictIDs(lo.Map(conflictingInputIDs.Slice(), branchdag.NewConflictID)...))

_ = consumersToFork.ForEach(func(transactionID utxo.TransactionID) (err error) {
b.utils.WithTransactionAndMetadata(transactionID, func(tx *Transaction, txMetadata *TransactionMetadata) {
b.ledger.Utils.WithTransactionAndMetadata(transactionID, func(tx *Transaction, txMetadata *TransactionMetadata) {
b.forkTransaction(tx, txMetadata, conflictingInputIDs)
})

Expand All @@ -84,18 +91,20 @@ func (b *booker) inheritBranchIDs(txID utxo.TransactionID, inputsMetadata Output
return branchdag.NewBranchIDs(branchID)
}

// storeOutputs stores the Outputs in the Ledger.
func (b *booker) storeOutputs(outputs Outputs, branchIDs branchdag.BranchIDs) {
_ = outputs.ForEach(func(output *Output) (err error) {
outputMetadata := NewOutputMetadata(output.ID())
outputMetadata.SetBranchIDs(branchIDs)

b.Storage.outputMetadataStorage.Store(outputMetadata).Release()
b.Storage.outputStorage.Store(output).Release()
b.ledger.Storage.outputMetadataStorage.Store(outputMetadata).Release()
b.ledger.Storage.outputStorage.Store(output).Release()

return nil
})
}

// determineConflictDetails determines whether a Transaction is conflicting and returns the conflict details.
func (b *booker) determineConflictDetails(txID utxo.TransactionID, inputsMetadata OutputsMetadata) (conflictingInputIDs utxo.OutputIDs, consumersToFork utxo.TransactionIDs) {
conflictingInputIDs = utxo.NewOutputIDs()
consumersToFork = utxo.NewTransactionIDs()
Expand All @@ -116,40 +125,42 @@ func (b *booker) determineConflictDetails(txID utxo.TransactionID, inputsMetadat
return conflictingInputIDs, consumersToFork
}

// forkTransaction forks an existing Transaction.
func (b *booker) forkTransaction(tx *Transaction, txMetadata *TransactionMetadata, outputsSpentByConflictingTx utxo.OutputIDs) {
b.mutex.Lock(txMetadata.ID())
b.ledger.mutex.Lock(txMetadata.ID())

conflictingInputs := b.utils.resolveInputs(tx.Inputs()).Intersect(outputsSpentByConflictingTx)
conflictingInputs := b.ledger.Utils.ResolveInputs(tx.Inputs()).Intersect(outputsSpentByConflictingTx)
previousParentBranches := txMetadata.BranchIDs()

forkedBranchID := branchdag.NewBranchID(txMetadata.ID())
conflictIDs := branchdag.NewConflictIDs(lo.Map(conflictingInputs.Slice(), branchdag.NewConflictID)...)
if !b.BranchDAG.CreateBranch(forkedBranchID, previousParentBranches, conflictIDs) {
b.BranchDAG.AddBranchToConflicts(forkedBranchID, conflictIDs)
b.mutex.Unlock(txMetadata.ID())
if !b.ledger.BranchDAG.CreateBranch(forkedBranchID, previousParentBranches, conflictIDs) {
b.ledger.BranchDAG.AddBranchToConflicts(forkedBranchID, conflictIDs)
b.ledger.mutex.Unlock(txMetadata.ID())
return
}

b.Events.TransactionForked.Trigger(&TransactionForkedEvent{
b.ledger.Events.TransactionForked.Trigger(&TransactionForkedEvent{
TransactionID: txMetadata.ID(),
ParentBranches: previousParentBranches,
})

if !b.updateBranchesAfterFork(txMetadata, forkedBranchID, previousParentBranches) {
b.mutex.Unlock(txMetadata.ID())
b.ledger.mutex.Unlock(txMetadata.ID())
return
}
b.mutex.Unlock(txMetadata.ID())
b.ledger.mutex.Unlock(txMetadata.ID())

b.propagateForkedBranchToFutureCone(txMetadata.OutputIDs(), forkedBranchID, previousParentBranches)

return
}

// propagateForkedBranchToFutureCone propagates a newly introduced Branch to its future cone.
func (b *booker) propagateForkedBranchToFutureCone(outputIDs utxo.OutputIDs, forkedBranchID branchdag.BranchID, previousParentBranches branchdag.BranchIDs) {
b.utils.WalkConsumingTransactionMetadata(outputIDs, func(consumingTxMetadata *TransactionMetadata, walker *walker.Walker[utxo.OutputID]) {
b.mutex.Lock(consumingTxMetadata.ID())
defer b.mutex.Unlock(consumingTxMetadata.ID())
b.ledger.Utils.WalkConsumingTransactionMetadata(outputIDs, func(consumingTxMetadata *TransactionMetadata, walker *walker.Walker[utxo.OutputID]) {
b.ledger.mutex.Lock(consumingTxMetadata.ID())
defer b.ledger.mutex.Unlock(consumingTxMetadata.ID())

if !b.updateBranchesAfterFork(consumingTxMetadata, forkedBranchID, previousParentBranches) {
return
Expand All @@ -159,9 +170,10 @@ func (b *booker) propagateForkedBranchToFutureCone(outputIDs utxo.OutputIDs, for
})
}

// updateBranchesAfterFork updates the BranchIDs of a Transaction after a fork.
func (b *booker) updateBranchesAfterFork(txMetadata *TransactionMetadata, forkedBranchID branchdag.BranchID, previousParents branchdag.BranchIDs) bool {
if txMetadata.IsConflicting() {
b.BranchDAG.UpdateBranchParents(branchdag.NewBranchID(txMetadata.ID()), forkedBranchID, previousParents)
b.ledger.BranchDAG.UpdateBranchParents(branchdag.NewBranchID(txMetadata.ID()), forkedBranchID, previousParents)
return false
}

Expand All @@ -172,15 +184,15 @@ func (b *booker) updateBranchesAfterFork(txMetadata *TransactionMetadata, forked
newBranchIDs := txMetadata.BranchIDs().Clone()
newBranchIDs.DeleteAll(previousParents)
newBranchIDs.Add(forkedBranchID)
newBranches := b.BranchDAG.FilterPendingBranches(newBranchIDs)
newBranches := b.ledger.BranchDAG.FilterPendingBranches(newBranchIDs)

b.Storage.CachedOutputsMetadata(txMetadata.OutputIDs()).Consume(func(outputMetadata *OutputMetadata) {
b.ledger.Storage.CachedOutputsMetadata(txMetadata.OutputIDs()).Consume(func(outputMetadata *OutputMetadata) {
outputMetadata.SetBranchIDs(newBranches)
})

txMetadata.SetBranchIDs(newBranches)

b.Events.TransactionBranchIDUpdated.Trigger(&TransactionBranchIDUpdatedEvent{
b.ledger.Events.TransactionBranchIDUpdated.Trigger(&TransactionBranchIDUpdatedEvent{
TransactionID: txMetadata.ID(),
AddedBranchID: forkedBranchID,
RemovedBranchIDs: previousParents,
Expand Down
11 changes: 4 additions & 7 deletions packages/ledger/branchdag/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,6 @@ func WithConflictMemberCacheTime(conflictMemberCacheTime time.Duration) Option {

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////

// region Option ///////////////////////////////////////////////////////////////////////////////////////////////////////

// Option represents a configurable parameter for the BranchDAG that modifies its behavior.
type Option func(*options)

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////

// region options //////////////////////////////////////////////////////////////////////////////////////////////////////

// options is a container for all configurable parameters of a BranchDAG.
Expand Down Expand Up @@ -114,7 +107,11 @@ func (o *options) apply(options ...Option) (self *options) {
for _, option := range options {
option(o)
}

return o
}

// Option represents a configurable parameter for the BranchDAG that modifies its behavior.
type Option func(*options)

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
5 changes: 3 additions & 2 deletions packages/ledger/branchdag/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Storage struct {

// conflictMemberStorage is an object storage used to persist ConflictMember objects.
conflictMemberStorage *objectstorage.ObjectStorage[*ConflictMember]

// shutdownOnce is used to ensure that the shutdown routine is executed only a single time.
shutdownOnce sync.Once
}
Expand Down Expand Up @@ -104,7 +104,8 @@ func (s *Storage) CachedConflictMember(conflictID ConflictID, branchID BranchID,
return s.conflictMemberStorage.Load(byteutils.ConcatBytes(conflictID.Bytes(), branchID.Bytes()))
}

// CachedConflictMembers retrieves the CachedObjects containing the ConflictMember references related to the named Conflict.
// CachedConflictMembers retrieves the CachedObjects containing the ConflictMember references related to the named
// conflict.
func (s *Storage) CachedConflictMembers(conflictID ConflictID) (cachedConflictMembers objectstorage.CachedObjects[*ConflictMember]) {
cachedConflictMembers = make(objectstorage.CachedObjects[*ConflictMember], 0)
s.conflictMemberStorage.ForEach(func(key []byte, cachedObject *objectstorage.CachedObject[*ConflictMember]) bool {
Expand Down
2 changes: 1 addition & 1 deletion packages/ledger/branchdag/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// Utils is a BranchDAG component that bundles utility related API to simplify common interactions with the BranchDAG.
type Utils struct {
// branchDAG contains a reference to the BranchDAG instance that created these Utils.
// branchDAG contains a reference to the BranchDAG that created the Utils.
branchDAG *BranchDAG
}

Expand Down
60 changes: 40 additions & 20 deletions packages/ledger/dataflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,63 +8,83 @@ import (

// region dataFlow /////////////////////////////////////////////////////////////////////////////////////////////////////

// dataFlow is a component of the realities-ledger that connects the different commands into data flows that can be executed as chain-of-command pattern.
// dataFlow is a Ledger component that defines the data flow (h)ow the different commands are chained together).
type dataFlow struct {
*Ledger
// ledger contains a reference to the Ledger that created the Utils.
ledger *Ledger
}

func newDataFlow(ledger *Ledger) *dataFlow {
// newDataFlow returns a new dataFlow instance for the given Ledger.
func newDataFlow(ledger *Ledger) (new *dataFlow) {
return &dataFlow{
ledger,
}
}

// storeAndProcessTransaction returns a DataFlow that stores and processes a Transaction.
func (d *dataFlow) storeAndProcessTransaction() *dataflow.DataFlow[*dataFlowParams] {
return dataflow.New[*dataFlowParams](
d.Storage.storeTransactionCommand,
d.ledger.Storage.storeTransactionCommand,
d.processTransaction().ChainedCommand,
)
}

// processTransaction returns a DataFlow that processes a previously stored Transaction.
func (d *dataFlow) processTransaction() *dataflow.DataFlow[*dataFlowParams] {
return dataflow.New[*dataFlowParams](
d.booker.checkAlreadyBookedCommand,
d.ledger.booker.checkAlreadyBookedCommand,
d.checkTransaction().ChainedCommand,
d.booker.bookTransactionCommand,
d.ledger.booker.bookTransactionCommand,
).WithErrorCallback(func(err error, params *dataFlowParams) {
d.Events.Error.Trigger(err)
d.ledger.Events.Error.Trigger(err)

// TODO: mark Transaction as invalid and trigger invalid event
})
}

// checkTransaction returns a DataFlow that checks the validity of a Transaction.
func (d *dataFlow) checkTransaction() *dataflow.DataFlow[*dataFlowParams] {
return dataflow.New[*dataFlowParams](
d.validator.checkSolidityCommand,
d.validator.checkOutputsCausallyRelatedCommand,
d.validator.checkTransactionExecutionCommand,
d.ledger.validator.checkSolidityCommand,
d.ledger.validator.checkOutputsCausallyRelatedCommand,
d.ledger.validator.checkTransactionExecutionCommand,
)
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////

// region dataFlowParams ///////////////////////////////////////////////////////////////////////////////////////////////

// dataFlowParams is a container for parameters that have to be determined when booking a Transactions.
type dataFlowParams struct {
// Transaction contains the Transaction that is being processed.
Transaction *Transaction

// TransactionMetadata contains the metadata of the Transaction that is being processed.
TransactionMetadata *TransactionMetadata

// InputIDs contains the list of OutputIDs that were referenced by the Inputs.
InputIDs utxo.OutputIDs

// Inputs contains the Outputs that were referenced as Inputs in the Transaction.
Inputs Outputs

// InputsMetadata contains the metadata of the Outputs that were referenced as Inputs in the Transaction.
InputsMetadata OutputsMetadata

// Consumers contains the Consumers (references from the spent Outputs) that were created by the Transaction.
Consumers []*Consumer

// Outputs contains the Outputs that were created by the Transaction.
Outputs Outputs
}

// newDataFlowParams returns a new dataFlowParams instance for the given Transaction.
func newDataFlowParams(tx *Transaction) (new *dataFlowParams) {
return &dataFlowParams{
Transaction: tx,
InputIDs: utxo.NewOutputIDs(),
}
}

type dataFlowParams struct {
Transaction *Transaction
TransactionMetadata *TransactionMetadata
InputIDs utxo.OutputIDs
Inputs Outputs
InputsMetadata OutputsMetadata
Consumers []*Consumer
Outputs Outputs
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
9 changes: 6 additions & 3 deletions packages/ledger/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import (
"github.com/cockroachdb/errors"
)

// ErrTransactionInvalid is returned if a Transaction or any of its building blocks is considered to be invalid.
var ErrTransactionInvalid = errors.New("transaction invalid")
var (
// ErrTransactionInvalid is returned if a Transaction is found to be invalid.
ErrTransactionInvalid = errors.New("transaction invalid")

var ErrTransactionUnsolid = errors.New("transaction unsolid")
// ErrTransactionUnsolid is returned if a Transaction consumes unsolid Outputs..
ErrTransactionUnsolid = errors.New("transaction unsolid")
)
Loading

0 comments on commit c0ec24e

Please sign in to comment.