diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0cc837fe59a698175d65ee0ae24e18b28a4bf2a4
--- /dev/null
+++ b/cmd/geth/les_test.go
@@ -0,0 +1,180 @@
+package main
+
+import (
+	"context"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/rpc"
+)
+
+type gethrpc struct {
+	name     string
+	rpc      *rpc.Client
+	geth     *testgeth
+	nodeInfo *p2p.NodeInfo
+}
+
+func (g *gethrpc) killAndWait() {
+	g.geth.Kill()
+	g.geth.WaitExit()
+}
+
+func (g *gethrpc) callRPC(result interface{}, method string, args ...interface{}) {
+	if err := g.rpc.Call(&result, method, args...); err != nil {
+		g.geth.Fatalf("callRPC %v: %v", method, err)
+	}
+}
+
+func (g *gethrpc) addPeer(peer *gethrpc) {
+	g.geth.Logf("%v.addPeer(%v)", g.name, peer.name)
+	enode := peer.getNodeInfo().Enode
+	peerCh := make(chan *p2p.PeerEvent)
+	sub, err := g.rpc.Subscribe(context.Background(), "admin", peerCh, "peerEvents")
+	if err != nil {
+		g.geth.Fatalf("subscribe %v: %v", g.name, err)
+	}
+	defer sub.Unsubscribe()
+	g.callRPC(nil, "admin_addPeer", enode)
+	dur := 14 * time.Second
+	timeout := time.After(dur)
+	select {
+	case ev := <-peerCh:
+		g.geth.Logf("%v received event: type=%v, peer=%v", g.name, ev.Type, ev.Peer)
+	case err := <-sub.Err():
+		g.geth.Fatalf("%v sub error: %v", g.name, err)
+	case <-timeout:
+		g.geth.Error("timeout adding peer after", dur)
+	}
+}
+
+// Use this function instead of `g.nodeInfo` directly
+func (g *gethrpc) getNodeInfo() *p2p.NodeInfo {
+	if g.nodeInfo != nil {
+		return g.nodeInfo
+	}
+	g.nodeInfo = &p2p.NodeInfo{}
+	g.callRPC(&g.nodeInfo, "admin_nodeInfo")
+	return g.nodeInfo
+}
+
+func (g *gethrpc) waitSynced() {
+	// Check if it's synced now
+	var result interface{}
+	g.callRPC(&result, "eth_syncing")
+	syncing, ok := result.(bool)
+	if ok && !syncing {
+		g.geth.Logf("%v already synced", g.name)
+		return
+	}
+
+	// Actually wait, subscribe to the event
+	ch := make(chan interface{})
+	sub, err := g.rpc.Subscribe(context.Background(), "eth", ch, "syncing")
+	if err != nil {
+		g.geth.Fatalf("%v syncing: %v", g.name, err)
+	}
+	defer sub.Unsubscribe()
+	timeout := time.After(4 * time.Second)
+	select {
+	case ev := <-ch:
+		g.geth.Log("'syncing' event", ev)
+		syncing, ok := ev.(bool)
+		if ok && !syncing {
+			break
+		}
+		g.geth.Log("Other 'syncing' event", ev)
+	case err := <-sub.Err():
+		g.geth.Fatalf("%v notification: %v", g.name, err)
+		break
+	case <-timeout:
+		g.geth.Fatalf("%v timeout syncing", g.name)
+		break
+	}
+}
+
+func startGethWithRpc(t *testing.T, name string, args ...string) *gethrpc {
+	g := &gethrpc{name: name}
+	args = append([]string{"--networkid=42", "--port=0", "--nousb", "--rpc", "--rpcport=0", "--rpcapi=admin,eth,les"}, args...)
+	t.Logf("Starting %v with rpc: %v", name, args)
+	g.geth = runGeth(t, args...)
+	// wait before we can attach to it. TODO: probe for it properly
+	time.Sleep(1 * time.Second)
+	var err error
+	ipcpath := filepath.Join(g.geth.Datadir, "geth.ipc")
+	g.rpc, err = rpc.Dial(ipcpath)
+	if err != nil {
+		t.Fatalf("%v rpc connect: %v", name, err)
+	}
+	return g
+}
+
+func initGeth(t *testing.T) string {
+	g := runGeth(t, "--networkid=42", "init", "./testdata/clique.json")
+	datadir := g.Datadir
+	g.WaitExit()
+	return datadir
+}
+
+func startLightServer(t *testing.T) *gethrpc {
+	datadir := initGeth(t)
+	runGeth(t, "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit()
+	account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105"
+	server := startGethWithRpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1")
+	return server
+}
+
+func startClient(t *testing.T, name string) *gethrpc {
+	datadir := initGeth(t)
+	return startGethWithRpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1")
+}
+
+func TestPriorityClient(t *testing.T) {
+	lightServer := startLightServer(t)
+	defer lightServer.killAndWait()
+
+	// Start client and add lightServer as peer
+	freeCli := startClient(t, "freeCli")
+	defer freeCli.killAndWait()
+	freeCli.addPeer(lightServer)
+
+	var peers []*p2p.PeerInfo
+	freeCli.callRPC(&peers, "admin_peers")
+	if len(peers) != 1 {
+		t.Errorf("Expected: # of client peers == 1, actual: %v", len(peers))
+		return
+	}
+
+	// Set up priority client, get its nodeID, increase its balance on the lightServer
+	prioCli := startClient(t, "prioCli")
+	defer prioCli.killAndWait()
+	// 3_000_000_000 once we move to Go 1.13
+	tokens := 3000000000
+	lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens, "foobar")
+	prioCli.addPeer(lightServer)
+
+	// Check if priority client is actually syncing and the regular client got kicked out
+	prioCli.callRPC(&peers, "admin_peers")
+	if len(peers) != 1 {
+		t.Errorf("Expected: # of prio peers == 1, actual: %v", len(peers))
+	}
+
+	nodes := map[string]*gethrpc{
+		lightServer.getNodeInfo().ID: lightServer,
+		freeCli.getNodeInfo().ID:     freeCli,
+		prioCli.getNodeInfo().ID:     prioCli,
+	}
+	lightServer.callRPC(&peers, "admin_peers")
+	peersWithNames := make(map[string]string)
+	for _, p := range peers {
+		peersWithNames[nodes[p.ID].name] = p.ID
+	}
+	if _, freeClientFound := peersWithNames[freeCli.name]; freeClientFound {
+		t.Error("client is still a peer of lightServer", peersWithNames)
+	}
+	if _, prioClientFound := peersWithNames[prioCli.name]; !prioClientFound {
+		t.Error("prio client is not among lightServer peers", peersWithNames)
+	}
+}
diff --git a/cmd/geth/testdata/blockchain.blocks b/cmd/geth/testdata/blockchain.blocks
new file mode 100644
index 0000000000000000000000000000000000000000..d29453d3e53b4382cc3ff4254e1931182a6c630d
Binary files /dev/null and b/cmd/geth/testdata/blockchain.blocks differ
diff --git a/cmd/geth/testdata/clique.json b/cmd/geth/testdata/clique.json
new file mode 100644
index 0000000000000000000000000000000000000000..b54b4a7d3b7210f4266d22555b1e0af52d9f19bf
--- /dev/null
+++ b/cmd/geth/testdata/clique.json
@@ -0,0 +1,24 @@
+{
+  "config": {
+    "chainId": 15,
+    "homesteadBlock": 0,
+    "eip150Block": 0,
+    "eip155Block": 0,
+    "eip158Block": 0,
+    "byzantiumBlock": 0,
+    "constantinopleBlock": 0,
+    "petersburgBlock": 0,
+    "clique": {
+      "period": 5,
+      "epoch": 30000
+    }
+  },
+  "difficulty": "1",
+  "gasLimit": "8000000",
+  "extradata": "0x000000000000000000000000000000000000000000000000000000000000000002f0d131f1f97aef08aec6e3291b957d9efe71050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+  "alloc": {
+    "02f0d131f1f97aef08aec6e3291b957d9efe7105": {
+      "balance": "300000"
+    }
+  }
+}
\ No newline at end of file
diff --git a/cmd/geth/testdata/key.prv b/cmd/geth/testdata/key.prv
new file mode 100644
index 0000000000000000000000000000000000000000..1d2687ea63f08d4a3cfc895bcf90188da034eb7b
--- /dev/null
+++ b/cmd/geth/testdata/key.prv
@@ -0,0 +1 @@
+48aa455c373ec5ce7fefb0e54f44a215decdc85b9047bc4d09801e038909bdbe
\ No newline at end of file
diff --git a/cmd/geth/testdata/password.txt b/cmd/geth/testdata/password.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
--- /dev/null
+++ b/cmd/geth/testdata/password.txt
@@ -0,0 +1 @@
+foobar
\ No newline at end of file