github.com/btcsuite/btcd@v0.24.0/rpcclient/chain_test.go (about)

     1  package rpcclient
     2  
     3  import (
     4  	"errors"
     5  	"github.com/gorilla/websocket"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  )
    13  
    14  var upgrader = websocket.Upgrader{}
    15  
    16  // TestUnmarshalGetBlockChainInfoResult ensures that the SoftForks and
    17  // UnifiedSoftForks fields of GetBlockChainInfoResult are properly unmarshaled
    18  // when using the expected backend version.
    19  func TestUnmarshalGetBlockChainInfoResultSoftForks(t *testing.T) {
    20  	t.Parallel()
    21  
    22  	tests := []struct {
    23  		name       string
    24  		version    BackendVersion
    25  		res        []byte
    26  		compatible bool
    27  	}{
    28  		{
    29  			name:       "bitcoind < 0.19.0 with separate softforks",
    30  			version:    BitcoindPre19,
    31  			res:        []byte(`{"softforks": [{"version": 2}]}`),
    32  			compatible: true,
    33  		},
    34  		{
    35  			name:       "bitcoind >= 0.19.0 with separate softforks",
    36  			version:    BitcoindPost19,
    37  			res:        []byte(`{"softforks": [{"version": 2}]}`),
    38  			compatible: false,
    39  		},
    40  		{
    41  			name:       "bitcoind < 0.19.0 with unified softforks",
    42  			version:    BitcoindPre19,
    43  			res:        []byte(`{"softforks": {"segwit": {"type": "bip9"}}}`),
    44  			compatible: false,
    45  		},
    46  		{
    47  			name:       "bitcoind >= 0.19.0 with unified softforks",
    48  			version:    BitcoindPost19,
    49  			res:        []byte(`{"softforks": {"segwit": {"type": "bip9"}}}`),
    50  			compatible: true,
    51  		},
    52  	}
    53  
    54  	for _, test := range tests {
    55  		success := t.Run(test.name, func(t *testing.T) {
    56  			// We'll start by unmarshaling the JSON into a struct.
    57  			// The SoftForks and UnifiedSoftForks field should not
    58  			// be set yet, as they are unmarshaled within a
    59  			// different function.
    60  			info, err := unmarshalPartialGetBlockChainInfoResult(test.res)
    61  			if err != nil {
    62  				t.Fatal(err)
    63  			}
    64  			if info.SoftForks != nil {
    65  				t.Fatal("expected SoftForks to be empty")
    66  			}
    67  			if info.UnifiedSoftForks != nil {
    68  				t.Fatal("expected UnifiedSoftForks to be empty")
    69  			}
    70  
    71  			// Proceed to unmarshal the softforks of the response
    72  			// with the expected version. If the version is
    73  			// incompatible with the response, then this should
    74  			// fail.
    75  			err = unmarshalGetBlockChainInfoResultSoftForks(
    76  				info, test.version, test.res,
    77  			)
    78  			if test.compatible && err != nil {
    79  				t.Fatalf("unable to unmarshal softforks: %v", err)
    80  			}
    81  			if !test.compatible && err == nil {
    82  				t.Fatal("expected to not unmarshal softforks")
    83  			}
    84  			if !test.compatible {
    85  				return
    86  			}
    87  
    88  			// If the version is compatible with the response, we
    89  			// should expect to see the proper softforks field set.
    90  			if test.version == BitcoindPost19 &&
    91  				info.SoftForks != nil {
    92  				t.Fatal("expected SoftForks to be empty")
    93  			}
    94  			if test.version == BitcoindPre19 &&
    95  				info.UnifiedSoftForks != nil {
    96  				t.Fatal("expected UnifiedSoftForks to be empty")
    97  			}
    98  		})
    99  		if !success {
   100  			return
   101  		}
   102  	}
   103  }
   104  
   105  func TestFutureGetBlockCountResultReceiveErrors(t *testing.T) {
   106  	responseChan := FutureGetBlockCountResult(make(chan *Response))
   107  	response := Response{
   108  		result: []byte{},
   109  		err:    errors.New("blah blah something bad happened"),
   110  	}
   111  	go func() {
   112  		responseChan <- &response
   113  	}()
   114  
   115  	_, err := responseChan.Receive()
   116  	if err == nil || err.Error() != "blah blah something bad happened" {
   117  		t.Fatalf("unexpected error: %s", err.Error())
   118  	}
   119  }
   120  
   121  func TestFutureGetBlockCountResultReceiveMarshalsResponseCorrectly(t *testing.T) {
   122  	responseChan := FutureGetBlockCountResult(make(chan *Response))
   123  	response := Response{
   124  		result: []byte{0x36, 0x36},
   125  		err:    nil,
   126  	}
   127  	go func() {
   128  		responseChan <- &response
   129  	}()
   130  
   131  	res, err := responseChan.Receive()
   132  	if err != nil {
   133  		t.Fatalf("unexpected error: %s", err.Error())
   134  	}
   135  
   136  	if res != 66 {
   137  		t.Fatalf("unexpected response: %d (0x%X)", res, res)
   138  	}
   139  }
   140  
   141  func TestClientConnectedToWSServerRunner(t *testing.T) {
   142  	type TestTableItem struct {
   143  		Name     string
   144  		TestCase func(t *testing.T)
   145  	}
   146  
   147  	testTable := []TestTableItem{
   148  		TestTableItem{
   149  			Name: "TestGetChainTxStatsAsyncSuccessTx",
   150  			TestCase: func(t *testing.T) {
   151  				client, serverReceivedChannel, cleanup := makeClient(t)
   152  				defer cleanup()
   153  				client.GetChainTxStatsAsync()
   154  
   155  				message := <-serverReceivedChannel
   156  				if message != "{\"jsonrpc\":\"1.0\",\"method\":\"getchaintxstats\",\"params\":[],\"id\":1}" {
   157  					t.Fatalf("received unexpected message: %s", message)
   158  				}
   159  			},
   160  		},
   161  		TestTableItem{
   162  			Name: "TestGetChainTxStatsAsyncShutdownError",
   163  			TestCase: func(t *testing.T) {
   164  				client, _, cleanup := makeClient(t)
   165  				defer cleanup()
   166  
   167  				// a bit of a hack here: since there are multiple places where we read
   168  				// from the shutdown channel, and it is not buffered, ensure that a shutdown
   169  				// message is sent every time it is read from, this will ensure that
   170  				// when client.GetChainTxStatsAsync() gets called, it hits the non-blocking
   171  				// read from the shutdown channel
   172  				go func() {
   173  					type shutdownMessage struct{}
   174  					for {
   175  						client.shutdown <- shutdownMessage{}
   176  					}
   177  				}()
   178  
   179  				var response *Response = nil
   180  
   181  				for response == nil {
   182  					respChan := client.GetChainTxStatsAsync()
   183  					select {
   184  					case response = <-respChan:
   185  					default:
   186  					}
   187  				}
   188  
   189  				if response.err == nil || response.err.Error() != "the client has been shutdown" {
   190  					t.Fatalf("unexpected error: %s", response.err.Error())
   191  				}
   192  			},
   193  		},
   194  		TestTableItem{
   195  			Name: "TestGetBestBlockHashAsync",
   196  			TestCase: func(t *testing.T) {
   197  				client, serverReceivedChannel, cleanup := makeClient(t)
   198  				defer cleanup()
   199  				ch := client.GetBestBlockHashAsync()
   200  
   201  				message := <-serverReceivedChannel
   202  				if message != "{\"jsonrpc\":\"1.0\",\"method\":\"getbestblockhash\",\"params\":[],\"id\":1}" {
   203  					t.Fatalf("received unexpected message: %s", message)
   204  				}
   205  
   206  				expectedResponse := Response{}
   207  
   208  				wg := sync.WaitGroup{}
   209  
   210  				wg.Add(1)
   211  				go func() {
   212  					defer wg.Done()
   213  					for {
   214  						client.requestLock.Lock()
   215  						if client.requestList.Len() > 0 {
   216  							r := client.requestList.Back()
   217  							r.Value.(*jsonRequest).responseChan <- &expectedResponse
   218  							client.requestLock.Unlock()
   219  							return
   220  						}
   221  						client.requestLock.Unlock()
   222  					}
   223  				}()
   224  
   225  				response := <-ch
   226  
   227  				if &expectedResponse != response {
   228  					t.Fatalf("received unexepcted response")
   229  				}
   230  
   231  				// ensure the goroutine created in this test exists,
   232  				// the test is ran with a timeout
   233  				wg.Wait()
   234  			},
   235  		},
   236  	}
   237  
   238  	// since these tests rely on concurrency, ensure there is a resonable timeout
   239  	// that they should run within
   240  	for _, testCase := range testTable {
   241  		done := make(chan bool)
   242  
   243  		go func() {
   244  			t.Run(testCase.Name, testCase.TestCase)
   245  			done <- true
   246  		}()
   247  
   248  		select {
   249  		case <-done:
   250  		case <-time.After(5 * time.Second):
   251  			t.Fatalf("timeout exceeded for: %s", testCase.Name)
   252  		}
   253  	}
   254  }
   255  
   256  func makeClient(t *testing.T) (*Client, chan string, func()) {
   257  	serverReceivedChannel := make(chan string)
   258  	s := httptest.NewServer(http.HandlerFunc(makeUpgradeOnConnect(serverReceivedChannel)))
   259  	url := strings.TrimPrefix(s.URL, "http://")
   260  
   261  	config := ConnConfig{
   262  		DisableTLS: true,
   263  		User:       "username",
   264  		Pass:       "password",
   265  		Host:       url,
   266  	}
   267  
   268  	client, err := New(&config, nil)
   269  	if err != nil {
   270  		t.Fatalf("error when creating new client %s", err.Error())
   271  	}
   272  	return client, serverReceivedChannel, func() {
   273  		s.Close()
   274  	}
   275  }
   276  
   277  func makeUpgradeOnConnect(ch chan string) func(http.ResponseWriter, *http.Request) {
   278  	return func(w http.ResponseWriter, r *http.Request) {
   279  		c, err := upgrader.Upgrade(w, r, nil)
   280  		if err != nil {
   281  			return
   282  		}
   283  		defer c.Close()
   284  		for {
   285  			_, message, err := c.ReadMessage()
   286  			if err != nil {
   287  				break
   288  			}
   289  
   290  			go func() {
   291  				ch <- string(message)
   292  			}()
   293  		}
   294  	}
   295  }