Skip to content

Commit

Permalink
Feat: bundled VM API in VM interface
Browse files Browse the repository at this point in the history
  • Loading branch information
hmoog committed Oct 3, 2023
1 parent c3e498e commit 2338e8b
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 57 deletions.
2 changes: 1 addition & 1 deletion pkg/protocol/engine/ledger/ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 18 additions & 8 deletions pkg/protocol/engine/ledger/ledger/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
19 changes: 13 additions & 6 deletions pkg/protocol/engine/mempool/tests/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Check failure on line 15 in pkg/protocol/engine/mempool/tests/vm.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/protocol/engine/mempool/tests/vm.go#L15

type assertion must be checked (forcetypeassert)
Raw output
pkg/protocol/engine/mempool/tests/vm.go:15:9: type assertion must be checked (forcetypeassert)
	return transaction.(*Transaction).Inputs()
	       ^
}

func (V *VM) ValidateSignatures(signedTransaction mempool.SignedTransaction, resolvedInputs []mempool.State) (executionContext context.Context, err error) {

Check failure on line 18 in pkg/protocol/engine/mempool/tests/vm.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/protocol/engine/mempool/tests/vm.go#L18

unused-parameter: parameter 'signedTransaction' seems to be unused, consider removing or renaming it as _ (revive)
Raw output
pkg/protocol/engine/mempool/tests/vm.go:18:33: unused-parameter: parameter 'signedTransaction' seems to be unused, consider removing or renaming it as _ (revive)
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) {

Check failure on line 22 in pkg/protocol/engine/mempool/tests/vm.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/protocol/engine/mempool/tests/vm.go#L22

unused-parameter: parameter 'executionContext' seems to be unused, consider removing or renaming it as _ (revive)
Raw output
pkg/protocol/engine/mempool/tests/vm.go:22:33: unused-parameter: parameter 'executionContext' seems to be unused, consider removing or renaming it as _ (revive)
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
}
Expand Down
2 changes: 0 additions & 2 deletions pkg/protocol/engine/mempool/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
36 changes: 12 additions & 24 deletions pkg/protocol/engine/mempool/v1/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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](),
Expand All @@ -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)
}

Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
20 changes: 6 additions & 14 deletions pkg/protocol/engine/mempool/v1/mempool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
14 changes: 12 additions & 2 deletions pkg/protocol/engine/mempool/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 2338e8b

Please sign in to comment.