From 2338e8b9decfc48a67046590ab6347cbbc793128 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:04:17 +0200 Subject: [PATCH] Feat: bundled VM API in VM interface --- pkg/protocol/engine/ledger/ledger/ledger.go | 2 +- pkg/protocol/engine/ledger/ledger/vm.go | 26 +++++++++----- pkg/protocol/engine/mempool/tests/vm.go | 19 ++++++---- pkg/protocol/engine/mempool/transaction.go | 2 -- pkg/protocol/engine/mempool/v1/mempool.go | 36 +++++++------------ .../engine/mempool/v1/mempool_test.go | 20 ++++------- pkg/protocol/engine/mempool/vm.go | 14 ++++++-- 7 files changed, 62 insertions(+), 57 deletions(-) diff --git a/pkg/protocol/engine/ledger/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger/ledger.go index 6136c26b2..5818a92ba 100644 --- a/pkg/protocol/engine/ledger/ledger/ledger.go +++ b/pkg/protocol/engine/ledger/ledger/ledger.go @@ -68,7 +68,7 @@ func NewProvider() module.Provider[*engine.Engine, ledger.Ledger] { l.setRetainTransactionFailureFunc(e.Retainer.RetainTransactionFailure) - l.memPool = mempoolv1.New(l.validateStardustTransaction, l.executeStardustVM, l.extractInputReferences, l.resolveState, e.Workers.CreateGroup("MemPool"), l.conflictDAG, e, l.errorHandler, mempoolv1.WithForkAllTransactions[ledger.BlockVoteRank](true)) + l.memPool = mempoolv1.New(NewVM(l), l.resolveState, e.Workers.CreateGroup("MemPool"), l.conflictDAG, l.errorHandler, mempoolv1.WithForkAllTransactions[ledger.BlockVoteRank](true)) e.EvictionState.Events.SlotEvicted.Hook(l.memPool.Evict) l.manaManager = mana.NewManager(l.apiProvider, l.resolveAccountOutput) diff --git a/pkg/protocol/engine/ledger/ledger/vm.go b/pkg/protocol/engine/ledger/ledger/vm.go index daf263543..20eb5bf3a 100644 --- a/pkg/protocol/engine/ledger/ledger/vm.go +++ b/pkg/protocol/engine/ledger/ledger/vm.go @@ -11,7 +11,17 @@ import ( "github.com/iotaledger/iota.go/v4/vm/stardust" ) -func (l *Ledger) extractInputReferences(transaction mempool.Transaction) (inputReferences []iotago.Input, err error) { +type VM struct { + ledger *Ledger +} + +func NewVM(ledger *Ledger) *VM { + return &VM{ + ledger: ledger, + } +} + +func (v *VM) TransactionInputs(transaction mempool.Transaction) (inputReferences []iotago.Input, err error) { stardustTransaction, ok := transaction.(*iotago.Transaction) if !ok { return nil, iotago.ErrTxTypeInvalid @@ -27,7 +37,7 @@ func (l *Ledger) extractInputReferences(transaction mempool.Transaction) (inputR return inputReferences, nil } -func (l *Ledger) validateStardustTransaction(signedTransaction mempool.SignedTransaction, resolvedInputStates []mempool.State) (executionContext context.Context, err error) { +func (v *VM) ValidateSignatures(signedTransaction mempool.SignedTransaction, resolvedInputStates []mempool.State) (executionContext context.Context, err error) { signedStardustTransaction, ok := signedTransaction.(*iotago.SignedTransaction) if !ok { return nil, iotago.ErrTxTypeInvalid @@ -57,7 +67,7 @@ func (l *Ledger) validateStardustTransaction(signedTransaction mempool.SignedTra bicInputSet := make(iotagovm.BlockIssuanceCreditInputSet) for _, inp := range bicInputs { - accountData, exists, accountErr := l.accountsLedger.Account(inp.AccountID, commitmentInput.Slot) + accountData, exists, accountErr := v.ledger.accountsLedger.Account(inp.AccountID, commitmentInput.Slot) if accountErr != nil { return nil, ierrors.Join(iotago.ErrBICInputInvalid, ierrors.Wrapf(accountErr, "could not get BIC input for account %s in slot %d", inp.AccountID, commitmentInput.Slot)) } @@ -87,7 +97,7 @@ func (l *Ledger) validateStardustTransaction(signedTransaction mempool.SignedTra accountID = iotago.AccountIDFromOutputID(outputID) } - reward, _, _, rewardErr := l.sybilProtection.ValidatorReward(accountID, stakingFeature.StakedAmount, stakingFeature.StartEpoch, stakingFeature.EndEpoch) + reward, _, _, rewardErr := v.ledger.sybilProtection.ValidatorReward(accountID, stakingFeature.StakedAmount, stakingFeature.StartEpoch, stakingFeature.EndEpoch) if rewardErr != nil { return nil, ierrors.Wrapf(iotago.ErrFailedToClaimStakingReward, "failed to get Validator reward for AccountOutput %s at index %d (StakedAmount: %d, StartEpoch: %d, EndEpoch: %d", outputID, inp.Index, stakingFeature.StakedAmount, stakingFeature.StartEpoch, stakingFeature.EndEpoch) } @@ -102,10 +112,10 @@ func (l *Ledger) validateStardustTransaction(signedTransaction mempool.SignedTra delegationEnd := castOutput.EndEpoch if delegationEnd == 0 { - delegationEnd = l.apiProvider.APIForSlot(commitmentInput.Slot).TimeProvider().EpochFromSlot(commitmentInput.Slot) - iotago.EpochIndex(1) + delegationEnd = v.ledger.apiProvider.APIForSlot(commitmentInput.Slot).TimeProvider().EpochFromSlot(commitmentInput.Slot) - iotago.EpochIndex(1) } - reward, _, _, rewardErr := l.sybilProtection.DelegatorReward(castOutput.ValidatorAddress.AccountID(), castOutput.DelegatedAmount, castOutput.StartEpoch, delegationEnd) + reward, _, _, rewardErr := v.ledger.sybilProtection.DelegatorReward(castOutput.ValidatorAddress.AccountID(), castOutput.DelegatedAmount, castOutput.StartEpoch, delegationEnd) if rewardErr != nil { return nil, ierrors.Wrapf(iotago.ErrFailedToClaimDelegationReward, "failed to get Delegator reward for DelegationOutput %s at index %d (StakedAmount: %d, StartEpoch: %d, EndEpoch: %d", outputID, inp.Index, castOutput.DelegatedAmount, castOutput.StartEpoch, castOutput.EndEpoch) } @@ -133,7 +143,7 @@ func (l *Ledger) validateStardustTransaction(signedTransaction mempool.SignedTra return executionContext, nil } -func (l *Ledger) executeStardustVM(executionContext context.Context, transaction mempool.Transaction) (outputs []mempool.State, err error) { +func (v *VM) ExecuteTransaction(executionContext context.Context, transaction mempool.Transaction) (outputs []mempool.State, err error) { stardustTransaction, ok := transaction.(*iotago.Transaction) if !ok { return nil, iotago.ErrTxTypeInvalid @@ -161,7 +171,7 @@ func (l *Ledger) executeStardustVM(executionContext context.Context, transaction for index, output := range createdOutputs { outputs = append(outputs, utxoledger.CreateOutput( - l.apiProvider, + v.ledger.apiProvider, iotago.OutputIDFromTransactionIDAndIndex(transactionID, uint16(index)), iotago.EmptyBlockID(), 0, diff --git a/pkg/protocol/engine/mempool/tests/vm.go b/pkg/protocol/engine/mempool/tests/vm.go index 4985e835f..b8f91a84a 100644 --- a/pkg/protocol/engine/mempool/tests/vm.go +++ b/pkg/protocol/engine/mempool/tests/vm.go @@ -6,24 +6,31 @@ import ( "github.com/iotaledger/hive.go/ierrors" ledgertests "github.com/iotaledger/iota-core/pkg/protocol/engine/ledger/tests" "github.com/iotaledger/iota-core/pkg/protocol/engine/mempool" + iotago "github.com/iotaledger/iota.go/v4" ) -func TransactionValidator(_ mempool.SignedTransaction, _ []mempool.State) (executionContext context.Context, err error) { +type VM struct{} + +func (V *VM) TransactionInputs(transaction mempool.Transaction) ([]iotago.Input, error) { + return transaction.(*Transaction).Inputs() +} + +func (V *VM) ValidateSignatures(signedTransaction mempool.SignedTransaction, resolvedInputs []mempool.State) (executionContext context.Context, err error) { return context.Background(), nil } -func TransactionExecutor(_ context.Context, inputTransaction mempool.Transaction) (outputs []mempool.State, err error) { - transaction, ok := inputTransaction.(*Transaction) +func (V *VM) ExecuteTransaction(executionContext context.Context, transaction mempool.Transaction) (outputs []mempool.State, err error) { + typedTransaction, ok := transaction.(*Transaction) if !ok { return nil, ierrors.New("invalid transaction type in MockedVM") } - if transaction.invalidTransaction { + if typedTransaction.invalidTransaction { return nil, ierrors.New("invalid transaction") } - for i := uint16(0); i < transaction.outputCount; i++ { - id, err := transaction.ID() + for i := uint16(0); i < typedTransaction.outputCount; i++ { + id, err := typedTransaction.ID() if err != nil { return nil, err } diff --git a/pkg/protocol/engine/mempool/transaction.go b/pkg/protocol/engine/mempool/transaction.go index fdad023a8..60b2dbde3 100644 --- a/pkg/protocol/engine/mempool/transaction.go +++ b/pkg/protocol/engine/mempool/transaction.go @@ -13,5 +13,3 @@ type Transaction interface { // ID returns the identifier of the Transaction. ID() (iotago.TransactionID, error) } - -type TransactionInputReferenceRetriever func(transaction Transaction) ([]iotago.Input, error) diff --git a/pkg/protocol/engine/mempool/v1/mempool.go b/pkg/protocol/engine/mempool/v1/mempool.go index 6e93935dc..76a9f677f 100644 --- a/pkg/protocol/engine/mempool/v1/mempool.go +++ b/pkg/protocol/engine/mempool/v1/mempool.go @@ -21,20 +21,12 @@ import ( // MemPool is a component that manages the state of transactions that are not yet included in the ledger state. type MemPool[VoteRank conflictdag.VoteRankType[VoteRank]] struct { - signedTransactionAttached *event.Event1[mempool.SignedTransactionMetadata] - - transactionAttached *event.Event1[mempool.TransactionMetadata] - - // executeStateTransition is the TransactionExecutor that is used to execute the state transition of transactions. - executeStateTransition mempool.TransactionExecutor + // vm is the virtual machine that is used to validate and execute transactions. + vm mempool.VM // resolveState is the function that is used to request state from the ledger. resolveState mempool.StateReferenceResolver - inputsOfTransaction mempool.TransactionInputReferenceRetriever - - validateSignedTransaction mempool.TransactionValidator - // attachments is the storage that is used to keep track of the attachments of transactions. attachments *memstorage.IndexedStorage[iotago.SlotIndex, iotago.BlockID, *SignedTransactionMetadata] @@ -66,27 +58,22 @@ type MemPool[VoteRank conflictdag.VoteRankType[VoteRank]] struct { optForkAllTransactions bool - apiProvider iotago.APIProvider + signedTransactionAttached *event.Event1[mempool.SignedTransactionMetadata] + + transactionAttached *event.Event1[mempool.TransactionMetadata] } // New is the constructor of the MemPool. func New[VoteRank conflictdag.VoteRankType[VoteRank]]( - transactionValidator mempool.TransactionValidator, - transactionExecutor mempool.TransactionExecutor, - transactionInputReferenceRetriever mempool.TransactionInputReferenceRetriever, + vm mempool.VM, stateResolver mempool.StateReferenceResolver, workers *workerpool.Group, conflictDAG conflictdag.ConflictDAG[iotago.TransactionID, mempool.StateID, VoteRank], - apiProvider iotago.APIProvider, errorHandler func(error), opts ...options.Option[MemPool[VoteRank]], ) *MemPool[VoteRank] { return options.Apply(&MemPool[VoteRank]{ - signedTransactionAttached: event.New1[mempool.SignedTransactionMetadata](), - transactionAttached: event.New1[mempool.TransactionMetadata](), - validateSignedTransaction: transactionValidator, - executeStateTransition: transactionExecutor, - inputsOfTransaction: transactionInputReferenceRetriever, + vm: vm, resolveState: stateResolver, attachments: memstorage.NewIndexedStorage[iotago.SlotIndex, iotago.BlockID, *SignedTransactionMetadata](), cachedTransactions: shrinkingmap.New[iotago.TransactionID, *TransactionMetadata](), @@ -95,8 +82,9 @@ func New[VoteRank conflictdag.VoteRankType[VoteRank]]( stateDiffs: shrinkingmap.New[iotago.SlotIndex, *StateDiff](), executionWorkers: workers.CreatePool("executionWorkers", 1), conflictDAG: conflictDAG, - apiProvider: apiProvider, errorHandler: errorHandler, + signedTransactionAttached: event.New1[mempool.SignedTransactionMetadata](), + transactionAttached: event.New1[mempool.TransactionMetadata](), }, opts, (*MemPool[VoteRank]).setup) } @@ -202,7 +190,7 @@ func (m *MemPool[VoteRank]) storeTransaction(signedTransaction mempool.SignedTra return nil, false, false, ierrors.Errorf("blockID %d is older than last evicted slot %d", blockID.Slot(), m.lastEvictedSlot) } - inputReferences, err := m.inputsOfTransaction(transaction) + inputReferences, err := m.vm.TransactionInputs(transaction) if err != nil { return nil, false, false, ierrors.Wrap(err, "failed to get input references of transaction") } @@ -261,7 +249,7 @@ func (m *MemPool[VoteRank]) solidifyInputs(transaction *TransactionMetadata) { func (m *MemPool[VoteRank]) executeTransaction(executionContext context.Context, transaction *TransactionMetadata) { m.executionWorkers.Submit(func() { - if outputStates, err := m.executeStateTransition(executionContext, transaction.Transaction()); err != nil { + if outputStates, err := m.vm.ExecuteTransaction(executionContext, transaction.Transaction()); err != nil { transaction.setInvalid(err) } else { transaction.setExecuted(outputStates) @@ -458,7 +446,7 @@ func (m *MemPool[VoteRank]) setupSignedTransaction(signedTransactionMetadata *Si transaction.addSigningTransaction(signedTransactionMetadata) transaction.OnSolid(func() { - executionContext, err := m.validateSignedTransaction(signedTransactionMetadata.SignedTransaction(), lo.Map(signedTransactionMetadata.transactionMetadata.inputs, (*StateMetadata).State)) + executionContext, err := m.vm.ValidateSignatures(signedTransactionMetadata.SignedTransaction(), lo.Map(signedTransactionMetadata.transactionMetadata.inputs, (*StateMetadata).State)) if err != nil { _ = signedTransactionMetadata.signaturesInvalid.Set(err) return diff --git a/pkg/protocol/engine/mempool/v1/mempool_test.go b/pkg/protocol/engine/mempool/v1/mempool_test.go index bb955f1a3..a79ff1068 100644 --- a/pkg/protocol/engine/mempool/v1/mempool_test.go +++ b/pkg/protocol/engine/mempool/v1/mempool_test.go @@ -19,8 +19,6 @@ import ( "github.com/iotaledger/iota-core/pkg/protocol/engine/mempool/conflictdag/conflictdagv1" mempooltests "github.com/iotaledger/iota-core/pkg/protocol/engine/mempool/tests" iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/api" - "github.com/iotaledger/iota.go/v4/tpkg" ) func TestMemPoolV1_InterfaceWithoutForkingEverything(t *testing.T) { @@ -36,11 +34,9 @@ func TestMempoolV1_ResourceCleanup(t *testing.T) { ledgerState := ledgertests.New(ledgertests.NewMockedState(iotago.TransactionID{}, 0)) conflictDAG := conflictdagv1.New[iotago.TransactionID, mempool.StateID, vote.MockedRank](func() int { return 0 }) - mempoolInstance := New[vote.MockedRank](mempooltests.TransactionValidator, mempooltests.TransactionExecutor, func(transaction mempool.Transaction) ([]iotago.Input, error) { - return transaction.(*mempooltests.Transaction).Inputs() - }, func(reference iotago.Input) *promise.Promise[mempool.State] { + mempoolInstance := New[vote.MockedRank](new(mempooltests.VM), func(reference iotago.Input) *promise.Promise[mempool.State] { return ledgerState.ResolveOutputState(reference.StateID()) - }, workers, conflictDAG, api.SingleVersionProvider(tpkg.TestAPI), func(error) {}) + }, workers, conflictDAG, func(error) {}) tf := mempooltests.NewTestFramework(t, mempoolInstance, conflictDAG, ledgerState, workers) @@ -107,11 +103,9 @@ func newTestFramework(t *testing.T) *mempooltests.TestFramework { ledgerState := ledgertests.New(ledgertests.NewMockedState(iotago.TransactionID{}, 0)) conflictDAG := conflictdagv1.New[iotago.TransactionID, mempool.StateID, vote.MockedRank](account.NewAccounts().SelectCommittee().SeatCount) - return mempooltests.NewTestFramework(t, New[vote.MockedRank](mempooltests.TransactionValidator, mempooltests.TransactionExecutor, func(transaction mempool.Transaction) ([]iotago.Input, error) { - return transaction.(*mempooltests.Transaction).Inputs() - }, func(reference iotago.Input) *promise.Promise[mempool.State] { + return mempooltests.NewTestFramework(t, New[vote.MockedRank](new(mempooltests.VM), func(reference iotago.Input) *promise.Promise[mempool.State] { return ledgerState.ResolveOutputState(reference.StateID()) - }, workers, conflictDAG, api.SingleVersionProvider(tpkg.TestAPI), func(error) {}), conflictDAG, ledgerState, workers) + }, workers, conflictDAG, func(error) {}), conflictDAG, ledgerState, workers) } func newForkingTestFramework(t *testing.T) *mempooltests.TestFramework { @@ -120,9 +114,7 @@ func newForkingTestFramework(t *testing.T) *mempooltests.TestFramework { ledgerState := ledgertests.New(ledgertests.NewMockedState(iotago.TransactionID{}, 0)) conflictDAG := conflictdagv1.New[iotago.TransactionID, mempool.StateID, vote.MockedRank](account.NewAccounts().SelectCommittee().SeatCount) - return mempooltests.NewTestFramework(t, New[vote.MockedRank](mempooltests.TransactionValidator, mempooltests.TransactionExecutor, func(transaction mempool.Transaction) ([]iotago.Input, error) { - return transaction.(*mempooltests.Transaction).Inputs() - }, func(reference iotago.Input) *promise.Promise[mempool.State] { + return mempooltests.NewTestFramework(t, New[vote.MockedRank](new(mempooltests.VM), func(reference iotago.Input) *promise.Promise[mempool.State] { return ledgerState.ResolveOutputState(reference.StateID()) - }, workers, conflictDAG, api.SingleVersionProvider(tpkg.TestAPI), func(error) {}, WithForkAllTransactions[vote.MockedRank](true)), conflictDAG, ledgerState, workers) + }, workers, conflictDAG, func(error) {}, WithForkAllTransactions[vote.MockedRank](true)), conflictDAG, ledgerState, workers) } diff --git a/pkg/protocol/engine/mempool/vm.go b/pkg/protocol/engine/mempool/vm.go index 0d05fb49c..49ffc1d80 100644 --- a/pkg/protocol/engine/mempool/vm.go +++ b/pkg/protocol/engine/mempool/vm.go @@ -2,8 +2,18 @@ package mempool import ( "context" + + iotago "github.com/iotaledger/iota.go/v4" ) -type TransactionValidator func(signedTransaction SignedTransaction, resolvedInputs []State) (executionContext context.Context, err error) +// VM is the interface that defines the virtual machine that is used to validate and execute transactions. +type VM interface { + // TransactionInputs returns the inputs of the given transaction. + TransactionInputs(transaction Transaction) ([]iotago.Input, error) + + // ValidateSignatures validates the signatures of the given SignedTransaction and returns the execution context. + ValidateSignatures(signedTransaction SignedTransaction, resolvedInputs []State) (executionContext context.Context, err error) -type TransactionExecutor func(executionContext context.Context, transaction Transaction) (outputs []State, err error) + // ExecuteTransaction executes the transaction in the given execution context and returns the resulting states. + ExecuteTransaction(executionContext context.Context, transaction Transaction) (outputs []State, err error) +}