Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: Add client node TEE freshness verification #4922

Merged
merged 6 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/4922.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
runtime: Add client node TEE freshness verification
4 changes: 4 additions & 0 deletions go/consensus/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ type ClientBackend interface {
// in a block. Use SubmitTxNoWait if you only need to broadcast the transaction.
SubmitTx(ctx context.Context, tx *transaction.SignedTransaction) error

// SubmitTxWithProof submits a signed consensus transaction, waits for the transaction to be
// included in a block and returns a proof of inclusion.
SubmitTxWithProof(ctx context.Context, tx *transaction.SignedTransaction) (*transaction.Proof, error)

// StateToGenesis returns the genesis state at the specified block height.
StateToGenesis(ctx context.Context, height int64) (*genesis.Document, error)

Expand Down
37 changes: 37 additions & 0 deletions go/consensus/api/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ var (

// methodSubmitTx is the SubmitTx method.
methodSubmitTx = serviceName.NewMethod("SubmitTx", transaction.SignedTransaction{})
// methodSubmitTxWithProof is the SubmitTxWithProof method.
methodSubmitTxWithProof = serviceName.NewMethod("SubmitTxWithProof", transaction.SignedTransaction{})
// methodStateToGenesis is the StateToGenesis method.
methodStateToGenesis = serviceName.NewMethod("StateToGenesis", int64(0))
// methodEstimateGas is the EstimateGas method.
Expand Down Expand Up @@ -78,6 +80,10 @@ var (
MethodName: methodSubmitTx.ShortName(),
Handler: handlerSubmitTx,
},
{
MethodName: methodSubmitTxWithProof.ShortName(),
Handler: handlerSubmitTxWithProof,
},
{
MethodName: methodStateToGenesis.ShortName(),
Handler: handlerStateToGenesis,
Expand Down Expand Up @@ -196,6 +202,29 @@ func handlerSubmitTx(
return interceptor(ctx, rq, info, handler)
}

func handlerSubmitTxWithProof(
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 srv.(ClientBackend).SubmitTxWithProof(ctx, rq)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: methodSubmitTxWithProof.FullName(),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ClientBackend).SubmitTxWithProof(ctx, req.(*transaction.SignedTransaction))
}
return interceptor(ctx, rq, info, handler)
}

func handlerStateToGenesis(
srv interface{},
ctx context.Context,
Expand Down Expand Up @@ -739,6 +768,14 @@ func (c *consensusClient) SubmitTx(ctx context.Context, tx *transaction.SignedTr
return c.conn.Invoke(ctx, methodSubmitTx.FullName(), tx, nil)
}

func (c *consensusClient) SubmitTxWithProof(ctx context.Context, tx *transaction.SignedTransaction) (*transaction.Proof, error) {
var proof transaction.Proof
if err := c.conn.Invoke(ctx, methodSubmitTxWithProof.FullName(), tx, &proof); err != nil {
return nil, err
}
return &proof, nil
}

func (c *consensusClient) StateToGenesis(ctx context.Context, height int64) (*genesis.Document, error) {
var rsp genesis.Document
if err := c.conn.Invoke(ctx, methodStateToGenesis.FullName(), height, &rsp); err != nil {
Expand Down
83 changes: 68 additions & 15 deletions go/consensus/api/submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ type SubmissionManager interface {
//
// It also automatically handles retries in case the nonce was incorrectly estimated.
SignAndSubmitTx(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) error

// SignAndSubmitTxWithProof populates the nonce and fee fields in the transaction, signs
// the transaction with the passed signer, submits it to consensus backend and creates
// a proof of inclusion.
//
// It also automatically handles retries in case the nonce was incorrectly estimated.
SignAndSubmitTxWithProof(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) (*transaction.SignedTransaction, *transaction.Proof, error)
}

type submissionManager struct {
Expand Down Expand Up @@ -124,7 +131,7 @@ func (m *submissionManager) EstimateGasAndSetFee(ctx context.Context, signer sig
return nil
}

func (m *submissionManager) signAndSubmitTx(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) error {
func (m *submissionManager) signAndSubmitTx(ctx context.Context, signer signature.Signer, tx *transaction.Transaction, withProof bool) (*transaction.SignedTransaction, *transaction.Proof, error) {
// Update transaction nonce.
var err error
signerAddr := staking.NewAddress(signer.Public())
Expand All @@ -134,14 +141,14 @@ func (m *submissionManager) signAndSubmitTx(ctx context.Context, signer signatur
if errors.Is(err, ErrNoCommittedBlocks) {
// No committed blocks available, retry submission.
m.logger.Debug("retrying transaction submission due to no committed blocks")
return err
return nil, nil, err
}
return backoff.Permanent(err)
return nil, nil, backoff.Permanent(err)
}

// Estimate the fee.
if err = m.EstimateGasAndSetFee(ctx, signer, tx); err != nil {
return fmt.Errorf("failed to estimate fee: %w", err)
return nil, nil, fmt.Errorf("failed to estimate fee: %w", err)
}

// Sign the transaction.
Expand All @@ -150,39 +157,68 @@ func (m *submissionManager) signAndSubmitTx(ctx context.Context, signer signatur
m.logger.Error("failed to sign transaction",
"err", err,
)
return backoff.Permanent(err)
return nil, nil, backoff.Permanent(err)
}

if err = m.backend.SubmitTx(ctx, sigTx); err != nil {
var proof *transaction.Proof
if withProof {
proof, err = m.backend.SubmitTxWithProof(ctx, sigTx)
} else {
err = m.backend.SubmitTx(ctx, sigTx)
}
if err != nil {
switch {
case errors.Is(err, transaction.ErrUpgradePending):
// Pending upgrade, retry submission.
m.logger.Debug("retrying transaction submission due to pending upgrade")
return err
return nil, nil, err
case errors.Is(err, transaction.ErrInvalidNonce):
// Invalid nonce, retry submission.
m.logger.Debug("retrying transaction submission due to invalid nonce",
"account_address", signerAddr,
"nonce", tx.Nonce,
)
return err
return nil, nil, err
default:
return backoff.Permanent(err)
return nil, nil, backoff.Permanent(err)
}
}

return nil
return sigTx, proof, nil
}

// Implements SubmissionManager.
func (m *submissionManager) SignAndSubmitTx(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) error {
func (m *submissionManager) signAndSubmitTxWithRetry(ctx context.Context, signer signature.Signer, tx *transaction.Transaction, withProof bool) (*transaction.SignedTransaction, *transaction.Proof, error) {
sched := cmnBackoff.NewExponentialBackOff()
sched.MaxInterval = maxSubmissionRetryInterval
sched.MaxElapsedTime = maxSubmissionRetryElapsedTime

return backoff.Retry(func() error {
return m.signAndSubmitTx(ctx, signer, tx)
}, backoff.WithContext(sched, ctx))
var (
sigTx *transaction.SignedTransaction
proof *transaction.Proof
)

f := func() error {
var err error
sigTx, proof, err = m.signAndSubmitTx(ctx, signer, tx, withProof)
return err
}

if err := backoff.Retry(f, backoff.WithContext(sched, ctx)); err != nil {
return nil, nil, err
}

return sigTx, proof, nil
}

// Implements SubmissionManager.
func (m *submissionManager) SignAndSubmitTx(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) error {
_, _, err := m.signAndSubmitTxWithRetry(ctx, signer, tx, false)
return err
}

// Implements SubmissionManager.
func (m *submissionManager) SignAndSubmitTxWithProof(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) (*transaction.SignedTransaction, *transaction.Proof, error) {
return m.signAndSubmitTxWithRetry(ctx, signer, tx, true)
}

// NewSubmissionManager creates a new transaction submission manager.
Expand All @@ -209,6 +245,18 @@ func SignAndSubmitTx(ctx context.Context, backend Backend, signer signature.Sign
return backend.SubmissionManager().SignAndSubmitTx(ctx, signer, tx)
}

// SignAndSubmitTxWithProof is a helper function that signs and submits
// a transaction to the consensus backend and creates a proof of inclusion.
//
// If the nonce is set to zero, it will be automatically filled in based on the
// current consensus state.
//
// If the fee is set to nil, it will be automatically filled in based on gas
// estimation and current gas price discovery.
func SignAndSubmitTxWithProof(ctx context.Context, backend Backend, signer signature.Signer, tx *transaction.Transaction) (*transaction.SignedTransaction, *transaction.Proof, error) {
return backend.SubmissionManager().SignAndSubmitTxWithProof(ctx, signer, tx)
}

// NoOpSubmissionManager implements a submission manager that doesn't support submitting transactions.
type NoOpSubmissionManager struct{}

Expand All @@ -226,3 +274,8 @@ func (m *NoOpSubmissionManager) EstimateGasAndSetFee(ctx context.Context, signer
func (m *NoOpSubmissionManager) SignAndSubmitTx(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) error {
return transaction.ErrMethodNotSupported
}

// SignAndSubmitTxWithProof implements SubmissionManager.
func (m *NoOpSubmissionManager) SignAndSubmitTxWithProof(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) (*transaction.SignedTransaction, *transaction.Proof, error) {
return nil, nil, transaction.ErrMethodNotSupported
}
11 changes: 10 additions & 1 deletion go/consensus/api/transaction/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ type PrettyTransaction struct {
Body interface{} `json:"body,omitempty"`
}

// SignedTransaction is a signed transaction.
// SignedTransaction is a signed consensus transaction.
type SignedTransaction struct {
signature.Signed
}
Expand Down Expand Up @@ -345,3 +345,12 @@ func NewMethodName(module, method string, bodyType interface{}) MethodName {

return MethodName(name)
}

// Proof is a proof of transaction inclusion in a block.
type Proof struct {
// Height is the block height at which the transaction was published.
Height int64 `json:"height"`

// RawProof is the actual raw proof.
RawProof []byte `json:"raw_proof"`
}
5 changes: 5 additions & 0 deletions go/consensus/tendermint/full/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,11 @@ func (n *commonNode) SubmitTx(ctx context.Context, tx *transaction.SignedTransac
return consensusAPI.ErrUnsupported
}

// Implements consensusAPI.Backend.
func (n *commonNode) SubmitTxWithProof(ctx context.Context, tx *transaction.SignedTransaction) (*transaction.Proof, error) {
return nil, consensusAPI.ErrUnsupported
}

// Implements consensusAPI.Backend.
func (n *commonNode) GetUnconfirmedTransactions(ctx context.Context) ([][]byte, error) {
return nil, consensusAPI.ErrUnsupported
Expand Down
61 changes: 51 additions & 10 deletions go/consensus/tendermint/full/full.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package full
import (
"bytes"
"context"
"crypto/sha256"
"fmt"
"math/rand"
"path/filepath"
Expand All @@ -17,6 +18,7 @@ import (
"github.com/spf13/viper"
tmabcitypes "github.com/tendermint/tendermint/abci/types"
tmconfig "github.com/tendermint/tendermint/config"
tmmerkle "github.com/tendermint/tendermint/crypto/merkle"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
tmlight "github.com/tendermint/tendermint/light"
tmmempool "github.com/tendermint/tendermint/mempool"
Expand Down Expand Up @@ -244,18 +246,56 @@ func (t *fullService) Mode() consensusAPI.Mode {

// Implements consensusAPI.Backend.
func (t *fullService) SubmitTx(ctx context.Context, tx *transaction.SignedTransaction) error {
if _, err := t.submitTx(ctx, tx); err != nil {
return err
}
return nil
}

// Implements consensusAPI.Backend.
func (t *fullService) SubmitTxWithProof(ctx context.Context, tx *transaction.SignedTransaction) (*transaction.Proof, error) {
data, err := t.submitTx(ctx, tx)
if err != nil {
return nil, err
}

txs, err := t.GetTransactions(ctx, data.Height)
if err != nil {
return nil, err
}

if data.Index >= uint32(len(txs)) {
return nil, fmt.Errorf("tendermint: invalid transaction index")
}

// Tendermint Merkle tree is computed over hashes and not over transactions.
hashes := make([][]byte, 0, len(txs))
for _, tx := range txs {
hash := sha256.Sum256(tx)
hashes = append(hashes, hash[:])
}

_, proofs := tmmerkle.ProofsFromByteSlices(hashes)

return &transaction.Proof{
Height: data.Height,
RawProof: cbor.Marshal(proofs[data.Index]),
}, nil
}

func (t *fullService) submitTx(ctx context.Context, tx *transaction.SignedTransaction) (*tmtypes.EventDataTx, error) {
// Subscribe to the transaction being included in a block.
data := cbor.Marshal(tx)
query := tmtypes.EventQueryTxFor(data)
subID := t.newSubscriberID()
txSub, err := t.subscribe(subID, query)
if err != nil {
return err
return nil, err
}
if ptrSub, ok := txSub.(*tendermintPubsubBuffer).tmSubscription.(*tmpubsub.Subscription); ok && ptrSub == nil {
t.Logger.Debug("broadcastTx: service has shut down. Cancel our context to recover")
<-ctx.Done()
return ctx.Err()
return nil, ctx.Err()
}

defer t.unsubscribe(subID, query) // nolint: errcheck
Expand All @@ -265,28 +305,29 @@ func (t *fullService) SubmitTx(ctx context.Context, tx *transaction.SignedTransa

recheckCh, recheckSub, err := t.mux.WatchInvalidatedTx(txHash)
if err != nil {
return err
return nil, err
}
defer recheckSub.Close()

// First try to broadcast.
if err := t.broadcastTxRaw(data); err != nil {
return err
return nil, err
}

// Wait for the transaction to be included in a block.
select {
case v := <-recheckCh:
return v
return nil, v
case v := <-txSub.Out():
if result := v.Data().(tmtypes.EventDataTx).Result; !result.IsOK() {
return errors.FromCode(result.GetCodespace(), result.GetCode(), result.GetLog())
data := v.Data().(tmtypes.EventDataTx)
if result := data.Result; !result.IsOK() {
return nil, errors.FromCode(result.GetCodespace(), result.GetCode(), result.GetLog())
}
return nil
return &data, nil
case <-txSub.Cancelled():
return context.Canceled
return nil, context.Canceled
case <-ctx.Done():
return ctx.Err()
return nil, ctx.Err()
}
}

Expand Down
5 changes: 5 additions & 0 deletions go/consensus/tendermint/seed/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ func (srv *seedService) SubmitTx(ctx context.Context, tx *transaction.SignedTran
return consensus.ErrUnsupported
}

// Implements consensus.Backend.
func (srv *seedService) SubmitTxWithProof(ctx context.Context, tx *transaction.SignedTransaction) (*transaction.Proof, error) {
return nil, consensus.ErrUnsupported
}

// Implements consensus.Backend.
func (srv *seedService) StateToGenesis(ctx context.Context, height int64) (*genesis.Document, error) {
return nil, consensus.ErrUnsupported
Expand Down
Loading