561 lines
15 KiB
Go
561 lines
15 KiB
Go
// Copyright (c) 2019-2020 The Zcash developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
|
package frontend
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/zcash/lightwalletd/common"
|
|
"github.com/zcash/lightwalletd/walletrpc"
|
|
)
|
|
|
|
var (
|
|
testT *testing.T
|
|
logger = logrus.New()
|
|
step int
|
|
|
|
blocks [][]byte // four test blocks
|
|
rawTxData [][]byte
|
|
)
|
|
|
|
const (
|
|
unitTestPath = "unittestcache"
|
|
unitTestChain = "unittestnet"
|
|
)
|
|
|
|
func testsetup() (walletrpc.CompactTxStreamerServer, *common.BlockCache) {
|
|
os.RemoveAll(unitTestPath)
|
|
cache := common.NewBlockCache(unitTestPath, unitTestChain, 380640, true)
|
|
lwd, err := NewLwdStreamer(cache)
|
|
if err != nil {
|
|
os.Stderr.WriteString(fmt.Sprint("NewLwdStreamer failed:", err))
|
|
os.Exit(1)
|
|
}
|
|
return lwd, cache
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
output, err := os.OpenFile("test-log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
os.Stderr.WriteString(fmt.Sprint("Cannot open test-log:", err))
|
|
os.Exit(1)
|
|
}
|
|
logger.SetOutput(output)
|
|
common.Log = logger.WithFields(logrus.Fields{
|
|
"app": "test",
|
|
})
|
|
|
|
// Several tests need test blocks; read all 4 into memory just once
|
|
// (for efficiency).
|
|
testBlocks, err := os.Open("../testdata/blocks")
|
|
if err != nil {
|
|
os.Stderr.WriteString(fmt.Sprint("Error:", err))
|
|
os.Exit(1)
|
|
}
|
|
defer testBlocks.Close()
|
|
scan := bufio.NewScanner(testBlocks)
|
|
for scan.Scan() { // each line (block)
|
|
blockJSON, _ := json.Marshal(scan.Text())
|
|
blocks = append(blocks, blockJSON)
|
|
}
|
|
|
|
testData, err := os.Open("../testdata/zip243_raw_tx")
|
|
if err != nil {
|
|
os.Stderr.WriteString(fmt.Sprint("Error:", err))
|
|
os.Exit(1)
|
|
}
|
|
defer testData.Close()
|
|
|
|
// Parse the raw transactions file
|
|
rawTxData = [][]byte{}
|
|
scan = bufio.NewScanner(testData)
|
|
for scan.Scan() {
|
|
dataLine := scan.Text()
|
|
// Skip the comments
|
|
if strings.HasPrefix(dataLine, "#") {
|
|
continue
|
|
}
|
|
|
|
txData, err := hex.DecodeString(dataLine)
|
|
if err != nil {
|
|
os.Stderr.WriteString(fmt.Sprint("Error:", err))
|
|
os.Exit(1)
|
|
}
|
|
|
|
rawTxData = append(rawTxData, txData)
|
|
}
|
|
|
|
// Setup is done; run all tests.
|
|
exitcode := m.Run()
|
|
|
|
// cleanup
|
|
os.Remove("test-log")
|
|
os.RemoveAll(unitTestPath)
|
|
|
|
os.Exit(exitcode)
|
|
}
|
|
|
|
func TestGetTransaction(t *testing.T) {
|
|
// GetTransaction() will mostly be tested below via TestGetTaddressTxids
|
|
lwd, _ := testsetup()
|
|
|
|
rawtx, err := lwd.GetTransaction(context.Background(),
|
|
&walletrpc.TxFilter{})
|
|
if err == nil {
|
|
testT.Fatal("GetTransaction unexpectedly succeeded")
|
|
}
|
|
if err.Error() != "Please call GetTransaction with txid" {
|
|
testT.Fatal("GetTransaction unexpected error message")
|
|
}
|
|
if rawtx != nil {
|
|
testT.Fatal("GetTransaction non-nil rawtx returned")
|
|
}
|
|
|
|
rawtx, err = lwd.GetTransaction(context.Background(),
|
|
&walletrpc.TxFilter{Block: &walletrpc.BlockID{Hash: []byte{}}})
|
|
if err == nil {
|
|
testT.Fatal("GetTransaction unexpectedly succeeded")
|
|
}
|
|
if err.Error() != "Can't GetTransaction with a blockhash+num. Please call GetTransaction with txid" {
|
|
testT.Fatal("GetTransaction unexpected error message")
|
|
}
|
|
if rawtx != nil {
|
|
testT.Fatal("GetTransaction non-nil rawtx returned")
|
|
}
|
|
}
|
|
|
|
func getblockStub(method string, params []json.RawMessage) (json.RawMessage, error) {
|
|
step++
|
|
var height string
|
|
err := json.Unmarshal(params[0], &height)
|
|
if err != nil {
|
|
testT.Fatal("could not unmarshal height")
|
|
}
|
|
if height != "380640" {
|
|
testT.Fatal("unexpected getblock height", height)
|
|
}
|
|
|
|
// Test retry logic (for the moment, it's very simple, just one retry).
|
|
switch step {
|
|
case 1:
|
|
return blocks[0], nil
|
|
case 2:
|
|
return nil, errors.New("getblock test error")
|
|
}
|
|
testT.Fatal("unexpected call to getblockStub")
|
|
return nil, nil
|
|
}
|
|
|
|
func TestGetLatestBlock(t *testing.T) {
|
|
testT = t
|
|
common.RawRequest = getblockStub
|
|
lwd, cache := testsetup()
|
|
|
|
// This argument is not used (it may be in the future)
|
|
req := &walletrpc.ChainSpec{}
|
|
|
|
blockID, err := lwd.GetLatestBlock(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("GetLatestBlock should have failed, empty cache")
|
|
}
|
|
if err.Error() != "Cache is empty. Server is probably not yet ready" {
|
|
t.Fatal("GetLatestBlock incorrect error", err)
|
|
}
|
|
if blockID != nil {
|
|
t.Fatal("unexpected blockID", blockID)
|
|
}
|
|
|
|
// This does zcashd rpc "getblock", calls getblockStub() above
|
|
block, err := common.GetBlock(cache, 380640)
|
|
if err != nil {
|
|
t.Fatal("getBlockFromRPC failed", err)
|
|
}
|
|
if err = cache.Add(380640, block); err != nil {
|
|
t.Fatal("cache.Add failed:", err)
|
|
}
|
|
blockID, err = lwd.GetLatestBlock(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal("lwd.GetLatestBlock failed", err)
|
|
}
|
|
if blockID.Height != 380640 {
|
|
t.Fatal("unexpected blockID.height")
|
|
}
|
|
step = 0
|
|
}
|
|
|
|
// A valid address starts with "t", followed by 34 alpha characters;
|
|
// these should all be detected as invalid.
|
|
var addressTests = []string{
|
|
"", // too short
|
|
"a", // too short
|
|
"t123456789012345678901234567890123", // one byte too short
|
|
"t12345678901234567890123456789012345", // one byte too long
|
|
"t123456789012345678901234567890123*", // invalid "*"
|
|
"s1234567890123456789012345678901234", // doesn't start with "t"
|
|
" t1234567890123456789012345678901234", // extra stuff before
|
|
"t1234567890123456789012345678901234 ", // extra stuff after
|
|
"\nt1234567890123456789012345678901234", // newline before
|
|
"t1234567890123456789012345678901234\n", // newline after
|
|
}
|
|
|
|
func zcashdrpcStub(method string, params []json.RawMessage) (json.RawMessage, error) {
|
|
step++
|
|
switch method {
|
|
case "getaddresstxids":
|
|
var filter struct {
|
|
Addresses []string
|
|
Start float64
|
|
End float64
|
|
}
|
|
err := json.Unmarshal(params[0], &filter)
|
|
if err != nil {
|
|
testT.Fatal("could not unmarshal block filter")
|
|
}
|
|
if len(filter.Addresses) != 1 {
|
|
testT.Fatal("wrong number of addresses")
|
|
}
|
|
if filter.Addresses[0] != "t1234567890123456789012345678901234" {
|
|
testT.Fatal("wrong address")
|
|
}
|
|
if filter.Start != 20 {
|
|
testT.Fatal("wrong start")
|
|
}
|
|
if filter.End != 30 {
|
|
testT.Fatal("wrong end")
|
|
}
|
|
return []byte("[\"6732cf8d67aac5b82a2a0f0217a7d4aa245b2adb0b97fd2d923dfc674415e221\"]"), nil
|
|
case "getrawtransaction":
|
|
switch step {
|
|
case 2:
|
|
tx := &struct {
|
|
Hex string `json:"hex"`
|
|
Height int `json:"height"`
|
|
}{
|
|
Hex: hex.EncodeToString(rawTxData[0]),
|
|
Height: 1234567,
|
|
}
|
|
return json.Marshal(tx)
|
|
case 4:
|
|
// empty return value, should be okay
|
|
return []byte(""), errors.New("-5: test getrawtransaction error")
|
|
}
|
|
}
|
|
testT.Fatal("unexpected call to zcashdrpcStub")
|
|
return nil, nil
|
|
}
|
|
|
|
type testgettx struct {
|
|
walletrpc.CompactTxStreamer_GetTaddressTxidsServer
|
|
}
|
|
|
|
func (tg *testgettx) Context() context.Context {
|
|
return context.Background()
|
|
}
|
|
|
|
func (tg *testgettx) Send(tx *walletrpc.RawTransaction) error {
|
|
if !bytes.Equal(tx.Data, rawTxData[0]) {
|
|
testT.Fatal("mismatch transaction data")
|
|
}
|
|
if tx.Height != 1234567 {
|
|
testT.Fatal("unexpected transaction height", tx.Height)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestGetTaddressTxids(t *testing.T) {
|
|
testT = t
|
|
common.RawRequest = zcashdrpcStub
|
|
lwd, _ := testsetup()
|
|
|
|
addressBlockFilter := &walletrpc.TransparentAddressBlockFilter{
|
|
Range: &walletrpc.BlockRange{
|
|
Start: &walletrpc.BlockID{Height: 20},
|
|
End: &walletrpc.BlockID{Height: 30},
|
|
},
|
|
}
|
|
|
|
// Ensure that a bad address is detected
|
|
for i, addressTest := range addressTests {
|
|
addressBlockFilter.Address = addressTest
|
|
err := lwd.GetTaddressTxids(addressBlockFilter, &testgettx{})
|
|
if err == nil {
|
|
t.Fatal("GetTaddressTxids should have failed on bad address, case", i)
|
|
}
|
|
if err.Error() != "Invalid address" {
|
|
t.Fatal("GetTaddressTxids incorrect error on bad address, case", i)
|
|
}
|
|
}
|
|
|
|
// valid address
|
|
addressBlockFilter.Address = "t1234567890123456789012345678901234"
|
|
err := lwd.GetTaddressTxids(addressBlockFilter, &testgettx{})
|
|
if err != nil {
|
|
t.Fatal("GetTaddressTxids failed", err)
|
|
}
|
|
|
|
// this time GetTransaction() will return an error
|
|
err = lwd.GetTaddressTxids(addressBlockFilter, &testgettx{})
|
|
if err == nil {
|
|
t.Fatal("GetTaddressTxids succeeded")
|
|
}
|
|
step = 0
|
|
}
|
|
|
|
func TestGetBlock(t *testing.T) {
|
|
testT = t
|
|
common.RawRequest = getblockStub
|
|
lwd, _ := testsetup()
|
|
|
|
_, err := lwd.GetBlock(context.Background(), &walletrpc.BlockID{})
|
|
if err == nil {
|
|
t.Fatal("GetBlock should have failed")
|
|
}
|
|
_, err = lwd.GetBlock(context.Background(), &walletrpc.BlockID{Height: 0})
|
|
if err == nil {
|
|
t.Fatal("GetBlock should have failed")
|
|
}
|
|
_, err = lwd.GetBlock(context.Background(), &walletrpc.BlockID{Hash: []byte{0}})
|
|
if err == nil {
|
|
t.Fatal("GetBlock should have failed")
|
|
}
|
|
if err.Error() != "GetBlock by Hash is not yet implemented" {
|
|
t.Fatal("GetBlock hash unimplemented error message failed")
|
|
}
|
|
|
|
// getblockStub() case 1: return error
|
|
block, err := lwd.GetBlock(context.Background(), &walletrpc.BlockID{Height: 380640})
|
|
if err != nil {
|
|
t.Fatal("GetBlock failed:", err)
|
|
}
|
|
if block.Height != 380640 {
|
|
t.Fatal("GetBlock returned unexpected block:", err)
|
|
}
|
|
// getblockStub() case 2: return error
|
|
block, err = lwd.GetBlock(context.Background(), &walletrpc.BlockID{Height: 380640})
|
|
if err == nil {
|
|
t.Fatal("GetBlock should have failed")
|
|
}
|
|
if block != nil {
|
|
t.Fatal("GetBlock returned unexpected non-nil block")
|
|
}
|
|
step = 0
|
|
}
|
|
|
|
type testgetbrange struct {
|
|
walletrpc.CompactTxStreamer_GetTaddressTxidsServer
|
|
}
|
|
|
|
func (tg *testgetbrange) Context() context.Context {
|
|
return context.Background()
|
|
}
|
|
|
|
func (tg *testgetbrange) Send(cb *walletrpc.CompactBlock) error {
|
|
return nil
|
|
}
|
|
|
|
func TestGetBlockRange(t *testing.T) {
|
|
testT = t
|
|
common.RawRequest = getblockStub
|
|
common.RawRequest = getblockStub
|
|
lwd, _ := testsetup()
|
|
|
|
blockrange := &walletrpc.BlockRange{
|
|
Start: &walletrpc.BlockID{Height: 380640},
|
|
End: &walletrpc.BlockID{Height: 380640},
|
|
}
|
|
// getblockStub() case 1 (success)
|
|
err := lwd.GetBlockRange(blockrange, &testgetbrange{})
|
|
if err != nil {
|
|
t.Fatal("GetBlockRange failed", err)
|
|
}
|
|
// getblockStub() case 2 (failure)
|
|
err = lwd.GetBlockRange(blockrange, &testgetbrange{})
|
|
if err == nil {
|
|
t.Fatal("GetBlockRange should have failed")
|
|
}
|
|
step = 0
|
|
}
|
|
|
|
func getblockchaininfoStub(method string, params []json.RawMessage) (json.RawMessage, error) {
|
|
getsaplinginfo, _ := ioutil.ReadFile("../testdata/getsaplinginfo")
|
|
getblockchaininfoReply, _ := hex.DecodeString(string(getsaplinginfo))
|
|
return getblockchaininfoReply, nil
|
|
}
|
|
|
|
func TestGetLightdInfo(t *testing.T) {
|
|
testT = t
|
|
common.RawRequest = getblockchaininfoStub
|
|
lwd, _ := testsetup()
|
|
|
|
ldinfo, err := lwd.GetLightdInfo(context.Background(), &walletrpc.Empty{})
|
|
if err != nil {
|
|
t.Fatal("GetLightdInfo failed", err)
|
|
}
|
|
if ldinfo.Vendor != "ECC LightWalletD" {
|
|
t.Fatal("GetLightdInfo: unexpected vendor", ldinfo)
|
|
}
|
|
step = 0
|
|
}
|
|
|
|
func sendrawtransactionStub(method string, params []json.RawMessage) (json.RawMessage, error) {
|
|
step++
|
|
if method != "sendrawtransaction" {
|
|
testT.Fatal("unexpected method")
|
|
}
|
|
if string(params[0]) != "\"07\"" {
|
|
testT.Fatal("unexpected tx data")
|
|
}
|
|
switch step {
|
|
case 1:
|
|
return []byte("sendtxresult"), nil
|
|
case 2:
|
|
return nil, errors.New("-17: some error")
|
|
}
|
|
testT.Fatal("unexpected call to sendrawtransactionStub")
|
|
return nil, nil
|
|
}
|
|
|
|
func TestSendTransaction(t *testing.T) {
|
|
testT = t
|
|
lwd, _ := testsetup()
|
|
common.RawRequest = sendrawtransactionStub
|
|
rawtx := walletrpc.RawTransaction{Data: []byte{7}}
|
|
sendresult, err := lwd.SendTransaction(context.Background(), &rawtx)
|
|
if err != nil {
|
|
t.Fatal("SendTransaction failed", err)
|
|
}
|
|
if sendresult.ErrorCode != 0 {
|
|
t.Fatal("SendTransaction unexpected ErrorCode return")
|
|
}
|
|
if sendresult.ErrorMessage != "sendtxresult" {
|
|
t.Fatal("SendTransaction unexpected ErrorMessage return")
|
|
}
|
|
|
|
// sendrawtransactionStub case 2 (error)
|
|
// but note that the error is send within the response
|
|
sendresult, err = lwd.SendTransaction(context.Background(), &rawtx)
|
|
if err != nil {
|
|
t.Fatal("SendTransaction failed:", err)
|
|
}
|
|
if sendresult.ErrorCode != -17 {
|
|
t.Fatal("SendTransaction unexpected ErrorCode return")
|
|
}
|
|
if sendresult.ErrorMessage != "some error" {
|
|
t.Fatal("SendTransaction unexpected ErrorMessage return")
|
|
}
|
|
step = 0
|
|
}
|
|
|
|
var sampleconf = `
|
|
testnet = 1
|
|
rpcport = 18232
|
|
rpcbind = 127.0.0.1
|
|
rpcuser = testlightwduser
|
|
rpcpassword = testlightwdpassword
|
|
`
|
|
|
|
func TestNewZRPCFromConf(t *testing.T) {
|
|
connCfg, err := connFromConf([]byte(sampleconf))
|
|
if err != nil {
|
|
t.Fatal("connFromConf failed")
|
|
}
|
|
if connCfg.Host != "127.0.0.1:18232" {
|
|
t.Fatal("connFromConf returned unexpected Host")
|
|
}
|
|
if connCfg.User != "testlightwduser" {
|
|
t.Fatal("connFromConf returned unexpected User")
|
|
}
|
|
if connCfg.Pass != "testlightwdpassword" {
|
|
t.Fatal("connFromConf returned unexpected User")
|
|
}
|
|
if !connCfg.HTTPPostMode {
|
|
t.Fatal("connFromConf returned unexpected HTTPPostMode")
|
|
}
|
|
if !connCfg.DisableTLS {
|
|
t.Fatal("connFromConf returned unexpected DisableTLS")
|
|
}
|
|
|
|
// can't pass an integer
|
|
connCfg, err = connFromConf(10)
|
|
if err == nil {
|
|
t.Fatal("connFromConf unexpected success")
|
|
}
|
|
|
|
// Can't verify returned values, but at least run it
|
|
_, err = NewZRPCFromConf([]byte(sampleconf))
|
|
if err != nil {
|
|
t.Fatal("NewZRPCFromClient failed")
|
|
}
|
|
_, err = NewZRPCFromConf(10)
|
|
if err == nil {
|
|
t.Fatal("NewZRPCFromClient unexpected success")
|
|
}
|
|
}
|
|
|
|
func TestMempoolFilter(t *testing.T) {
|
|
txidlist := []string{
|
|
"2e819d0bab5c819dc7d5f92d1bfb4127ce321daf847f6602",
|
|
"29e594c312eee49bc2c9ad37367ba58f857c4a7387ec9715",
|
|
"d4d090e60bf9141c6573f0598b84cc1f9817543e55a4d84d",
|
|
"d4714779c6dd32a72077bd79d4a70cb2153b552d7addec15",
|
|
"9839c1d4deca000656caff57c1f720f4fbd114b52239edde",
|
|
"ce5a28854a509ab309faa433542e73414fef6e903a3d52f5",
|
|
}
|
|
exclude := []string{
|
|
"98aa", // common prefix (98) but no match
|
|
"19", // no match
|
|
"29", // one match (should not appear)
|
|
"d4", // 2 matches (both should appear in result)
|
|
"ce5a28854a509ab309faa433542e73414fef6e903a3d52f5", // exact match
|
|
"ce5a28854a509ab309faa433542e73414fef6e903a3d52f500", // extra stuff ignored
|
|
}
|
|
expected := []string{
|
|
"2e819d0bab5c819dc7d5f92d1bfb4127ce321daf847f6602",
|
|
"9839c1d4deca000656caff57c1f720f4fbd114b52239edde",
|
|
"d4714779c6dd32a72077bd79d4a70cb2153b552d7addec15",
|
|
"d4d090e60bf9141c6573f0598b84cc1f9817543e55a4d84d",
|
|
}
|
|
actual := MempoolFilter(txidlist, exclude)
|
|
if len(actual) != len(expected) {
|
|
t.Fatal("mempool: wrong number of filter results")
|
|
}
|
|
for i := 0; i < len(actual); i++ {
|
|
if actual[i] != expected[i] {
|
|
t.Fatal(fmt.Sprintf("mempool: expected: %s actual: %s",
|
|
expected[i], actual[i]))
|
|
}
|
|
}
|
|
// If the exclude list is empty, return the entire mempool.
|
|
actual = MempoolFilter(txidlist, []string{})
|
|
expected = []string{
|
|
"29e594c312eee49bc2c9ad37367ba58f857c4a7387ec9715",
|
|
"2e819d0bab5c819dc7d5f92d1bfb4127ce321daf847f6602",
|
|
"9839c1d4deca000656caff57c1f720f4fbd114b52239edde",
|
|
"ce5a28854a509ab309faa433542e73414fef6e903a3d52f5",
|
|
"d4714779c6dd32a72077bd79d4a70cb2153b552d7addec15",
|
|
"d4d090e60bf9141c6573f0598b84cc1f9817543e55a4d84d",
|
|
}
|
|
if len(actual) != len(expected) {
|
|
t.Fatal("mempool: wrong number of filter results")
|
|
}
|
|
for i := 0; i < len(actual); i++ {
|
|
if actual[i] != expected[i] {
|
|
t.Fatal(fmt.Sprintf("mempool: expected: %s actual: %s",
|
|
expected[i], actual[i]))
|
|
}
|
|
}
|
|
|
|
}
|