Skip to content

Commit

Permalink
go/oasis-test-runner/scenario/e2e/runtime: Add kv test client
Browse files Browse the repository at this point in the history
  • Loading branch information
Yawning committed Jun 24, 2021
1 parent 8dfc641 commit 58d719f
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 50 deletions.
5 changes: 5 additions & 0 deletions go/oasis-test-runner/oasis/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ func (net *Network) ClientController() *Controller {
return net.clientController
}

// SetClientController sets the client controller.
func (net *Network) SetClientController(ctrl *Controller) {
net.clientController = ctrl
}

// NumRegisterNodes returns the number of all nodes that need to register.
func (net *Network) NumRegisterNodes() int {
return len(net.validators) +
Expand Down
2 changes: 1 addition & 1 deletion go/oasis-test-runner/scenario/e2e/runtime/client_expire.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (sc *clientExpireImpl) Run(childEnv *env.Env) error {
return fmt.Errorf("SubmitTxNoWait expected no error, got: %b", err)
}

err = sc.submitKeyValueRuntimeInsertTx(ctx, runtimeID, "hello", "test", 0)
_, err = sc.submitKeyValueRuntimeInsertTx(ctx, runtimeID, "hello", "test", 0)
if !errors.Is(err, api.ErrTransactionExpired) {
return fmt.Errorf("expected error: %v, got: %v", api.ErrTransactionExpired, err)
}
Expand Down
2 changes: 1 addition & 1 deletion go/oasis-test-runner/scenario/e2e/runtime/gas_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (sc *gasFeesRuntimesImpl) Run(childEnv *env.Env) error {

// Submit a runtime transaction to check whether transaction processing works.
sc.Logger.Info("submitting transaction to runtime")
if err := sc.submitKeyValueRuntimeInsertTx(ctx, runtimeID, "hello", "non-free world", 0); err != nil {
if _, err := sc.submitKeyValueRuntimeInsertTx(ctx, runtimeID, "hello", "non-free world", 0); err != nil {
return err
}

Expand Down
4 changes: 4 additions & 0 deletions go/oasis-test-runner/scenario/e2e/runtime/late_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ func (sc *lateStartImpl) Run(childEnv *env.Env) error {
return fmt.Errorf("expected error: %v, got: %v", api.ErrNotSynced, err)
}

// Set the ClientController to the late-started one, so that the test
// client works.
sc.Net.SetClientController(ctrl)

sc.Logger.Info("Starting the basic test client")
// Explicitly wait for the client to sync, before starting the client.
if err = sc.waitForClientSync(ctx); err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (sc *multipleRuntimesImpl) Run(childEnv *env.Env) error {
"runtime_id", rt.ID,
)

if err := sc.submitKeyValueRuntimeInsertTx(ctx, rt.ID, "hello", fmt.Sprintf("world at iteration %d from %s", i, rt.ID), 0); err != nil {
if _, err := sc.submitKeyValueRuntimeInsertTx(ctx, rt.ID, "hello", fmt.Sprintf("world at iteration %d from %s", i, rt.ID), 0); err != nil {
return err
}

Expand Down
263 changes: 252 additions & 11 deletions go/oasis-test-runner/scenario/e2e/runtime/runtime_client_kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@ package runtime

import (
"context"
"crypto"
"fmt"
"math/rand"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/drbg"
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
"github.com/oasisprotocol/oasis-core/go/common/crypto/mathrand"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)

var BasicKVTestClient = NewBinaryTestClient(
"simple-keyvalue-client",
nil,
)
var BasicKVTestClient = NewKeyValueTestClient()

// KeyValueTestClient is a client that exercises the simple key-value
// test runtime.
type KeyValueTestClient struct {
sc *runtimeImpl

seed string
repeat bool

ctx context.Context
cancelFn context.CancelFunc
errCh chan error
}

func (cli *KeyValueTestClient) Init(scenario *runtimeImpl) error {
Expand All @@ -26,28 +36,213 @@ func (cli *KeyValueTestClient) Init(scenario *runtimeImpl) error {
}

func (cli *KeyValueTestClient) Start(ctx context.Context, childEnv *env.Env) error {
panic("not implemented")
cli.ctx = ctx

subCtx, cancelFn := context.WithCancel(ctx)
cli.errCh = make(chan error)
cli.cancelFn = cancelFn

go func() {
cli.errCh <- cli.workload(subCtx)
}()

return nil
}

func (cli *KeyValueTestClient) Wait() error {
panic("not implemented")
var err error

// Wait for the network to fail, the context to be canceled, or the
// workload to terminate on it's own.
select {
case err = <-cli.sc.Net.Errors():
cli.cancelFn()
case <-cli.ctx.Done():
err = cli.ctx.Err()
cli.cancelFn()
case err = <-cli.errCh:
}

return err
}

func (cli *KeyValueTestClient) Kill() error {
// Kill the workload.
cli.cancelFn()

// Wait for the network to fail, or the workload to terminate on it's own.
select {
case err := <-cli.sc.Net.Errors():
return err
case err := <-cli.errCh:
return err
}
}

func (cli *KeyValueTestClient) Clone() TestClient {
panic("not implemented")
ncli := NewKeyValueTestClient().WithSeed(cli.seed)
if cli.repeat {
ncli = ncli.WithRepeat()
}
return ncli
}

func (cli *KeyValueTestClient) WithSeed(seed string) *KeyValueTestClient {
cli.seed = seed
return cli
}

func (cli *KeyValueTestClient) WithRepeat() *KeyValueTestClient {
cli.repeat = true
return cli
}

func (cli *KeyValueTestClient) workload(ctx context.Context) error {
// Initialize the nonce DRBG.
rng, err := drbgFromSeed(
[]byte("oasis-core/oasis-test-runner/e2e/runtime/kv"),
[]byte(cli.seed),
)
if err != nil {
return err
}

cli.sc.Logger.Info("initializing simple key/value runtime!")

const (
myKey = "hello_key"

myLongKey = "I laud Agni the priest, the divine minister of sacrifice, who invokes the gods, and is the most rich in gems."
myLongValue = "May Agni, the invoker, the sage, the true, the most renowned, a god, come hither with the gods!"
)

// Check whether Runtime ID is also set remotely.
rtID, err := cli.sc.submitKeyValueRuntimeGetRuntimeIDTx(
ctx,
runtimeID,
rng.Uint64(),
)
if err != nil {
return fmt.Errorf("failed to query remote runtime ID: %w", err)
}
if !rtID.Equal(&runtimeID) {
return fmt.Errorf("remote runtime ID mismatch (Got: %v, Expected: %v)", rtID, runtimeID)
}

for iter := 0; ; iter++ {
var resp string

cli.sc.Logger.Info("beginning client loop",
"iteration", iter,
)

// Test simple [set,get] calls.
myValue := fmt.Sprintf("hello_value_from_%s:%d", runtimeID, iter)
cli.sc.Logger.Info("storing k/v pair to database",
"key", myKey,
"value", myValue,
)
if resp, err = cli.sc.submitKeyValueRuntimeInsertTx(
ctx,
runtimeID,
myKey,
myValue,
rng.Uint64(),
); err != nil {
return fmt.Errorf("failed to insert k/v pair: %w", err)
}
if iter == 0 && resp != "" {
return fmt.Errorf("k/v pair already exists: '%v'", resp)
}

cli.sc.Logger.Info("checking if key exists and has the correct value")
resp, err = cli.sc.submitKeyValueRuntimeGetTx(
ctx,
runtimeID,
myKey,
rng.Uint64(),
)
if err != nil {
return err
}
if resp != myValue {
return fmt.Errorf("key does not have expected value (Got: '%v', Expected: '%v')", resp, myValue)
}

// Test [set, get] long key calls
cli.sc.Logger.Info("storing long k/v pair to database",
"key", myLongKey,
"value", myLongValue,
)
if resp, err = cli.sc.submitKeyValueRuntimeInsertTx(
ctx,
runtimeID,
myLongKey,
myLongValue,
rng.Uint64(),
); err != nil {
return fmt.Errorf("failed to insert k/v pair: %w", err)
}
if iter == 0 && resp != "" {
return fmt.Errorf("k/v pair already exists: '%v'", resp)
}

if err = cli.sc.submitConsensusXferTx(
ctx,
runtimeID,
staking.Transfer{},
rng.Uint64(),
); err != nil {
return fmt.Errorf("failed to submit consensus transfer: %w", err)
}

cli.sc.Logger.Info("checking if long key exists and has the correct value")
if resp, err = cli.sc.submitKeyValueRuntimeGetTx(
ctx,
runtimeID,
myLongKey,
rng.Uint64(),
); err != nil {
return err
}
if resp != myLongValue {
return fmt.Errorf("key does not have expected value (Got: '%v', Expected: '%v')", resp, myLongValue)
}

if !cli.repeat {
break
}
}

cli.sc.Logger.Info("testing consensus queries")
if _, err = cli.sc.submitRuntimeTx(ctx, runtimeID, "consensus_accounts", struct {
Nonce uint64 `json:"nonce"`
}{
Nonce: rng.Uint64(),
}); err != nil {
return fmt.Errorf("failed to submit consensus_accounts query: %w", err)
}
// TODO: The old test printed out the accounts and delegations, but
// it's not like it validated them or anything.

cli.sc.Logger.Info("simple k/v client finished")

return nil
}

func NewKeyValueTestClient() *KeyValueTestClient {
panic("not implemented")
return &KeyValueTestClient{
seed: "seeeeeeeeeeeeeeeeeeeeeeeeeeeeeed",
}
}

func (sc *runtimeImpl) submitKeyValueRuntimeInsertTx(
ctx context.Context,
id common.Namespace,
key, value string,
nonce uint64,
) error {
_, err := sc.submitRuntimeTx(ctx, id, "insert", struct {
) (string, error) {
rawRsp, err := sc.submitRuntimeTx(ctx, id, "insert", struct {
Key string `json:"key"`
Value string `json:"value"`
Nonce uint64 `json:"nonce"`
Expand All @@ -56,7 +251,16 @@ func (sc *runtimeImpl) submitKeyValueRuntimeInsertTx(
Value: value,
Nonce: nonce,
})
return err
if err != nil {
return "", fmt.Errorf("failed to submit insert tx to runtime: %w", err)
}

var rsp string
if err = cbor.Unmarshal(rawRsp, &rsp); err != nil {
return "", fmt.Errorf("failed to unmarshal response from runtime: %w", err)
}

return rsp, nil
}

func (sc *runtimeImpl) submitKeyValueRuntimeGetTx(
Expand All @@ -83,3 +287,40 @@ func (sc *runtimeImpl) submitKeyValueRuntimeGetTx(

return rsp, nil
}

func (sc *runtimeImpl) submitKeyValueRuntimeGetRuntimeIDTx(
ctx context.Context,
id common.Namespace,
nonce uint64,
) (common.Namespace, error) {
var rsp common.Namespace
rawRsp, err := sc.submitRuntimeTx(ctx, runtimeID, "get_runtime_id", struct {
Nonce uint64 `json:"nonce"`
}{
Nonce: nonce,
})
if err != nil {
return rsp, fmt.Errorf("failed to submit get_runtime_id tx to runtime: %w", err)
}

if err = cbor.Unmarshal(rawRsp, &rsp); err != nil {
return rsp, fmt.Errorf("failed to unmarshal response from runtime: %w", err)
}

return rsp, nil
}

func drbgFromSeed(domainSep, seed []byte) (rand.Source64, error) {
h := hash.NewFromBytes(seed)
drbg, err := drbg.New(
crypto.SHA512_256,
h[:],
nil,
domainSep,
)
if err != nil {
return nil, fmt.Errorf("failed to initialize drbg: %w", err)
}

return mathrand.New(drbg), nil
}
Loading

0 comments on commit 58d719f

Please sign in to comment.