Skip to content

Commit

Permalink
[Miner] feat: add supplier client (#42)
Browse files Browse the repository at this point in the history
* chore: add `TxClient` interface

* chore: add option support to `ReplayObservable`

* feat: add `txClient` implementation

* test: `txClient`

* test: tx client integration

* chore: s/tx/transaction/g

* chore: update pkg README.md template

* wip: client pkg README

* docs: fix client pkg godoc comment

* refactor: consolidate keyring errors & helpers

* refactor: keyring test helpers

* fix: flakey test

* chore: dial back godoc comments 😅

* chore: add `SupplierClient` interface

* feat: add supplier client implementation

* test:  supplier test helpers

* test: supplier client tests

* test: supplier client integration test

* chore: update go.mod

* trigger CI

* chore: revise (and move to godoc.go) `testblock` & `testeventsquery` pkg godoc comment

* chore: update go.mod

* chore: refactor & condense godoc comments

* chore: fix import paths post-update

* chore: add godoc comment
  • Loading branch information
bryanchriswhite authored Nov 7, 2023
1 parent aecdf18 commit 5b3fd95
Show file tree
Hide file tree
Showing 11 changed files with 557 additions and 2 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
github.com/pokt-network/smt v0.7.1
github.com/regen-network/gocuke v0.6.2
github.com/spf13/cast v1.5.1
github.com/spf13/cobra v1.7.0
Expand Down Expand Up @@ -86,6 +87,7 @@ require (
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/docker/go-units v0.5.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
Expand Down Expand Up @@ -1582,6 +1584,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pokt-network/smt v0.7.1 h1:WHcZeMLe+9U1/kCAhdbssdyTYzYxxb74sf8MCvG34M8=
github.com/pokt-network/smt v0.7.1/go.mod h1:K7BLEOWoZGZmY5USQuYvTkZ3qXjE6m39BMufBvVo3U8=
github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=
github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
github.com/polyfloyd/go-errorlint v1.0.0/go.mod h1:KZy4xxPJyy88/gldCe5OdW6OQRtNO3EZE7hXzmnebgA=
Expand Down
2 changes: 2 additions & 0 deletions internal/testclient/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/stretchr/testify/require"
)

// NewKey creates a new Secp256k1 key and mnemonic for the given name within
// the provided keyring.
func NewKey(
t *testing.T,
name string,
Expand Down
37 changes: 37 additions & 0 deletions internal/testclient/testsupplier/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package testsupplier

import (
"testing"

"cosmossdk.io/depinject"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/internal/testclient/testtx"
"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/supplier"
"github.com/pokt-network/poktroll/pkg/client/tx"
)

// NewLocalnetClient creates and returns a new supplier client that connects to
// the localnet sequencer.
func NewLocalnetClient(
t *testing.T,
signingKeyName string,
) client.SupplierClient {
t.Helper()

txClientOpt := tx.WithSigningKeyName(signingKeyName)
supplierClientOpt := supplier.WithSigningKeyName(signingKeyName)

txCtx := testtx.NewLocalnetContext(t)
txClient := testtx.NewLocalnetClient(t, txClientOpt)

deps := depinject.Supply(
txCtx,
txClient,
)

supplierClient, err := supplier.NewSupplierClient(deps, supplierClientOpt)
require.NoError(t, err)
return supplierClient
}
93 changes: 93 additions & 0 deletions internal/testclient/testtx/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package testtx

import (
"context"
"testing"
"time"

"cosmossdk.io/depinject"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/internal/mocks/mockclient"
"github.com/pokt-network/poktroll/internal/testclient/testblock"
"github.com/pokt-network/poktroll/internal/testclient/testeventsquery"
"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/tx"
"github.com/pokt-network/poktroll/pkg/either"
)

type signAndBroadcastFn func(context.Context, cosmostypes.Msg) either.AsyncError

// TODO_CONSIDERATION: functions like these (NewLocalnetXXX) could probably accept
// and return depinject.Config arguments to support shared dependencies.

// NewLocalnetClient creates and returns a new client for use with the localnet
// sequencer.
func NewLocalnetClient(t *testing.T, opts ...client.TxClientOption) client.TxClient {
t.Helper()

ctx := context.Background()
txCtx := NewLocalnetContext(t)
eventsQueryClient := testeventsquery.NewLocalnetClient(t)
blockClient := testblock.NewLocalnetClient(ctx, t)

deps := depinject.Supply(
txCtx,
eventsQueryClient,
blockClient,
)

txClient, err := tx.NewTxClient(ctx, deps, opts...)
require.NoError(t, err)

return txClient
}

// NewOneTimeDelayedSignAndBroadcastTxClient constructs a mock TxClient with the
// expectation to perform a SignAndBroadcast operation with a specified delay.
func NewOneTimeDelayedSignAndBroadcastTxClient(
t *testing.T,
delay time.Duration,
) *mockclient.MockTxClient {
t.Helper()

signAndBroadcast := newSignAndBroadcastSucceedsDelayed(delay)
return NewOneTimeSignAndBroadcastTxClient(t, signAndBroadcast)
}

// NewOneTimeSignAndBroadcastTxClient constructs a mock TxClient with the
// expectation to perform a SignAndBroadcast operation, which will call and receive
// the return from the given signAndBroadcast function.
func NewOneTimeSignAndBroadcastTxClient(
t *testing.T,
signAndBroadcast signAndBroadcastFn,
) *mockclient.MockTxClient {
t.Helper()

var ctrl = gomock.NewController(t)

txClient := mockclient.NewMockTxClient(ctrl)
txClient.EXPECT().SignAndBroadcast(
gomock.AssignableToTypeOf(context.Background()),
gomock.Any(),
).DoAndReturn(signAndBroadcast).Times(1)

return txClient
}

// newSignAndBroadcastSucceedsDelayed returns a signAndBroadcastFn that succeeds
// after the given delay.
func newSignAndBroadcastSucceedsDelayed(delay time.Duration) signAndBroadcastFn {
return func(ctx context.Context, msg cosmostypes.Msg) either.AsyncError {
errCh := make(chan error)

go func() {
time.Sleep(delay)
close(errCh)
}()

return either.AsyncErr(errCh)
}
}
22 changes: 22 additions & 0 deletions internal/testclient/testtx/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ import (
"github.com/pokt-network/poktroll/pkg/client/tx"
)

// NewLocalnetContext creates and returns a new transaction context configured
// for use with the localnet sequencer.
func NewLocalnetContext(t *testing.T) client.TxContext {
t.Helper()

flagSet := testclient.NewLocalnetFlagSet(t)
clientCtx := testclient.NewLocalnetClientCtx(t, flagSet)
txFactory, err := cosmostx.NewFactoryCLI(*clientCtx, flagSet)
require.NoError(t, err)
require.NotEmpty(t, txFactory)

deps := depinject.Supply(
*clientCtx,
txFactory,
)

txCtx, err := tx.NewTxContext(deps)
require.NoError(t, err)

return txCtx
}

// TODO_IMPROVE: these mock constructor helpers could include parameters for the
// "times" (e.g. exact, min, max) values which are passed to their respective
// gomock.EXPECT() method calls (i.e. Times(), MinTimes(), MaxTimes()).
Expand Down
32 changes: 30 additions & 2 deletions pkg/client/interface.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//go:generate mockgen -destination=../../internal/mocks/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient
//go:generate mockgen -destination=../../internal/mocks/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient
//go:generate mockgen -destination=../../internal/mocks/mockclient/tx_client_mock.go -package=mockclient . TxContext
//go:generate mockgen -destination=../../internal/mocks/mockclient/tx_client_mock.go -package=mockclient . TxContext,TxClient
//go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_tx_builder_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client TxBuilder
//go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_keyring_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/crypto/keyring Keyring
//go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_client_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client AccountRetriever
Expand All @@ -14,11 +14,36 @@ import (
cosmosclient "github.com/cosmos/cosmos-sdk/client"
cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
"github.com/pokt-network/smt"

"github.com/pokt-network/poktroll/pkg/either"
"github.com/pokt-network/poktroll/pkg/observable"
sessiontypes "github.com/pokt-network/poktroll/x/session/types"
)

// SupplierClient is an interface for sufficient for a supplier operator to be
// able to construct blockchain transactions from pocket protocol-specific messages
// related to its role.
type SupplierClient interface {
// CreateClaim sends a claim message which creates an on-chain commitment by
// calling supplier to the given smt.SparseMerkleSumTree root hash of the given
// session's mined relays.
CreateClaim(
ctx context.Context,
sessionHeader sessiontypes.SessionHeader,
rootHash []byte,
) error
// SubmitProof sends a proof message which contains the
// smt.SparseMerkleClosestProof, corresponding to some previously created claim
// for the same session. The proof is validated on-chain as part of the pocket
// protocol.
SubmitProof(
ctx context.Context,
sessionHeader sessiontypes.SessionHeader,
proof *smt.SparseMerkleClosestProof,
) error
}

// TxClient provides a synchronous interface initiating and waiting for transactions
// derived from cosmos-sdk messages, in a cosmos-sdk based blockchain network.
type TxClient interface {
Expand Down Expand Up @@ -79,7 +104,7 @@ type BlocksObservable observable.ReplayObservable[Block]
// BlockClient is an interface which provides notifications about newly committed
// blocks as well as direct access to the latest block via some blockchain API.
type BlockClient interface {
// Blocks returns an observable which emits newly committed blocks.
// CommittedBlocksSequence returns an observable which emits newly committed blocks.
CommittedBlocksSequence(context.Context) BlocksObservable
// LatestBlock returns the latest block that has been committed.
LatestBlock(context.Context) Block
Expand Down Expand Up @@ -148,3 +173,6 @@ type EventsQueryClientOption func(EventsQueryClient)

// TxClientOption defines a function type that modifies the TxClient.
type TxClientOption func(TxClient)

// SupplierClientOption defines a function type that modifies the SupplierClient.
type SupplierClientOption func(SupplierClient)
127 changes: 127 additions & 0 deletions pkg/client/supplier/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package supplier

import (
"context"

"cosmossdk.io/depinject"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
"github.com/pokt-network/smt"

"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/keyring"
sessiontypes "github.com/pokt-network/poktroll/x/session/types"
suppliertypes "github.com/pokt-network/poktroll/x/supplier/types"
)

var _ client.SupplierClient = (*supplierClient)(nil)

// supplierClient
type supplierClient struct {
signingKeyName string
signingKeyAddr cosmostypes.AccAddress

txClient client.TxClient
txCtx client.TxContext
}

// NewSupplierClient constructs a new SupplierClient with the given dependencies
// and options. If a signingKeyName is not configured, an error will be returned.
//
// Required dependencies:
// - client.TxClient
// - client.TxContext
//
// Available options:
// - WithSigningKeyName
func NewSupplierClient(
deps depinject.Config,
opts ...client.SupplierClientOption,
) (*supplierClient, error) {
sClient := &supplierClient{}

if err := depinject.Inject(
deps,
&sClient.txClient,
&sClient.txCtx,
); err != nil {
return nil, err
}

for _, opt := range opts {
opt(sClient)
}

if err := sClient.validateConfigAndSetDefaults(); err != nil {
return nil, err
}

return sClient, nil
}

// SubmitProof constructs a submit proof message then signs and broadcasts it
// to the network via #txClient. It blocks until the transaction is included in
// a block or times out.
func (sClient *supplierClient) SubmitProof(
ctx context.Context,
sessionHeader sessiontypes.SessionHeader,
proof *smt.SparseMerkleClosestProof,
) error {
proofBz, err := proof.Marshal()
if err != nil {
return err
}

msg := &suppliertypes.MsgSubmitProof{
SupplierAddress: sClient.signingKeyAddr.String(),
SessionHeader: &sessionHeader,
Proof: proofBz,
}
eitherErr := sClient.txClient.SignAndBroadcast(ctx, msg)
err, errCh := eitherErr.SyncOrAsyncError()
if err != nil {
return err
}

return <-errCh
}

// CreateClaim constructs a creates claim message then signs and broadcasts it
// to the network via #txClient. It blocks until the transaction is included in
// a block or times out.
func (sClient *supplierClient) CreateClaim(
ctx context.Context,
sessionHeader sessiontypes.SessionHeader,
rootHash []byte,
) error {
msg := &suppliertypes.MsgCreateClaim{
SupplierAddress: sClient.signingKeyAddr.String(),
SessionHeader: &sessionHeader,
RootHash: rootHash,
}
eitherErr := sClient.txClient.SignAndBroadcast(ctx, msg)
err, errCh := eitherErr.SyncOrAsyncError()
if err != nil {
return err
}

err = <-errCh
return err
}

// validateConfigAndSetDefaults attempts to get the address from the keyring
// corresponding to the key whose name matches the configured signingKeyName.
// If signingKeyName is empty or the keyring does not contain the corresponding
// key, an error is returned.
func (sClient *supplierClient) validateConfigAndSetDefaults() error {
signingAddr, err := keyring.KeyNameToAddr(
sClient.signingKeyName,
sClient.txCtx.GetKeyring(),
)
if err != nil {
return err
}

sClient.signingKeyAddr = signingAddr

return nil
}
Loading

0 comments on commit 5b3fd95

Please sign in to comment.