From 6f8f7eee639c42b534acc2ee0dcdf3845ed1f76f Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Fri, 31 Jul 2020 10:29:21 +0200 Subject: [PATCH] go/consensus: Add SubmitTxNoWait method The new method allows submitting a transaction without waiting for it to be included in a block. --- .changelog/3152.feature.md | 4 ++++ go/consensus/api/api.go | 3 ++- go/consensus/api/base.go | 5 ++++ go/consensus/api/grpc.go | 34 +++++++++++++++++++++++++++ go/consensus/api/light.go | 5 ++++ go/consensus/tendermint/full/light.go | 7 ++++++ go/consensus/tests/tester.go | 10 ++++++++ 7 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 .changelog/3152.feature.md diff --git a/.changelog/3152.feature.md b/.changelog/3152.feature.md new file mode 100644 index 00000000000..2a72da596c8 --- /dev/null +++ b/.changelog/3152.feature.md @@ -0,0 +1,4 @@ +go/consensus: Add SubmitTxNoWait method + +The new method allows submitting a transaction without waiting for it to be +included in a block. diff --git a/go/consensus/api/api.go b/go/consensus/api/api.go index f32af8b0ab6..d045b754aa2 100644 --- a/go/consensus/api/api.go +++ b/go/consensus/api/api.go @@ -87,7 +87,8 @@ type ClientBackend interface { LightClientBackend TransactionAuthHandler - // SubmitTx submits a signed consensus transaction. + // SubmitTx submits a signed consensus transaction and waits for the transaction to be included + // in a block. Use SubmitTxNoWait if you only need to broadcast the transaction. SubmitTx(ctx context.Context, tx *transaction.SignedTransaction) error // StateToGenesis returns the genesis state at the specified block height. diff --git a/go/consensus/api/base.go b/go/consensus/api/base.go index 118ac8e3907..ec5c60a05ce 100644 --- a/go/consensus/api/base.go +++ b/go/consensus/api/base.go @@ -198,3 +198,8 @@ func (b *BaseBackend) GetParameters(ctx context.Context, height int64) (*Paramet func (b *BaseBackend) State() syncer.ReadSyncer { panic(ErrUnsupported) } + +// Implements Backend. +func (b *BaseBackend) SubmitTxNoWait(ctx context.Context, tx *transaction.SignedTransaction) error { + panic(ErrUnsupported) +} diff --git a/go/consensus/api/grpc.go b/go/consensus/api/grpc.go index 789fa35522d..452a3038498 100644 --- a/go/consensus/api/grpc.go +++ b/go/consensus/api/grpc.go @@ -57,6 +57,8 @@ var ( methodStateSyncGetPrefixes = lightServiceName.NewMethod("StateSyncGetPrefixes", syncer.GetPrefixesRequest{}) // methodStateSyncIterate is the StateSyncIterate method. methodStateSyncIterate = lightServiceName.NewMethod("StateSyncIterate", syncer.IterateRequest{}) + // methodSubmitTxNoWait is the SubmitTxNoWait method. + methodSubmitTxNoWait = lightServiceName.NewMethod("SubmitTxNoWait", transaction.SignedTransaction{}) // serviceDesc is the gRPC service descriptor. serviceDesc = grpc.ServiceDesc{ @@ -146,6 +148,10 @@ var ( MethodName: methodStateSyncIterate.ShortName(), Handler: handlerStateSyncIterate, }, + { + MethodName: methodSubmitTxNoWait.ShortName(), + Handler: handlerSubmitTxNoWait, + }, }, } ) @@ -561,6 +567,29 @@ func handlerStateSyncIterate( // nolint: golint return interceptor(ctx, rq, info, handler) } +func handlerSubmitTxNoWait( // nolint: golint + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + rq := new(transaction.SignedTransaction) + if err := dec(rq); err != nil { + return nil, err + } + if interceptor == nil { + return nil, srv.(LightClientBackend).SubmitTxNoWait(ctx, rq) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodSubmitTxNoWait.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return nil, srv.(LightClientBackend).SubmitTxNoWait(ctx, req.(*transaction.SignedTransaction)) + } + return interceptor(ctx, rq, info, handler) +} + // RegisterService registers a new client backend service with the given gRPC server. func RegisterService(server *grpc.Server, service ClientBackend) { server.RegisterService(&serviceDesc, service) @@ -639,6 +668,11 @@ func (c *consensusLightClient) State() syncer.ReadSyncer { return &stateReadSync{c} } +// Implements LightClientBackend. +func (c *consensusLightClient) SubmitTxNoWait(ctx context.Context, tx *transaction.SignedTransaction) error { + return c.conn.Invoke(ctx, methodSubmitTxNoWait.FullName(), tx, nil) +} + type consensusClient struct { consensusLightClient diff --git a/go/consensus/api/light.go b/go/consensus/api/light.go index a9533e5642e..a4d379fb1dd 100644 --- a/go/consensus/api/light.go +++ b/go/consensus/api/light.go @@ -3,6 +3,7 @@ package api import ( "context" + "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" "github.com/oasisprotocol/oasis-core/go/storage/mkvs/syncer" ) @@ -21,6 +22,10 @@ type LightClientBackend interface { // and verify it against the trusted local root. State() syncer.ReadSyncer + // SubmitTxNoWait submits a signed consensus transaction, but does not wait for the transaction + // to be included in a block. Use SubmitTx if you need to wait for execution. + SubmitTxNoWait(ctx context.Context, tx *transaction.SignedTransaction) error + // TODO: Move SubmitEvidence etc. from Backend. } diff --git a/go/consensus/tendermint/full/light.go b/go/consensus/tendermint/full/light.go index 6815490925b..5651bba7619 100644 --- a/go/consensus/tendermint/full/light.go +++ b/go/consensus/tendermint/full/light.go @@ -8,7 +8,9 @@ import ( tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" tmstate "github.com/tendermint/tendermint/state" + "github.com/oasisprotocol/oasis-core/go/common/cbor" consensusAPI "github.com/oasisprotocol/oasis-core/go/consensus/api" + "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" "github.com/oasisprotocol/oasis-core/go/storage/mkvs/syncer" ) @@ -79,3 +81,8 @@ func (t *fullService) GetParameters(ctx context.Context, height int64) (*consens func (t *fullService) State() syncer.ReadSyncer { return t.mux.State().Storage() } + +// Implements LightClientBackend. +func (t *fullService) SubmitTxNoWait(ctx context.Context, tx *transaction.SignedTransaction) error { + return t.broadcastTxRaw(cbor.Marshal(tx)) +} diff --git a/go/consensus/tests/tester.go b/go/consensus/tests/tester.go index 00560d332b9..0d0d932df52 100644 --- a/go/consensus/tests/tester.go +++ b/go/consensus/tests/tester.go @@ -119,6 +119,16 @@ func ConsensusImplementationTests(t *testing.T, backend consensus.ClientBackend) require.Equal(params.Height, blk.Height, "returned parameters height should be correct") require.NotNil(params.Meta, "returned parameters should contain metadata") + err = backend.SubmitTxNoWait(ctx, &transaction.SignedTransaction{}) + require.Error(err, "SubmitTxNoWait should fail with invalid transaction") + + testTx := transaction.NewTransaction(0, nil, epochtimemock.MethodSetEpoch, epoch) + testSigner := memorySigner.NewTestSigner("consensus tests tx signer") + testSigTx, err := transaction.Sign(testSigner, testTx) + require.NoError(err, "transaction.Sign") + err = backend.SubmitTxNoWait(ctx, testSigTx) + require.NoError(err, "SubmitTxNoWait") + // We should be able to do remote state queries. Of course the state format is backend-specific // so we simply perform some usual storage operations like fetching random keys and iterating // through everything.