From 28193665be4d6471a4a087746ffecd4b32d1ec69 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Thu, 31 Mar 2022 15:16:33 +0200 Subject: [PATCH] Feat: added test framework --- packages/refactored/ledger/booker.go | 14 ++++ packages/refactored/ledger/dataflow.go | 11 ++- packages/refactored/ledger/ledger.go | 33 ++------ packages/refactored/ledger/ledger_test.go | 10 +++ packages/refactored/ledger/solidifier.go | 16 +--- packages/refactored/ledger/storage.go | 12 ++- .../ledger/{testutils.go => testframework.go} | 75 +++++++++++++++++++ .../refactored/ledger/{executor.go => vm.go} | 0 packages/refactored/utxo/outputid.go | 2 +- packages/refactored/utxo/outputids.go | 6 +- packages/refactored/utxo/transactionid.go | 2 +- 11 files changed, 134 insertions(+), 47 deletions(-) rename packages/refactored/ledger/{testutils.go => testframework.go} (74%) rename packages/refactored/ledger/{executor.go => vm.go} (100%) diff --git a/packages/refactored/ledger/booker.go b/packages/refactored/ledger/booker.go index db956b2b5a..cc4bd80208 100644 --- a/packages/refactored/ledger/booker.go +++ b/packages/refactored/ledger/booker.go @@ -20,6 +20,20 @@ func NewBooker(ledger *Ledger) (new *Booker) { } } +func (b *Booker) checkAlreadyBookedCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) { + if params.TransactionMetadata == nil { + cachedTransactionMetadata := b.CachedTransactionMetadata(params.Transaction.ID()) + defer cachedTransactionMetadata.Release() + params.TransactionMetadata, _ = cachedTransactionMetadata.Unwrap() + } + + if params.TransactionMetadata.Booked() { + return nil + } + + return next(params) +} + func (b *Booker) bookTransactionCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) { b.bookTransaction(params.TransactionMetadata, params.InputsMetadata, params.Consumers, params.Outputs) diff --git a/packages/refactored/ledger/dataflow.go b/packages/refactored/ledger/dataflow.go index ce60d4d18b..ba3a22f008 100644 --- a/packages/refactored/ledger/dataflow.go +++ b/packages/refactored/ledger/dataflow.go @@ -2,6 +2,7 @@ package ledger import ( "github.com/iotaledger/hive.go/generics/dataflow" + "github.com/iotaledger/hive.go/generics/event" ) type DataFlow struct { @@ -14,6 +15,14 @@ func NewDataFlow(ledger *Ledger) *DataFlow { } } +func (d *DataFlow) Setup() { + d.TransactionBookedEvent.Attach(event.NewClosure[TransactionID](func(txID TransactionID) { + d.CachedTransaction(txID).Consume(func(tx *Transaction) { + _ = d.Ledger.processTransaction(tx) + }) + })) +} + func (d *DataFlow) storeAndProcessTransaction() *dataflow.DataFlow[*dataFlowParams] { return dataflow.New[*dataFlowParams]( d.storeTransactionCommand, @@ -23,7 +32,7 @@ func (d *DataFlow) storeAndProcessTransaction() *dataflow.DataFlow[*dataFlowPara func (d *DataFlow) processTransaction() *dataflow.DataFlow[*dataFlowParams] { return dataflow.New[*dataFlowParams]( - d.initConsumersCommand, + d.checkAlreadyBookedCommand, d.checkTransaction().ChainedCommand, d.bookTransactionCommand, ).WithErrorCallback(func(err error, params *dataFlowParams) { diff --git a/packages/refactored/ledger/ledger.go b/packages/refactored/ledger/ledger.go index a9e8945871..0804c68334 100644 --- a/packages/refactored/ledger/ledger.go +++ b/packages/refactored/ledger/ledger.go @@ -18,13 +18,14 @@ type Ledger struct { TransactionBookedEvent *event.Event[TransactionID] ErrorEvent *event.Event[error] - DataFlow *DataFlow - *Options *Storage *Solidifier *Validator *VM *Booker + + *DataFlow + *Options *Utils *branchdag.BranchDAG @@ -54,7 +55,6 @@ func New(store kvstore.KVStore, vm utxo.VM, options ...Option) (ledger *Ledger) return ledger } -// Configure modifies the configuration of the Ledger. func (l *Ledger) Configure(options ...Option) { if l.Options == nil { l.Options = &Options{ @@ -69,41 +69,22 @@ func (l *Ledger) Configure(options ...Option) { } } -func (l *Ledger) Setup() { - l.TransactionBookedEvent.Attach(event.NewClosure[TransactionID](func(txID TransactionID) { - l.CachedTransactionMetadata(txID).Consume(func(txMetadata *TransactionMetadata) { - l.CachedTransaction(txID).Consume(func(tx *Transaction) { - _ = l.processTransaction(tx, txMetadata) - }) - }) - })) -} - -// StoreAndProcessTransaction is the only public facing api func (l *Ledger) StoreAndProcessTransaction(tx utxo.Transaction) (err error) { l.Lock(tx.ID()) defer l.Unlock(tx.ID()) - return l.DataFlow.storeAndProcessTransaction().Run(&dataFlowParams{ - Transaction: NewTransaction(tx), - }) + return l.DataFlow.storeAndProcessTransaction().Run(&dataFlowParams{Transaction: NewTransaction(tx)}) } func (l *Ledger) CheckTransaction(tx utxo.Transaction) (err error) { - return l.DataFlow.checkTransaction().Run(&dataFlowParams{ - Transaction: NewTransaction(tx), - InputIDs: l.resolveInputs(tx.Inputs()), - }) + return l.DataFlow.checkTransaction().Run(&dataFlowParams{Transaction: NewTransaction(tx)}) } -func (l *Ledger) processTransaction(tx *Transaction, txMetadata *TransactionMetadata) (err error) { +func (l *Ledger) processTransaction(tx *Transaction) (err error) { l.Lock(tx.ID()) defer l.Unlock(tx.ID()) - return l.DataFlow.processTransaction().Run(&dataFlowParams{ - Transaction: tx, - TransactionMetadata: txMetadata, - }) + return l.DataFlow.processTransaction().Run(&dataFlowParams{Transaction: tx}) } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/refactored/ledger/ledger_test.go b/packages/refactored/ledger/ledger_test.go index fb83adc0f9..35117170cf 100644 --- a/packages/refactored/ledger/ledger_test.go +++ b/packages/refactored/ledger/ledger_test.go @@ -13,6 +13,14 @@ import ( ) func TestLedger(t *testing.T) { + testFramework := NewTestFramework() + + testFramework.CreateTransaction("TX1", 2, "Genesis") + testFramework.CreateTransaction("TX2", 2, "TX1.0") + testFramework.CreateTransaction("TX3", 2, "TX1.1") + + fmt.Println(testFramework.IssueTransaction("TX2")) + vm := NewMockedVM() genesisOutput := NewOutput(NewMockedOutput(utxo.EmptyTransactionID, 0)) @@ -40,6 +48,8 @@ func TestLedger(t *testing.T) { NewMockedInput(nonExistingOutput.ID()), }, 2) + fmt.Println("CHECK: ", ledger.CheckTransaction(tx1)) + tx1.ID().RegisterAlias("TX1") fmt.Println(tx1.ID()) diff --git a/packages/refactored/ledger/solidifier.go b/packages/refactored/ledger/solidifier.go index 306cc26991..b09117e454 100644 --- a/packages/refactored/ledger/solidifier.go +++ b/packages/refactored/ledger/solidifier.go @@ -18,21 +18,11 @@ func NewSolidifier(ledger *Ledger) (new *Solidifier) { } } -func (s *Solidifier) initConsumersCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) { - if params.TransactionMetadata.Booked() { - return nil +func (s *Solidifier) checkSolidityCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) { + if params.InputIDs.IsEmpty() { + params.InputIDs = s.resolveInputs(params.Transaction.Inputs()) } - params.InputIDs = s.resolveInputs(params.Transaction.Inputs()) - - cachedConsumers := s.initConsumers(params.InputIDs, params.Transaction.ID()) - defer cachedConsumers.Release() - params.Consumers = cachedConsumers.Unwrap() - - return next(params) -} - -func (s *Solidifier) checkSolidityCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) { cachedInputs := s.CachedOutputs(params.InputIDs) defer cachedInputs.Release() if params.Inputs = NewOutputs(cachedInputs.Unwrap(true)...); params.Inputs.Size() != len(cachedInputs) { diff --git a/packages/refactored/ledger/storage.go b/packages/refactored/ledger/storage.go index b8e63ff8be..9ef727e3db 100644 --- a/packages/refactored/ledger/storage.go +++ b/packages/refactored/ledger/storage.go @@ -60,10 +60,10 @@ func NewStorage(ledger *Ledger) (newStorage *Storage) { } } -func (d *DataFlow) storeTransactionCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) { +func (s *Storage) storeTransactionCommand(params *dataFlowParams, next dataflow.Next[*dataFlowParams]) (err error) { created := false - cachedTransactionMetadata := d.CachedTransactionMetadata(params.Transaction.ID(), func(txID TransactionID) *TransactionMetadata { - d.transactionStorage.Store(params.Transaction).Release() + cachedTransactionMetadata := s.CachedTransactionMetadata(params.Transaction.ID(), func(txID TransactionID) *TransactionMetadata { + s.transactionStorage.Store(params.Transaction).Release() created = true return NewTransactionMetadata(txID) }) @@ -79,7 +79,11 @@ func (d *DataFlow) storeTransactionCommand(params *dataFlowParams, next dataflow return errors.Errorf("%s is an unsolid reattachment: %w", params.Transaction.ID(), ErrTransactionUnsolid) } - d.TransactionStoredEvent.Trigger(params.Transaction.ID()) + cachedConsumers := s.initConsumers(params.InputIDs, params.Transaction.ID()) + defer cachedConsumers.Release() + params.Consumers = cachedConsumers.Unwrap(true) + + s.TransactionStoredEvent.Trigger(params.Transaction.ID()) return next(params) } diff --git a/packages/refactored/ledger/testutils.go b/packages/refactored/ledger/testframework.go similarity index 74% rename from packages/refactored/ledger/testutils.go rename to packages/refactored/ledger/testframework.go index 480a5b3721..f280c56068 100644 --- a/packages/refactored/ledger/testutils.go +++ b/packages/refactored/ledger/testframework.go @@ -1,10 +1,13 @@ package ledger import ( + "fmt" + "strconv" "sync" "sync/atomic" "github.com/iotaledger/hive.go/generics/objectstorage" + "github.com/iotaledger/hive.go/kvstore/mapdb" "github.com/iotaledger/hive.go/marshalutil" "github.com/iotaledger/hive.go/stringify" @@ -12,6 +15,66 @@ import ( "github.com/iotaledger/goshimmer/packages/refactored/utxo" ) +// region TestFramework //////////////////////////////////////////////////////////////////////////////////////////////// + +type TestFramework struct { + Ledger *Ledger + + transactionsByAlias map[string]*MockedTransaction + outputIDsByAlias map[string]OutputID +} + +func NewTestFramework(options ...Option) (new *TestFramework) { + new = &TestFramework{ + Ledger: New(mapdb.NewMapDB(), NewMockedVM(), options...), + + transactionsByAlias: make(map[string]*MockedTransaction), + outputIDsByAlias: make(map[string]OutputID), + } + + var genesisOutputID OutputID + genesisOutputID.RegisterAlias("Genesis") + + new.outputIDsByAlias["Genesis"] = genesisOutputID + + return new +} + +func (t *TestFramework) CreateTransaction(txAlias string, outputCount uint16, inputAliases ...string) { + mockedInputs := make([]*MockedInput, 0) + for _, inputAlias := range inputAliases { + outputID, exists := t.outputIDsByAlias[inputAlias] + if !exists { + panic(fmt.Sprintf("unknown input alias: %s", inputAlias)) + } + + mockedInputs = append(mockedInputs, NewMockedInput(outputID)) + } + + tx := NewMockedTransaction(mockedInputs, outputCount) + tx.ID().RegisterAlias(txAlias) + t.transactionsByAlias[txAlias] = tx + + for i := uint16(0); i < outputCount; i++ { + outputID := utxo.NewOutputID(tx.ID(), i, []byte("")) + outputAlias := txAlias + "." + strconv.Itoa(int(i)) + + outputID.RegisterAlias(outputAlias) + t.outputIDsByAlias[outputAlias] = outputID + } +} + +func (t *TestFramework) IssueTransaction(txAlias string) (err error) { + transaction, exists := t.transactionsByAlias[txAlias] + if !exists { + panic(fmt.Sprintf("unknown transaction alias: %s", txAlias)) + } + + return t.Ledger.StoreAndProcessTransaction(transaction) +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + // region MockedInput ////////////////////////////////////////////////////////////////////////////////////////////////// type MockedInput struct { @@ -211,8 +274,20 @@ func (m *MockedTransaction) Bytes() []byte { } func (m *MockedTransaction) String() (humanReadable string) { + inputIDs := NewOutputIDs() + for _, input := range m.Inputs() { + inputIDs.Add(input.(*MockedInput).outputID) + } + + outputIDs := NewOutputIDs() + for i := uint16(0); i < m.outputCount; i++ { + outputIDs.Add(utxo.NewOutputID(m.ID(), i, []byte(""))) + } + return stringify.Struct("MockedTransaction", stringify.StructField("id", m.ID()), + stringify.StructField("inputs", inputIDs), + stringify.StructField("outputs", outputIDs), ) } diff --git a/packages/refactored/ledger/executor.go b/packages/refactored/ledger/vm.go similarity index 100% rename from packages/refactored/ledger/executor.go rename to packages/refactored/ledger/vm.go diff --git a/packages/refactored/utxo/outputid.go b/packages/refactored/utxo/outputid.go index 0303967f58..3bebaec562 100644 --- a/packages/refactored/utxo/outputid.go +++ b/packages/refactored/utxo/outputid.go @@ -101,7 +101,7 @@ func (o OutputID) String() (humanReadable string) { defer _outputIDAliasesMutex.RUnlock() if alias, exists := _outputIDAliases[o]; exists { - return "OutputID(" + alias + ")" + return alias } return "OutputID(" + base58.Encode(o[:]) + ")" diff --git a/packages/refactored/utxo/outputids.go b/packages/refactored/utxo/outputids.go index d75bcea63e..af025a8a86 100644 --- a/packages/refactored/utxo/outputids.go +++ b/packages/refactored/utxo/outputids.go @@ -45,6 +45,10 @@ func (o OutputIDs) FromMarshalUtil(marshalUtil *marshalutil.MarshalUtil) (err er return nil } +func (o OutputIDs) IsEmpty() (empty bool) { + return o.OrderedMap == nil || o.OrderedMap.Size() == 0 +} + func (o OutputIDs) Add(outputID OutputID) (added bool) { return o.Set(outputID, types.Void) } @@ -136,7 +140,7 @@ func (o OutputIDs) Bytes() (serialized []byte) { } func (o OutputIDs) String() (humanReadable string) { - elementStrings := generics.Map(o.Slice(), OutputID.Base58) + elementStrings := generics.Map(o.Slice(), OutputID.String) if len(elementStrings) == 0 { return "OutputIDs()" } diff --git a/packages/refactored/utxo/transactionid.go b/packages/refactored/utxo/transactionid.go index 447dc77a93..fef755ac09 100644 --- a/packages/refactored/utxo/transactionid.go +++ b/packages/refactored/utxo/transactionid.go @@ -96,7 +96,7 @@ func (t TransactionID) String() string { defer _transactionIDAliasesMutex.RUnlock() if alias, exists := _transactionIDAliases[t]; exists { - return "TransactionID(" + alias + ")" + return alias } return "TransactionID(" + t.Base58() + ")"