github.com/evdatsion/aphelion-dpos-bft@v0.32.1/lite/proxy/wrapper.go (about)

     1  package proxy
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
     8  
     9  	"github.com/evdatsion/aphelion-dpos-bft/crypto/merkle"
    10  	"github.com/evdatsion/aphelion-dpos-bft/lite"
    11  	rpcclient "github.com/evdatsion/aphelion-dpos-bft/rpc/client"
    12  	ctypes "github.com/evdatsion/aphelion-dpos-bft/rpc/core/types"
    13  	rpctypes "github.com/evdatsion/aphelion-dpos-bft/rpc/lib/types"
    14  )
    15  
    16  var _ rpcclient.Client = Wrapper{}
    17  
    18  // Wrapper wraps a rpcclient with a Verifier and double-checks any input that is
    19  // provable before passing it along. Allows you to make any rpcclient fully secure.
    20  type Wrapper struct {
    21  	rpcclient.Client
    22  	cert *lite.DynamicVerifier
    23  	prt  *merkle.ProofRuntime
    24  }
    25  
    26  // SecureClient uses a given Verifier to wrap an connection to an untrusted
    27  // host and return a cryptographically secure rpc client.
    28  //
    29  // If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface
    30  func SecureClient(c rpcclient.Client, cert *lite.DynamicVerifier) Wrapper {
    31  	prt := defaultProofRuntime()
    32  	wrap := Wrapper{c, cert, prt}
    33  	// TODO: no longer possible as no more such interface exposed....
    34  	// if we wrap http client, then we can swap out the event switch to filter
    35  	// if hc, ok := c.(*rpcclient.HTTP); ok {
    36  	// 	evt := hc.WSEvents.EventSwitch
    37  	// 	hc.WSEvents.EventSwitch = WrappedSwitch{evt, wrap}
    38  	// }
    39  	return wrap
    40  }
    41  
    42  // ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof
    43  func (w Wrapper) ABCIQueryWithOptions(path string, data cmn.HexBytes,
    44  	opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
    45  
    46  	res, err := GetWithProofOptions(w.prt, path, data, opts, w.Client, w.cert)
    47  	return res, err
    48  }
    49  
    50  // ABCIQuery uses default options for the ABCI query and verifies the returned proof
    51  func (w Wrapper) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) {
    52  	return w.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions)
    53  }
    54  
    55  // Tx queries for a given tx and verifies the proof if it was requested
    56  func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
    57  	res, err := w.Client.Tx(hash, prove)
    58  	if !prove || err != nil {
    59  		return res, err
    60  	}
    61  	h := int64(res.Height)
    62  	sh, err := GetCertifiedCommit(h, w.Client, w.cert)
    63  	if err != nil {
    64  		return res, err
    65  	}
    66  	err = res.Proof.Validate(sh.DataHash)
    67  	return res, err
    68  }
    69  
    70  // BlockchainInfo requests a list of headers and verifies them all...
    71  // Rather expensive.
    72  //
    73  // TODO: optimize this if used for anything needing performance
    74  func (w Wrapper) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
    75  	r, err := w.Client.BlockchainInfo(minHeight, maxHeight)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	// go and verify every blockmeta in the result....
    81  	for _, meta := range r.BlockMetas {
    82  		// get a checkpoint to verify from
    83  		res, err := w.Commit(&meta.Header.Height)
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		sh := res.SignedHeader
    88  		err = ValidateBlockMeta(meta, sh)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  	}
    93  
    94  	return r, nil
    95  }
    96  
    97  // Block returns an entire block and verifies all signatures
    98  func (w Wrapper) Block(height *int64) (*ctypes.ResultBlock, error) {
    99  	resBlock, err := w.Client.Block(height)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	// get a checkpoint to verify from
   104  	resCommit, err := w.Commit(height)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	sh := resCommit.SignedHeader
   109  
   110  	// now verify
   111  	err = ValidateBlockMeta(resBlock.BlockMeta, sh)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	err = ValidateBlock(resBlock.Block, sh)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	return resBlock, nil
   120  }
   121  
   122  // Commit downloads the Commit and certifies it with the lite.
   123  //
   124  // This is the foundation for all other verification in this module
   125  func (w Wrapper) Commit(height *int64) (*ctypes.ResultCommit, error) {
   126  	if height == nil {
   127  		resStatus, err := w.Client.Status()
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  		// NOTE: If resStatus.CatchingUp, there is a race
   132  		// condition where the validator set for the next height
   133  		// isn't available until some time after the blockstore
   134  		// has height h on the remote node.  This isn't an issue
   135  		// once the node has caught up, and a syncing node likely
   136  		// won't have this issue esp with the implementation we
   137  		// have here, but we may have to address this at some
   138  		// point.
   139  		height = new(int64)
   140  		*height = resStatus.SyncInfo.LatestBlockHeight
   141  	}
   142  	rpcclient.WaitForHeight(w.Client, *height, nil)
   143  	res, err := w.Client.Commit(height)
   144  	// if we got it, then verify it
   145  	if err == nil {
   146  		sh := res.SignedHeader
   147  		err = w.cert.Verify(sh)
   148  	}
   149  	return res, err
   150  }
   151  
   152  func (w Wrapper) RegisterOpDecoder(typ string, dec merkle.OpDecoder) {
   153  	w.prt.RegisterOpDecoder(typ, dec)
   154  }
   155  
   156  // SubscribeWS subscribes for events using the given query and remote address as
   157  // a subscriber, but does not verify responses (UNSAFE)!
   158  func (w Wrapper) SubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) {
   159  	out, err := w.Client.Subscribe(context.Background(), ctx.RemoteAddr(), query)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	go func() {
   165  		for {
   166  			select {
   167  			case resultEvent := <-out:
   168  				// XXX(melekes) We should have a switch here that performs a validation
   169  				// depending on the event's type.
   170  				ctx.WSConn.TryWriteRPCResponse(
   171  					rpctypes.NewRPCSuccessResponse(
   172  						ctx.WSConn.Codec(),
   173  						rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", ctx.JSONReq.ID)),
   174  						resultEvent,
   175  					))
   176  			case <-w.Client.Quit():
   177  				return
   178  			}
   179  		}
   180  	}()
   181  
   182  	return &ctypes.ResultSubscribe{}, nil
   183  }
   184  
   185  // UnsubscribeWS calls original client's Unsubscribe using remote address as a
   186  // subscriber.
   187  func (w Wrapper) UnsubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) {
   188  	err := w.Client.Unsubscribe(context.Background(), ctx.RemoteAddr(), query)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	return &ctypes.ResultUnsubscribe{}, nil
   193  }
   194  
   195  // UnsubscribeAllWS calls original client's UnsubscribeAll using remote address
   196  // as a subscriber.
   197  func (w Wrapper) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) {
   198  	err := w.Client.UnsubscribeAll(context.Background(), ctx.RemoteAddr())
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	return &ctypes.ResultUnsubscribe{}, nil
   203  }
   204  
   205  // // WrappedSwitch creates a websocket connection that auto-verifies any info
   206  // // coming through before passing it along.
   207  // //
   208  // // Since the verification takes 1-2 rpc calls, this is obviously only for
   209  // // relatively low-throughput situations that can tolerate a bit extra latency
   210  // type WrappedSwitch struct {
   211  // 	types.EventSwitch
   212  // 	client rpcclient.Client
   213  // }
   214  
   215  // // FireEvent verifies any block or header returned from the eventswitch
   216  // func (s WrappedSwitch) FireEvent(event string, data events.EventData) {
   217  // 	tm, ok := data.(types.TMEventData)
   218  // 	if !ok {
   219  // 		fmt.Printf("bad type %#v\n", data)
   220  // 		return
   221  // 	}
   222  
   223  // 	// check to validate it if possible, and drop if not valid
   224  // 	switch t := tm.(type) {
   225  // 	case types.EventDataNewBlockHeader:
   226  // 		err := verifyHeader(s.client, t.Header)
   227  // 		if err != nil {
   228  // 			fmt.Printf("Invalid header: %#v\n", err)
   229  // 			return
   230  // 		}
   231  // 	case types.EventDataNewBlock:
   232  // 		err := verifyBlock(s.client, t.Block)
   233  // 		if err != nil {
   234  // 			fmt.Printf("Invalid block: %#v\n", err)
   235  // 			return
   236  // 		}
   237  // 		// TODO: can we verify tx as well? anything else
   238  // 	}
   239  
   240  // 	// looks good, we fire it
   241  // 	s.EventSwitch.FireEvent(event, data)
   242  // }
   243  
   244  // func verifyHeader(c rpcclient.Client, head *types.Header) error {
   245  // 	// get a checkpoint to verify from
   246  // 	commit, err := c.Commit(&head.Height)
   247  // 	if err != nil {
   248  // 		return err
   249  // 	}
   250  // 	check := certclient.CommitFromResult(commit)
   251  // 	return ValidateHeader(head, check)
   252  // }
   253  //
   254  // func verifyBlock(c rpcclient.Client, block *types.Block) error {
   255  // 	// get a checkpoint to verify from
   256  // 	commit, err := c.Commit(&block.Height)
   257  // 	if err != nil {
   258  // 		return err
   259  // 	}
   260  // 	check := certclient.CommitFromResult(commit)
   261  // 	return ValidateBlock(block, check)
   262  // }