Liquid 使用方法

三个节点,测试一下p2p广播功能,点到点消息发送功能和节点发现功能

  1. 场景模拟node1 发送广播,node2和node3 订阅,
  2. node1 点对点 发送数据给node2和node3
  3. node2连接node1 , node3连接node2,node1发现node3 节点

代码分别如下

node1

/*
Copyright (C) BABEC. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
    "chainmaker.org/chainmaker/common/v2/crypto/asym"
    cmTls "chainmaker.org/chainmaker/common/v2/crypto/tls"
    cmx509 "chainmaker.org/chainmaker/common/v2/crypto/x509"
    "chainmaker.org/chainmaker/common/v2/helper"
    "chainmaker.org/chainmaker/net-liquid/core/host"
    "chainmaker.org/chainmaker/net-liquid/core/peer"
    "chainmaker.org/chainmaker/net-liquid/core/protocol"
    "chainmaker.org/chainmaker/net-liquid/core/util"
    "chainmaker.org/chainmaker/net-liquid/discovery/protocoldiscovery"
    nethost "chainmaker.org/chainmaker/net-liquid/host"
    "chainmaker.org/chainmaker/net-liquid/logger"
    "chainmaker.org/chainmaker/net-liquid/pubsub"
    "context"
    "fmt"
    ma "github.com/multiformats/go-multiaddr"
    "strconv"
    "time"
)

const msg = "Hello!My first LIQUID program demo."
const testProtocolID = "/test"
var (
    addrsTcp = []ma.Multiaddr{
        ma.StringCast("/ip4/127.0.0.1/tcp/7081"),
        ma.StringCast("/ip4/127.0.0.1/tcp/7084"),
        ma.StringCast("/ip4/127.0.0.1/tcp/7085"),
    }


    keyPEMs = [][]byte{
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDc16StWEUNF0W7iXrT/k0Niy1ZWKbheI2wkjMqJ3WcaoAoGCCqGSM49\nAwEHoUQDQgAEmung19cBCguCaWeyf/nnARHRISTxtaxxf3Zqn+EQxxeYsXqa8oAg\ncalmJaGcmk9kTc0aJwpi7bKvJQdrRg1vFw==\n-----END EC PRIVATE KEY-----\n"),
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIGLbfX/EvXQtyO+GJvj5rFB0gyTqO3g4gO/gHX2A/BgNoAoGCCqGSM49\nAwEHoUQDQgAE0nVVBXfDQp+EaRbXXfXjn3QG+KYKqApi13+aeNMO7hvS4FlS3B5Z\nMRUCS2oBICwDgLf2q6ef1T5by1u9+IKK7w==\n-----END EC PRIVATE KEY-----\n"),
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIEUG3DFeXHVr5WjTRfpuSou30zEy2DMmzmyxuSd7F7K9oAoGCCqGSM49\nAwEHoUQDQgAEF+Tvi0fMzJOT9DLS+SFKPIo8kb3ouM54dJ9ibbohpR7FC8Iiid4g\nJ1QNguDjxEmwMV7Q1YiAuighKEmvtb4R+Q==\n-----END EC PRIVATE KEY-----"),
    }

    certPEMs = [][]byte{
        []byte("-----BEGIN CERTIFICATE-----\nMIICcjCCAhegAwIBAgIIFPWUuqixxuAwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzU2MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNTYwHhcNMjEx\nMDI4MDgyMzU3WhcNNDExMDIzMDgyMzU3WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNTYx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNTYtbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAASa6eDX1wEKC4JpZ7J/+ecBEdEhJPG1rHF/\ndmqf4RDHF5ixeprygCBxqWYloZyaT2RNzRonCmLtsq8lB2tGDW8Xo4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEIOTdkAJ/Fe7PiOnSFe5Fa95mreAwpX2OiK2sB3+Bd86jMCsGA1UdIwQk\nMCKAIKUCNBAgGIS+Af0ucVsMq9ZGS6S2GLlbcDyrtghnqnAnMBkGA1UdEQQSMBCC\nDmhjLW9yZzU2LW5vZGUxMAoGCCqGSM49BAMCA0kAMEYCIQDH+9IoSsBPE33ZRuog\ng9FBCAVj30uuxB08b+eUjbdDGgIhANZ7yABDBk4z9r+gEYtVdFPZdjkEmPa9dYVG\n1u9tUvFy\n-----END CERTIFICATE-----\n"),
        []byte("-----BEGIN CERTIFICATE-----\nMIICcDCCAhegAwIBAgIIJY/rNMZhQFwwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzU3MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNTcwHhcNMjEx\nMDI4MDgzMDI5WhcNNDExMDIzMDgzMDI5WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNTcx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNTctbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAATSdVUFd8NCn4RpFtdd9eOfdAb4pgqoCmLX\nf5p40w7uG9LgWVLcHlkxFQJLagEgLAOAt/arp5/VPlvLW734gorvo4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEII/jHW2D+EGmFFPJupqBoCubnOwYiEF30VGIZjngZIfwMCsGA1UdIwQk\nMCKAIJ6svKRzGIGG9O0Qh0xEizT54wjh7HivJPiATX5h7ZahMBkGA1UdEQQSMBCC\nDmhjLW9yZzU3LW5vZGUxMAoGCCqGSM49BAMCA0cAMEQCIDES1Cz+VE1uiwSDQYDO\noUD9O51G+a3NMnELPp3GZudUAiBe4jJKk1xPU8jIM6yG5B9q3A7CeASpbbISV0tW\nRDLVYw==\n-----END CERTIFICATE-----\n"),
        []byte("-----BEGIN CERTIFICATE-----\nMIICcjCCAhegAwIBAgIIJFu2YffFkwMwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzY1MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNjUwHhcNMjEx\nMTE1MDUzMjQ5WhcNNDExMTEwMDUzMjQ5WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNjUx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNjUtbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQX5O+LR8zMk5P0MtL5IUo8ijyRvei4znh0\nn2JtuiGlHsULwiKJ3iAnVA2C4OPESbAxXtDViIC6KCEoSa+1vhH5o4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEIGMwbIbdCt+HSL/t2+51nJNAhvH705W8/NnKCj7naO53MCsGA1UdIwQk\nMCKAIIMGhjkBj8VD2+2zAscpecisB5b7dlRdLCJwpce55MSIMBkGA1UdEQQSMBCC\nDmhjLW9yZzY1LW5vZGUxMAoGCCqGSM49BAMCA0kAMEYCIQCSA05P68pGYBCrFoTF\nsPrjx7XN7x1EdFzKb7jnqIc6SQIhAOJYgKkwQQwrXV+yqQJ9XmGRM9I3Ghdvs+Ew\nN4dM580M\n-----END CERTIFICATE-----\n"),
    }

    pidList = []peer.ID{
        "QmVGwL6VXMab7HubwkheZQpJkKKxutqGseJnjg4upWhvDo",
        "Qmav9PUgzrdaLTgJ68XzX9gb4eKLzTonMUpAEsyJRFqV63",
        "QmNQTmmZj8STcDhpKK3izd6q9DT42EuXu7oJyH7Tk1czga",
    }
)


func CreateHostTCP(idx int, seeds map[peer.ID]ma.Multiaddr) (host.Host, error) {
    certPool := cmx509.NewCertPool()
    for i := range certPEMs {
        certPool.AppendCertsFromPEM(certPEMs[i])
    }
    sk, err := asym.PrivateKeyFromPEM(keyPEMs[idx], nil)
    if err != nil {
        return nil, err
    }
    tlsCert, err := cmTls.X509KeyPair(certPEMs[idx], keyPEMs[idx])
    if err != nil {
        return nil, err
    }
    hostCfg := &nethost.HostConfig{
        TlsCfg: &cmTls.Config{
            Certificates:       []cmTls.Certificate{tlsCert},
            InsecureSkipVerify: true,
            ClientAuth:         cmTls.RequireAnyClientCert,
            VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*cmx509.Certificate) error {
                tlsCertBytes := rawCerts[0]
                cert, err := cmx509.ParseCertificate(tlsCertBytes)
                if err != nil {
                    return err
                }
                _, err = cert.Verify(cmx509.VerifyOptions{Roots: certPool})
                if err != nil {
                    return err
                }
                return nil
            },
        },
        LoadPidFunc: func(certificates []*cmx509.Certificate) (peer.ID, error) {
            pid, err := helper.GetLibp2pPeerIdFromCertDer(certificates[0].Raw)
            if err != nil {
                return "", err
            }
            return peer.ID(pid), err
        },
        SendStreamPoolInitSize:    10,
        SendStreamPoolCap:         50,
        PeerReceiveStreamMaxCount: 100,
        ListenAddresses:           []ma.Multiaddr{addrsTcp[idx]},
        DirectPeers:               seeds,
        MsgCompress:               false,
        Insecurity:                false,
        PrivateKey:                sk,
    }

    return hostCfg.NewHost(nethost.TcpNetwork, context.Background(), logger.NewLogPrinter("HOST"+strconv.Itoa(idx)))
}

func TestHostTCP() {
    // create host1 pidList[1]: ma.Join(addr2TargetTcp, ma.StringCast("/p2p/"+pidList[1].ToString()))
    host1, err := CreateHostTCP(0, map[peer.ID]ma.Multiaddr{})
    // register notifee
    notifyBundle := &host.NotifieeBundle{
        PeerConnectedFunc: func(id peer.ID) {
            fmt.Printf("节点已连接,节点ID:%s\n", id)
        },
        PeerDisconnectedFunc: func(id peer.ID) {
            fmt.Printf("节点已断开,节点ID:%s\n", id)
        },
        PeerProtocolSupportedFunc: func(protocolID protocol.ID, pid peer.ID) {
            fmt.Printf("节点%s新支持协议%s\n", pid, protocolID)
        },
        PeerProtocolUnsupportedFunc: func(protocolID protocol.ID, pid peer.ID) {
            fmt.Printf("节点%s取消支持协议%s\n", pid, protocolID)
        },
    }
    host1.Notify(notifyBundle)
    ps := pubsub.NewChainPubSub("c1635410037173", logger.NewLogPrinter("PubSub"))
    err = ps.AttachHost(host1)
    if err != nil {
        // do something
        panic(err)
    }
    // start hosts
    err = host1.Start()

    err = host1.RegisterMsgPayloadHandler(testProtocolID, func(senderPID peer.ID, msgPayload []byte) {
        fmt.Println(string(msgPayload))
    })


    testTopic := "topic"
    go func() {
        i:=0
        for  {
            i++
            // host1 send msg to host2
            time.Sleep(time.Second)
            err = host1.SendMsg(testProtocolID, pidList[1], []byte(msg))
            err = host1.SendMsg(testProtocolID, pidList[2], []byte(msg))
            ps.Publish(testTopic,[]byte("广播的消息"+strconv.Itoa(i)))
        }
    }()
    // 节点发现服务
    // 10. 开启节点发现服务
    log := logger.NewLogPrinter("TEST")
    discoveryService, err := protocoldiscovery.NewProtocolBasedDiscovery(host1, protocoldiscovery.WithLogger(log))
    if err != nil {
        // do something
        panic(err)
    }
    ctx := context.Background()
    // 11. 宣布自己支持的服务
    err = discoveryService.Announce(ctx, testProtocolID)
    if err != nil {
        // do something
        panic(err)
    }

    // 12. 搜寻其他节点
    findC, err := discoveryService.FindPeers(ctx, testProtocolID)
    if err != nil {
        // do something
        panic(err)
    }
    go listenFindingC(ctx,0,host1,findC)
    select {

    }

}

func listenFindingC(ctx context.Context, idx int, h host.Host, c <-chan ma.Multiaddr) {
    for {
        select {
        case <-ctx.Done():
            return
        case ai := <-c:
            fmt.Printf("发现新节点,地址:%s\n", ai.String())
            addr, pid := util.GetNetAddrAndPidFromNormalMultiAddr(ai)
            if pid == "" {
                fmt.Errorf("[Discovery%d] peer id not contains in addr", idx)
                continue
            }
            if h.ConnMgr().PeerCount() >= h.ConnMgr().MaxPeerCountAllowed() || h.ID() == pid || h.ConnMgr().IsConnected(pid) {
                continue
            }
            fmt.Printf("[Discovery%d] find new peer.(pid: %s, addr: %s)", idx, pid, addr.String())
            _, err := h.Dial(ai)
            if err!=nil{
                fmt.Errorf("连接出现错误")
                continue
            }

        }
    }
}

node 2

/*
Copyright (C) BABEC. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
    "chainmaker.org/chainmaker/common/v2/crypto/asym"
    cmTls "chainmaker.org/chainmaker/common/v2/crypto/tls"
    cmx509 "chainmaker.org/chainmaker/common/v2/crypto/x509"
    "chainmaker.org/chainmaker/common/v2/helper"
    "chainmaker.org/chainmaker/net-liquid/core/handler"
    "chainmaker.org/chainmaker/net-liquid/core/host"
    "chainmaker.org/chainmaker/net-liquid/core/peer"
    "chainmaker.org/chainmaker/net-liquid/core/protocol"
    "chainmaker.org/chainmaker/net-liquid/core/util"
    "chainmaker.org/chainmaker/net-liquid/discovery/protocoldiscovery"
    nethost "chainmaker.org/chainmaker/net-liquid/host"
    "chainmaker.org/chainmaker/net-liquid/logger"
    "chainmaker.org/chainmaker/net-liquid/pubsub"
    "context"
    "fmt"
    ma "github.com/multiformats/go-multiaddr"
    "strconv"
)

const msg = "Hello!My first LIQUID program demo."
const testProtocolID = "/test"
var (
    addrsTcp = []ma.Multiaddr{
        ma.StringCast("/ip4/127.0.0.1/tcp/7081"),
        ma.StringCast("/ip4/127.0.0.1/tcp/7084"),
        ma.StringCast("/ip4/127.0.0.1/tcp/7085"),
    }


    keyPEMs = [][]byte{
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDc16StWEUNF0W7iXrT/k0Niy1ZWKbheI2wkjMqJ3WcaoAoGCCqGSM49\nAwEHoUQDQgAEmung19cBCguCaWeyf/nnARHRISTxtaxxf3Zqn+EQxxeYsXqa8oAg\ncalmJaGcmk9kTc0aJwpi7bKvJQdrRg1vFw==\n-----END EC PRIVATE KEY-----\n"),
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIGLbfX/EvXQtyO+GJvj5rFB0gyTqO3g4gO/gHX2A/BgNoAoGCCqGSM49\nAwEHoUQDQgAE0nVVBXfDQp+EaRbXXfXjn3QG+KYKqApi13+aeNMO7hvS4FlS3B5Z\nMRUCS2oBICwDgLf2q6ef1T5by1u9+IKK7w==\n-----END EC PRIVATE KEY-----\n"),
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIEUG3DFeXHVr5WjTRfpuSou30zEy2DMmzmyxuSd7F7K9oAoGCCqGSM49\nAwEHoUQDQgAEF+Tvi0fMzJOT9DLS+SFKPIo8kb3ouM54dJ9ibbohpR7FC8Iiid4g\nJ1QNguDjxEmwMV7Q1YiAuighKEmvtb4R+Q==\n-----END EC PRIVATE KEY-----"),
    }

    certPEMs = [][]byte{
        []byte("-----BEGIN CERTIFICATE-----\nMIICcjCCAhegAwIBAgIIFPWUuqixxuAwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzU2MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNTYwHhcNMjEx\nMDI4MDgyMzU3WhcNNDExMDIzMDgyMzU3WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNTYx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNTYtbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAASa6eDX1wEKC4JpZ7J/+ecBEdEhJPG1rHF/\ndmqf4RDHF5ixeprygCBxqWYloZyaT2RNzRonCmLtsq8lB2tGDW8Xo4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEIOTdkAJ/Fe7PiOnSFe5Fa95mreAwpX2OiK2sB3+Bd86jMCsGA1UdIwQk\nMCKAIKUCNBAgGIS+Af0ucVsMq9ZGS6S2GLlbcDyrtghnqnAnMBkGA1UdEQQSMBCC\nDmhjLW9yZzU2LW5vZGUxMAoGCCqGSM49BAMCA0kAMEYCIQDH+9IoSsBPE33ZRuog\ng9FBCAVj30uuxB08b+eUjbdDGgIhANZ7yABDBk4z9r+gEYtVdFPZdjkEmPa9dYVG\n1u9tUvFy\n-----END CERTIFICATE-----\n"),
        []byte("-----BEGIN CERTIFICATE-----\nMIICcDCCAhegAwIBAgIIJY/rNMZhQFwwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzU3MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNTcwHhcNMjEx\nMDI4MDgzMDI5WhcNNDExMDIzMDgzMDI5WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNTcx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNTctbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAATSdVUFd8NCn4RpFtdd9eOfdAb4pgqoCmLX\nf5p40w7uG9LgWVLcHlkxFQJLagEgLAOAt/arp5/VPlvLW734gorvo4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEII/jHW2D+EGmFFPJupqBoCubnOwYiEF30VGIZjngZIfwMCsGA1UdIwQk\nMCKAIJ6svKRzGIGG9O0Qh0xEizT54wjh7HivJPiATX5h7ZahMBkGA1UdEQQSMBCC\nDmhjLW9yZzU3LW5vZGUxMAoGCCqGSM49BAMCA0cAMEQCIDES1Cz+VE1uiwSDQYDO\noUD9O51G+a3NMnELPp3GZudUAiBe4jJKk1xPU8jIM6yG5B9q3A7CeASpbbISV0tW\nRDLVYw==\n-----END CERTIFICATE-----\n"),
        []byte("-----BEGIN CERTIFICATE-----\nMIICcjCCAhegAwIBAgIIJFu2YffFkwMwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzY1MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNjUwHhcNMjEx\nMTE1MDUzMjQ5WhcNNDExMTEwMDUzMjQ5WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNjUx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNjUtbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQX5O+LR8zMk5P0MtL5IUo8ijyRvei4znh0\nn2JtuiGlHsULwiKJ3iAnVA2C4OPESbAxXtDViIC6KCEoSa+1vhH5o4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEIGMwbIbdCt+HSL/t2+51nJNAhvH705W8/NnKCj7naO53MCsGA1UdIwQk\nMCKAIIMGhjkBj8VD2+2zAscpecisB5b7dlRdLCJwpce55MSIMBkGA1UdEQQSMBCC\nDmhjLW9yZzY1LW5vZGUxMAoGCCqGSM49BAMCA0kAMEYCIQCSA05P68pGYBCrFoTF\nsPrjx7XN7x1EdFzKb7jnqIc6SQIhAOJYgKkwQQwrXV+yqQJ9XmGRM9I3Ghdvs+Ew\nN4dM580M\n-----END CERTIFICATE-----\n"),
    }

    pidList = []peer.ID{
        "QmVGwL6VXMab7HubwkheZQpJkKKxutqGseJnjg4upWhvDo",
        "Qmav9PUgzrdaLTgJ68XzX9gb4eKLzTonMUpAEsyJRFqV63",
        "QmNQTmmZj8STcDhpKK3izd6q9DT42EuXu7oJyH7Tk1czga",
    }
)

func CreateHostTCP(idx int, seeds map[peer.ID]ma.Multiaddr) (host.Host, error) {
    certPool := cmx509.NewCertPool()
    for i := range certPEMs {
        certPool.AppendCertsFromPEM(certPEMs[i])
    }
    sk, err := asym.PrivateKeyFromPEM(keyPEMs[idx], nil)
    if err != nil {
        return nil, err
    }
    tlsCert, err := cmTls.X509KeyPair(certPEMs[idx], keyPEMs[idx])
    if err != nil {
        return nil, err
    }
    hostCfg := &nethost.HostConfig{
        TlsCfg: &cmTls.Config{
            Certificates:       []cmTls.Certificate{tlsCert},
            InsecureSkipVerify: true,
            ClientAuth:         cmTls.RequireAnyClientCert,
            VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*cmx509.Certificate) error {
                tlsCertBytes := rawCerts[0]
                cert, err := cmx509.ParseCertificate(tlsCertBytes)
                if err != nil {
                    return err
                }
                _, err = cert.Verify(cmx509.VerifyOptions{Roots: certPool})
                if err != nil {
                    return err
                }
                return nil
            },
        },
        LoadPidFunc: func(certificates []*cmx509.Certificate) (peer.ID, error) {
            pid, err := helper.GetLibp2pPeerIdFromCertDer(certificates[0].Raw)
            if err != nil {
                return "", err
            }
            return peer.ID(pid), err
        },
        SendStreamPoolInitSize:    10,
        SendStreamPoolCap:         50,
        PeerReceiveStreamMaxCount: 100,
        ListenAddresses:           []ma.Multiaddr{addrsTcp[idx]},
        DirectPeers:               seeds,
        MsgCompress:               false,
        Insecurity:                false,
        PrivateKey:                sk,
    }
    return hostCfg.NewHost(nethost.TcpNetwork, context.Background(), logger.NewLogPrinter("HOST"+strconv.Itoa(idx)))
}
func TestHostTCP() {
    // create host2 pidList[0]: ma.Join(addrsTcp[0], ma.StringCast("/p2p/"+pidList[0].ToString()))
    host1, _ := CreateHostTCP(1, map[peer.ID]ma.Multiaddr{pidList[0]: ma.Join(addrsTcp[0], ma.StringCast("/p2p/"+pidList[0].ToString()))})

    notifyBundle := &host.NotifieeBundle{
        PeerConnectedFunc: func(id peer.ID) {
            fmt.Printf("节点已连接,节点ID:%s\n", id)
        },
        PeerDisconnectedFunc: func(id peer.ID) {
            fmt.Printf("节点已断开,节点ID:%s\n", id)
        },
        PeerProtocolSupportedFunc: func(protocolID protocol.ID, pid peer.ID) {
            fmt.Printf("节点%s新支持协议%s\n", pid, protocolID)
        },
        PeerProtocolUnsupportedFunc: func(protocolID protocol.ID, pid peer.ID) {
            fmt.Printf("节点%s取消支持协议%s\n", pid, protocolID)
        },
    }
    host1.Notify(notifyBundle)

    ps := pubsub.NewChainPubSub("c1635410037173", logger.NewLogPrinter("PubSub"))
    err := ps.AttachHost(host1)
    if err != nil {
        // do something
        panic(err)
    }
    testTopic := "topic"
    var subHandler handler.SubMsgHandler = func(publisher peer.ID, topic string, msg []byte) {
        fmt.Printf("收到节点%s向频道%s发布的消息:%s\n", publisher, topic, string(msg))
    }
    ps.Subscribe(testTopic, subHandler)
    // start hosts
    _ = host1.Start()
    // wait for connection established between host1 and host2

    // register msg payload handler
    _ = host1.RegisterMsgPayloadHandler(testProtocolID, func(senderPID peer.ID, msgPayload []byte) {
        fmt.Println("收到Message"+string(msgPayload))
    })

    // 节点发现服务
    // 10. 开启节点发现服务
    log := logger.NewLogPrinter("TEST")
    discoveryService, err := protocoldiscovery.NewProtocolBasedDiscovery(host1, protocoldiscovery.WithLogger(log))
    if err != nil {
        // do something
        panic(err)
    }
    ctx := context.Background()
    // 11. 宣布自己支持的服务
    err = discoveryService.Announce(ctx, testProtocolID)
    if err != nil {
        // do something
        panic(err)
    }

    // 12. 搜寻其他节点
    findC, err := discoveryService.FindPeers(ctx, testProtocolID)
    if err != nil {
        // do something
        panic(err)
    }
    go listenFindingC(ctx,1,host1,findC)
    select {

    }

}

func listenFindingC(ctx context.Context, idx int, h host.Host, c <-chan ma.Multiaddr) {
    for {
        select {
        case <-ctx.Done():
            return
        case ai := <-c:
            fmt.Printf("发现新节点,地址:%s\n", ai.String())
            addr, pid := util.GetNetAddrAndPidFromNormalMultiAddr(ai)
            if pid == "" {
                fmt.Errorf("[Discovery%d] peer id not contains in addr", idx)
                continue
            }
            if h.ConnMgr().PeerCount() >= h.ConnMgr().MaxPeerCountAllowed() || h.ID() == pid || h.ConnMgr().IsConnected(pid) {
                continue
            }
            fmt.Printf("[Discovery%d] find new peer.(pid: %s, addr: %s)", idx, pid, addr.String())
            _, _ = h.Dial(ai)
        }
    }
}

node3

/*
Copyright (C) BABEC. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
    "chainmaker.org/chainmaker/common/v2/crypto/asym"
    cmTls "chainmaker.org/chainmaker/common/v2/crypto/tls"
    cmx509 "chainmaker.org/chainmaker/common/v2/crypto/x509"
    "chainmaker.org/chainmaker/common/v2/helper"
    "chainmaker.org/chainmaker/net-liquid/core/handler"
    "chainmaker.org/chainmaker/net-liquid/core/host"
    "chainmaker.org/chainmaker/net-liquid/core/peer"
    "chainmaker.org/chainmaker/net-liquid/core/protocol"
    "chainmaker.org/chainmaker/net-liquid/core/util"
    "chainmaker.org/chainmaker/net-liquid/discovery/protocoldiscovery"
    nethost "chainmaker.org/chainmaker/net-liquid/host"
    "chainmaker.org/chainmaker/net-liquid/logger"
    "chainmaker.org/chainmaker/net-liquid/pubsub"
    "context"
    "fmt"
    ma "github.com/multiformats/go-multiaddr"
    "strconv"
)

const msg = "Hello!My first LIQUID program demo."
const testProtocolID = "/test"
var (
    addrsTcp = []ma.Multiaddr{
        ma.StringCast("/ip4/127.0.0.1/tcp/7081"),
        ma.StringCast("/ip4/127.0.0.1/tcp/7084"),
        ma.StringCast("/ip4/127.0.0.1/tcp/7085"),
    }


    keyPEMs = [][]byte{
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDc16StWEUNF0W7iXrT/k0Niy1ZWKbheI2wkjMqJ3WcaoAoGCCqGSM49\nAwEHoUQDQgAEmung19cBCguCaWeyf/nnARHRISTxtaxxf3Zqn+EQxxeYsXqa8oAg\ncalmJaGcmk9kTc0aJwpi7bKvJQdrRg1vFw==\n-----END EC PRIVATE KEY-----\n"),
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIGLbfX/EvXQtyO+GJvj5rFB0gyTqO3g4gO/gHX2A/BgNoAoGCCqGSM49\nAwEHoUQDQgAE0nVVBXfDQp+EaRbXXfXjn3QG+KYKqApi13+aeNMO7hvS4FlS3B5Z\nMRUCS2oBICwDgLf2q6ef1T5by1u9+IKK7w==\n-----END EC PRIVATE KEY-----\n"),
        []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIEUG3DFeXHVr5WjTRfpuSou30zEy2DMmzmyxuSd7F7K9oAoGCCqGSM49\nAwEHoUQDQgAEF+Tvi0fMzJOT9DLS+SFKPIo8kb3ouM54dJ9ibbohpR7FC8Iiid4g\nJ1QNguDjxEmwMV7Q1YiAuighKEmvtb4R+Q==\n-----END EC PRIVATE KEY-----"),
    }

    certPEMs = [][]byte{
        []byte("-----BEGIN CERTIFICATE-----\nMIICcjCCAhegAwIBAgIIFPWUuqixxuAwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzU2MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNTYwHhcNMjEx\nMDI4MDgyMzU3WhcNNDExMDIzMDgyMzU3WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNTYx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNTYtbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAASa6eDX1wEKC4JpZ7J/+ecBEdEhJPG1rHF/\ndmqf4RDHF5ixeprygCBxqWYloZyaT2RNzRonCmLtsq8lB2tGDW8Xo4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEIOTdkAJ/Fe7PiOnSFe5Fa95mreAwpX2OiK2sB3+Bd86jMCsGA1UdIwQk\nMCKAIKUCNBAgGIS+Af0ucVsMq9ZGS6S2GLlbcDyrtghnqnAnMBkGA1UdEQQSMBCC\nDmhjLW9yZzU2LW5vZGUxMAoGCCqGSM49BAMCA0kAMEYCIQDH+9IoSsBPE33ZRuog\ng9FBCAVj30uuxB08b+eUjbdDGgIhANZ7yABDBk4z9r+gEYtVdFPZdjkEmPa9dYVG\n1u9tUvFy\n-----END CERTIFICATE-----\n"),
        []byte("-----BEGIN CERTIFICATE-----\nMIICcDCCAhegAwIBAgIIJY/rNMZhQFwwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzU3MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNTcwHhcNMjEx\nMDI4MDgzMDI5WhcNNDExMDIzMDgzMDI5WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNTcx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNTctbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAATSdVUFd8NCn4RpFtdd9eOfdAb4pgqoCmLX\nf5p40w7uG9LgWVLcHlkxFQJLagEgLAOAt/arp5/VPlvLW734gorvo4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEII/jHW2D+EGmFFPJupqBoCubnOwYiEF30VGIZjngZIfwMCsGA1UdIwQk\nMCKAIJ6svKRzGIGG9O0Qh0xEizT54wjh7HivJPiATX5h7ZahMBkGA1UdEQQSMBCC\nDmhjLW9yZzU3LW5vZGUxMAoGCCqGSM49BAMCA0cAMEQCIDES1Cz+VE1uiwSDQYDO\noUD9O51G+a3NMnELPp3GZudUAiBe4jJKk1xPU8jIM6yG5B9q3A7CeASpbbISV0tW\nRDLVYw==\n-----END CERTIFICATE-----\n"),
        []byte("-----BEGIN CERTIFICATE-----\nMIICcjCCAhegAwIBAgIIJFu2YffFkwMwCgYIKoZIzj0EAwIwZDELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxETAPBgNVBAoT\nCGhjLW9yZzY1MQswCQYDVQQLEwJjYTERMA8GA1UEAxMIaGMtb3JnNjUwHhcNMjEx\nMTE1MDUzMjQ5WhcNNDExMTEwMDUzMjQ5WjBxMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzERMA8GA1UEChMIaGMtb3JnNjUx\nEjAQBgNVBAsTCWNvbnNlbnN1czEXMBUGA1UEAxMOaGMtb3JnNjUtbm9kZTEwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQX5O+LR8zMk5P0MtL5IUo8ijyRvei4znh0\nn2JtuiGlHsULwiKJ3iAnVA2C4OPESbAxXtDViIC6KCEoSa+1vhH5o4GlMIGiMA4G\nA1UdDwEB/wQEAwID+DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKQYD\nVR0OBCIEIGMwbIbdCt+HSL/t2+51nJNAhvH705W8/NnKCj7naO53MCsGA1UdIwQk\nMCKAIIMGhjkBj8VD2+2zAscpecisB5b7dlRdLCJwpce55MSIMBkGA1UdEQQSMBCC\nDmhjLW9yZzY1LW5vZGUxMAoGCCqGSM49BAMCA0kAMEYCIQCSA05P68pGYBCrFoTF\nsPrjx7XN7x1EdFzKb7jnqIc6SQIhAOJYgKkwQQwrXV+yqQJ9XmGRM9I3Ghdvs+Ew\nN4dM580M\n-----END CERTIFICATE-----\n"),
    }

    pidList = []peer.ID{
        "QmVGwL6VXMab7HubwkheZQpJkKKxutqGseJnjg4upWhvDo",
        "Qmav9PUgzrdaLTgJ68XzX9gb4eKLzTonMUpAEsyJRFqV63",
        "QmNQTmmZj8STcDhpKK3izd6q9DT42EuXu7oJyH7Tk1czga",
    }
)

func CreateHostTCP(idx int, seeds map[peer.ID]ma.Multiaddr) (host.Host, error) {
    certPool := cmx509.NewCertPool()
    for i := range certPEMs {
        certPool.AppendCertsFromPEM(certPEMs[i])
    }
    sk, err := asym.PrivateKeyFromPEM(keyPEMs[idx], nil)
    if err != nil {
        return nil, err
    }
    tlsCert, err := cmTls.X509KeyPair(certPEMs[idx], keyPEMs[idx])
    if err != nil {
        return nil, err
    }
    hostCfg := &nethost.HostConfig{
        TlsCfg: &cmTls.Config{
            Certificates:       []cmTls.Certificate{tlsCert},
            InsecureSkipVerify: true,
            ClientAuth:         cmTls.RequireAnyClientCert,
            VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*cmx509.Certificate) error {
                tlsCertBytes := rawCerts[0]
                cert, err := cmx509.ParseCertificate(tlsCertBytes)
                if err != nil {
                    return err
                }
                _, err = cert.Verify(cmx509.VerifyOptions{Roots: certPool})
                if err != nil {
                    return err
                }
                return nil
            },
        },
        LoadPidFunc: func(certificates []*cmx509.Certificate) (peer.ID, error) {
            pid, err := helper.GetLibp2pPeerIdFromCertDer(certificates[0].Raw)
            if err != nil {
                return "", err
            }
            return peer.ID(pid), err
        },
        SendStreamPoolInitSize:    10,
        SendStreamPoolCap:         50,
        PeerReceiveStreamMaxCount: 100,
        ListenAddresses:           []ma.Multiaddr{addrsTcp[idx]},
        DirectPeers:               seeds,
        MsgCompress:               false,
        Insecurity:                false,
        PrivateKey:                sk,
    }
    return hostCfg.NewHost(nethost.TcpNetwork, context.Background(), logger.NewLogPrinter("HOST"+strconv.Itoa(idx)))
}
func TestHostTCP() {
    // create host2 pidList[0]: ma.Join(addrsTcp[0], ma.StringCast("/p2p/"+pidList[0].ToString()))
    host1, _ := CreateHostTCP(2, map[peer.ID]ma.Multiaddr{pidList[1]: ma.Join(addrsTcp[1], ma.StringCast("/p2p/"+pidList[1].ToString()))})

    notifyBundle := &host.NotifieeBundle{
        PeerConnectedFunc: func(id peer.ID) {
            fmt.Printf("节点已连接,节点ID:%s\n", id)
        },
        PeerDisconnectedFunc: func(id peer.ID) {
            fmt.Printf("节点已断开,节点ID:%s\n", id)
        },
        PeerProtocolSupportedFunc: func(protocolID protocol.ID, pid peer.ID) {
            fmt.Printf("节点%s新支持协议%s\n", pid, protocolID)
        },
        PeerProtocolUnsupportedFunc: func(protocolID protocol.ID, pid peer.ID) {
            fmt.Printf("节点%s取消支持协议%s\n", pid, protocolID)
        },
    }
    host1.Notify(notifyBundle)

    ps := pubsub.NewChainPubSub("c1635410037173", logger.NewLogPrinter("PubSub"))
    err := ps.AttachHost(host1)
    if err != nil {
        // do something
        panic(err)
    }
    testTopic := "topic"
    var subHandler handler.SubMsgHandler = func(publisher peer.ID, topic string, msg []byte) {
        fmt.Printf("收到节点%s向频道%s发布的消息:%s\n", publisher, topic, string(msg))
    }
    ps.Subscribe(testTopic, subHandler)
    // start hosts
    _ = host1.Start()
    // wait for connection established between host1 and host2

    // register msg payload handler
    _ = host1.RegisterMsgPayloadHandler(testProtocolID, func(senderPID peer.ID, msgPayload []byte) {
        fmt.Println("收到Message"+string(msgPayload))
    })

    // 节点发现服务
    // 10. 开启节点发现服务
    log := logger.NewLogPrinter("TEST")
    discoveryService, err := protocoldiscovery.NewProtocolBasedDiscovery(host1, protocoldiscovery.WithLogger(log))
    if err != nil {
        // do something
        panic(err)
    }
    ctx := context.Background()
    // 11. 宣布自己支持的服务
    err = discoveryService.Announce(ctx, testProtocolID)
    if err != nil {
        // do something
        panic(err)
    }

    // 12. 搜寻其他节点
    findC, err := discoveryService.FindPeers(ctx, testProtocolID)
    if err != nil {
        // do something
        panic(err)
    }
    go listenFindingC(ctx,2,host1,findC)
    select {

    }

}

func listenFindingC(ctx context.Context, idx int, h host.Host, c <-chan ma.Multiaddr) {
    for {
        select {
        case <-ctx.Done():
            return
        case ai := <-c:
            fmt.Printf("发现新节点,地址:%s\n", ai.String())
            addr, pid := util.GetNetAddrAndPidFromNormalMultiAddr(ai)
            if pid == "" {
                fmt.Errorf("[Discovery%d] peer id not contains in addr", idx)
                continue
            }
            if h.ConnMgr().PeerCount() >= h.ConnMgr().MaxPeerCountAllowed() || h.ID() == pid || h.ConnMgr().IsConnected(pid) {
                continue
            }
            fmt.Printf("[Discovery%d] find new peer.(pid: %s, addr: %s)", idx, pid, addr.String())
            _, _ = h.Dial(ai)
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,182评论 6 543
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,489评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,290评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,776评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,510评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,866评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,860评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,036评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,585评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,331评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,536评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,058评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,754评论 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,154评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,469评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,273评论 3 399
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,505评论 2 379

推荐阅读更多精彩内容