diff --git a/auth/ed25519.go b/auth/ed25519.go index 6546ad7dd3..485053fff6 100644 --- a/auth/ed25519.go +++ b/auth/ed25519.go @@ -102,6 +102,14 @@ func (d *ED25519Factory) Address() codec.Address { return NewED25519Address(d.priv.PublicKey()) } +func GenerateED25519AuthFactory() (chain.AuthFactory, error) { + privateKey, err := ed25519.GeneratePrivateKey() + if err != nil { + return nil, err + } + return NewED25519Factory(privateKey), nil +} + type ED25519AuthEngine struct{} func (*ED25519AuthEngine) GetBatchVerifier(cores int, count int) chain.AuthBatchVerifier { diff --git a/docs/tutorials/morpheusvm/5_testing.md b/docs/tutorials/morpheusvm/5_testing.md index 350643f7a3..183b69b20e 100644 --- a/docs/tutorials/morpheusvm/5_testing.md +++ b/docs/tutorials/morpheusvm/5_testing.md @@ -355,7 +355,7 @@ import ( // ref https://onsi.github.io/ginkgo/#mental-model-how-ginkgo-traverses-the-spec-hierarchy var TestsRegistry = ®istry.Registry{} -var _ = registry.Register(TestsRegistry, "Transfer Transaction", func(t ginkgo.FullGinkgoTInterface, tn tworkload.TestNetwork) { +var _ = registry.Register(TestsRegistry, "Transfer Transaction", func(t ginkgo.FullGinkgoTInterface, tn tworkload.TestNetwork, authFactories []chain.AuthFactory) { }) ``` @@ -365,30 +365,36 @@ registry of all the tests that we want to run against our VM. Afterwards, we have the following snippet: ```go -registry.Register(TestsRegistry, "Transfer Transaction", func(t ginkgo.FullGinkgoTInterface, tn tworkload.TestNetwork) { +registry.Register(TestsRegistry, "Transfer Transaction", func(t ginkgo.FullGinkgoTInterface, tn tworkload.TestNetwork, authFactories ...chain.AuthFactory) { -}) +}, 1000000, 1000000) ``` -Here, we are adding a test to `TestRegistry`. However, we're +Here, we are adding a test to `TestRegistry`, requesting an authFactory to be funded with 1_000_000 tokens. However, we're missing the test itself. In short, here's what we want to do in our testing logic: -- Setup necessary values +- Setup necessary values & Check the funds of the requested authFactories - Create our test TX - Send our TX - Require that our TX is sent and that the outputs are as expected +- Check the receiver has received the funds Focusing on the first step, we can write the following inside the anonymous function: ```go require := require.New(t) - other, err := ed25519.GeneratePrivateKey() - require.NoError(err) - toAddress := auth.NewED25519Address(other.PublicKey()) + ctx := context.Background() + sourceAuthFactory, targetAuthFactory := authFactories[0], authFactories[1] - authFactory := tn.Configuration().AuthFactories()[0] + client := jsonrpc.NewJSONRPCClient(tn.URIs()[0]) + sourceBalance, err := client.GetBalance(ctx, sourceAuthFactory.Address()) + require.NoError(err) + require.Equal(uint64(1000000), sourceBalance) + targetBalance, err := client.GetBalance(ctx, targetAuthFactory.Address()) + require.NoError(err) + require.Equal(uint64(1000000), targetBalance) ``` Next, we'll create our test transaction. In short, we'll want to send a value of @@ -396,20 +402,21 @@ Next, we'll create our test transaction. In short, we'll want to send a value of ```go tx, err := tn.GenerateTx(context.Background(), []chain.Action{&actions.Transfer{ - To: toAddress, + To: targetAuthFactory.Address(, Value: 1, }}, - authFactory, + sourceAuthFactory, ) require.NoError(err) ``` -Finally, we'll want to send our TX and do the checks mentioned in the last step. +Finally, we'll want to send our TX, check that the Tx has been executed and receiver has received the amount of token. This step will consist of the following: - Creating a context with a deadline of 2 seconds - If the test takes longer than 2 seconds, it will fail - Calling `ConfirmTxs` with our TX being passed in +- Requesting the balance The function `ConfirmTXs` is useful as it checks that our TX was sent and that, if finalized, our transaction has the expected outputs. We have @@ -418,8 +425,14 @@ the following: ```go timeoutCtx, timeoutCtxFnc := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second)) defer timeoutCtxFnc() - require.NoError(tn.ConfirmTxs(timeoutCtx, []*chain.Transaction{tx})) + + sourceBalance, err = client.GetBalance(ctx, sourceAuthFactory.Address()) + require.NoError(err) + require.True(uint64(1000000) > sourceBalance) + targetBalance, err = client.GetBalance(ctx, targetAuthFactory.Address()) + require.NoError(err) + require.Equal(uint64(1000001), targetBalance) ``` ## Registering our Tests diff --git a/examples/morpheusvm/tests/e2e/e2e_test.go b/examples/morpheusvm/tests/e2e/e2e_test.go index e747f7766e..a392e81fe1 100644 --- a/examples/morpheusvm/tests/e2e/e2e_test.go +++ b/examples/morpheusvm/tests/e2e/e2e_test.go @@ -10,11 +10,10 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/stretchr/testify/require" - _ "github.com/ava-labs/hypersdk/examples/morpheusvm/tests" // include the tests that are shared between the integration and e2e - "github.com/ava-labs/hypersdk/abi" "github.com/ava-labs/hypersdk/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" + "github.com/ava-labs/hypersdk/examples/morpheusvm/tests" // include the tests that are shared between the integration and e2e "github.com/ava-labs/hypersdk/examples/morpheusvm/tests/workload" "github.com/ava-labs/hypersdk/examples/morpheusvm/throughput" "github.com/ava-labs/hypersdk/examples/morpheusvm/vm" @@ -40,7 +39,9 @@ func init() { var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { require := require.New(ginkgo.GinkgoT()) - testingNetworkConfig, err := workload.NewTestNetworkConfig(100 * time.Millisecond) + customAllocs, err := tests.TestsRegistry.GenerateCustomAllocations(auth.GenerateED25519AuthFactory) + require.NoError(err) + testingNetworkConfig, err := workload.NewTestNetworkConfig(100*time.Millisecond, customAllocs) require.NoError(err) expectedABI, err := abi.NewABI(vm.ActionParser.GetRegisteredTypes(), vm.OutputParser.GetRegisteredTypes()) diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index f616b2939e..71f891bbdd 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -8,10 +8,9 @@ import ( "github.com/stretchr/testify/require" - _ "github.com/ava-labs/hypersdk/examples/morpheusvm/tests" // include the tests that are shared between the integration and e2e - "github.com/ava-labs/hypersdk/auth" "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/ava-labs/hypersdk/examples/morpheusvm/tests" // include the tests that are shared between the integration and e2e "github.com/ava-labs/hypersdk/examples/morpheusvm/tests/workload" "github.com/ava-labs/hypersdk/examples/morpheusvm/vm" "github.com/ava-labs/hypersdk/tests/integration" @@ -27,7 +26,9 @@ func TestIntegration(t *testing.T) { var _ = ginkgo.BeforeSuite(func() { require := require.New(ginkgo.GinkgoT()) - testingNetworkConfig, err := workload.NewTestNetworkConfig(0) + customAllocs, err := tests.TestsRegistry.GenerateCustomAllocations(auth.GenerateED25519AuthFactory) + require.NoError(err) + testingNetworkConfig, err := workload.NewTestNetworkConfig(0, customAllocs) require.NoError(err) randomEd25519Priv, err := ed25519.GeneratePrivateKey() diff --git a/examples/morpheusvm/tests/transfer.go b/examples/morpheusvm/tests/transfer.go index 11d3387437..2df64acfb2 100644 --- a/examples/morpheusvm/tests/transfer.go +++ b/examples/morpheusvm/tests/transfer.go @@ -9,9 +9,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk/auth" + "github.com/ava-labs/hypersdk/api/jsonrpc" "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/tests/registry" @@ -24,23 +23,36 @@ import ( // ref https://onsi.github.io/ginkgo/#mental-model-how-ginkgo-traverses-the-spec-hierarchy var TestsRegistry = ®istry.Registry{} -var _ = registry.Register(TestsRegistry, "Transfer Transaction", func(t ginkgo.FullGinkgoTInterface, tn tworkload.TestNetwork) { - require := require.New(t) - other, err := ed25519.GeneratePrivateKey() - require.NoError(err) - toAddress := auth.NewED25519Address(other.PublicKey()) - - authFactory := tn.Configuration().AuthFactories()[0] - tx, err := tn.GenerateTx(context.Background(), []chain.Action{&actions.Transfer{ - To: toAddress, - Value: 1, - }}, - authFactory, - ) - require.NoError(err) - - timeoutCtx, timeoutCtxFnc := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second)) - defer timeoutCtxFnc() - - require.NoError(tn.ConfirmTxs(timeoutCtx, []*chain.Transaction{tx})) -}) +var _ = registry.Register(TestsRegistry, "Transfer Transaction", + func(t ginkgo.FullGinkgoTInterface, tn tworkload.TestNetwork, authFactories []chain.AuthFactory) { + require := require.New(t) + ctx := context.Background() + sourceAuthFactory, targetAuthFactory := authFactories[0], authFactories[1] + + client := jsonrpc.NewJSONRPCClient(tn.URIs()[0]) + sourceBalance, err := client.GetBalance(ctx, sourceAuthFactory.Address()) + require.NoError(err) + require.Equal(uint64(1000000), sourceBalance) + targetBalance, err := client.GetBalance(ctx, targetAuthFactory.Address()) + require.NoError(err) + require.Equal(uint64(1000000), targetBalance) + + tx, err := tn.GenerateTx(ctx, []chain.Action{&actions.Transfer{ + To: targetAuthFactory.Address(), + Value: 1, + }}, + sourceAuthFactory, + ) + require.NoError(err) + + timeoutCtx, timeoutCtxFnc := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second)) + defer timeoutCtxFnc() + require.NoError(tn.ConfirmTxs(timeoutCtx, []*chain.Transaction{tx})) + + sourceBalance, err = client.GetBalance(ctx, sourceAuthFactory.Address()) + require.NoError(err) + require.True(uint64(1000000) > sourceBalance) + targetBalance, err = client.GetBalance(ctx, targetAuthFactory.Address()) + require.NoError(err) + require.Equal(uint64(1000001), targetBalance) + }, 1000000, 1000000) diff --git a/examples/morpheusvm/tests/workload/genesis.go b/examples/morpheusvm/tests/workload/genesis.go index cd41c6b487..1426f715ec 100644 --- a/examples/morpheusvm/tests/workload/genesis.go +++ b/examples/morpheusvm/tests/workload/genesis.go @@ -32,7 +32,7 @@ var ed25519HexKeys = []string{ "8a7be2e0c9a2d09ac2861c34326d6fe5a461d920ba9c2b345ae28e603d517df148735063f8d5d8ba79ea4668358943e5c80bc09e9b2b9a15b5b15db6c1862e88", //nolint:lll } -func newGenesis(authFactories []chain.AuthFactory, minBlockGap time.Duration) *genesis.DefaultGenesis { +func newGenesis(authFactories []chain.AuthFactory, testsLocalAllocations []*genesis.CustomAllocation, minBlockGap time.Duration) *genesis.DefaultGenesis { // allocate the initial balance to the addresses customAllocs := make([]*genesis.CustomAllocation, 0, len(authFactories)) for _, authFactory := range authFactories { @@ -41,6 +41,7 @@ func newGenesis(authFactories []chain.AuthFactory, minBlockGap time.Duration) *g Balance: InitialBalance, }) } + customAllocs = append(customAllocs, testsLocalAllocations...) genesis := genesis.NewDefaultGenesis(customAllocs) @@ -70,9 +71,9 @@ func newDefaultAuthFactories() []chain.AuthFactory { return authFactories } -func NewTestNetworkConfig(minBlockGap time.Duration) (workload.DefaultTestNetworkConfiguration, error) { +func NewTestNetworkConfig(minBlockGap time.Duration, customAllocs []*genesis.CustomAllocation) (workload.DefaultTestNetworkConfiguration, error) { keys := newDefaultAuthFactories() - genesis := newGenesis(keys, minBlockGap) + genesis := newGenesis(keys, customAllocs, minBlockGap) genesisBytes, err := json.Marshal(genesis) if err != nil { return workload.DefaultTestNetworkConfiguration{}, err diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index f22b8361ec..2211d8da4e 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -248,7 +248,7 @@ var _ = ginkgo.Describe("[Custom VM Tests]", ginkgo.Serial, func() { for _, test := range testRegistry.List() { ginkgo.It(test.Name, func() { testNetwork := NewNetwork(tc) - test.Fnc(ginkgo.GinkgoT(), testNetwork) + test.Fnc(ginkgo.GinkgoT(), testNetwork, test.AuthFactories) }) } } diff --git a/tests/integration/integration.go b/tests/integration/integration.go index 8c1fffcf97..3ec1ff56ef 100644 --- a/tests/integration/integration.go +++ b/tests/integration/integration.go @@ -624,7 +624,7 @@ var _ = ginkgo.Describe("[Tx Processing]", ginkgo.Serial, func() { for _, test := range testRegistry.List() { ginkgo.It(fmt.Sprintf("Custom VM Test '%s'", test.Name), func() { require.NoError(testNetwork.SynchronizeNetwork(context.Background())) - test.Fnc(ginkgo.GinkgoT(), testNetwork) + test.Fnc(ginkgo.GinkgoT(), testNetwork, test.AuthFactories) }) } } diff --git a/tests/registry/registry.go b/tests/registry/registry.go index 55c6e9cae2..b555d6c151 100644 --- a/tests/registry/registry.go +++ b/tests/registry/registry.go @@ -5,38 +5,65 @@ package registry import ( "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/require" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/genesis" "github.com/ava-labs/hypersdk/tests/workload" ) -type TestFunc func(t ginkgo.FullGinkgoTInterface, tn workload.TestNetwork) +type TestFunc func(t ginkgo.FullGinkgoTInterface, tn workload.TestNetwork, authFactories []chain.AuthFactory) type namedTest struct { - Fnc TestFunc - Name string + Fnc TestFunc + Name string + AuthFactories []chain.AuthFactory + requestedBalances []uint64 } type Registry struct { - tests []namedTest + tests []*namedTest } -func (r *Registry) Add(name string, f TestFunc) { - r.tests = append(r.tests, namedTest{Fnc: f, Name: name}) +func (r *Registry) Add(name string, f TestFunc, requestedBalances ...uint64) { + r.tests = append(r.tests, &namedTest{Fnc: f, Name: name, requestedBalances: requestedBalances}) } -func (r *Registry) List() []namedTest { +func (r *Registry) List() []*namedTest { if r == nil { - return []namedTest{} + return []*namedTest{} } return r.tests } +func (r *Registry) GenerateCustomAllocations(generateAuthFactory func() (chain.AuthFactory, error)) ([]*genesis.CustomAllocation, error) { + requestedAllocations := make([]*genesis.CustomAllocation, 0) + for index, test := range r.tests { + for _, requestedBalance := range test.requestedBalances { + authFactory, err := generateAuthFactory() + if err != nil { + return nil, err + } + test.AuthFactories = append(test.AuthFactories, authFactory) + requestedAllocations = append( + requestedAllocations, + &genesis.CustomAllocation{ + Address: authFactory.Address(), + Balance: requestedBalance, + }, + ) + } + r.tests[index] = test + } + return requestedAllocations, nil +} + // we need to pre-register all the test registries that are created externally in order to comply with the ginko execution order. // i.e. the global `var _ = ginkgo.Describe` used in the integration/e2e tests need to have this field populated before the iteration // over the top level nodes. var testRegistries = map[*Registry]bool{} -func Register(registry *Registry, name string, f TestFunc) bool { - registry.Add(name, f) +func Register(registry *Registry, name string, f TestFunc, requestedBalances ...uint64) bool { + registry.Add(name, withRequiredPrefundedAuthFactories(f, len(requestedBalances)), requestedBalances...) testRegistries[registry] = true return true } @@ -44,3 +71,11 @@ func Register(registry *Registry, name string, f TestFunc) bool { func GetTestsRegistries() map[*Registry]bool { return testRegistries } + +// withRequiredPrefundedAuthFactories wraps the TestFunc in a new TestFunc adding length validation over the provided authFactories +func withRequiredPrefundedAuthFactories(f TestFunc, requiredLength int) TestFunc { + return func(t ginkgo.FullGinkgoTInterface, tn workload.TestNetwork, authFactories []chain.AuthFactory) { + require.Len(t, authFactories, requiredLength, "required pre-funded authFactories have not been initialized") + f(t, tn, authFactories) + } +}