From faeb97e75f3c035c6bd6dbfdb185386356a4024e Mon Sep 17 00:00:00 2001 From: Minh Huy Tran Date: Tue, 4 Jun 2024 11:40:50 +0200 Subject: [PATCH 1/3] feat(account): add new APIs for interaction with server's AddressBook Signed-off-by: Minh Huy Tran --- README.md | 39 +++++---- p2p/account.go | 166 +++++++++++++++++++++++++++++++++----- p2p/account_test.go | 190 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index dfb755a..162adca 100644 --- a/README.md +++ b/README.md @@ -39,22 +39,14 @@ Example usage: The `Dialer` requires the other peers to be already "registered" to connect with them. Before dialing, the `Register` must be called. Example (register peer before propose a channel with them): -```go +````go // OpenChannel opens a new channel with the specified peer and funding. func (c *PaymentClient) OpenChannel(peer wire.Address, peerID string, amount float64) *PaymentChannel { - // We define the channel participants. The proposer has always index 0. Here - // we use the on-chain addresses as off-chain addresses, but we could also - // use different ones. - participants := []wire.Address{c.waddress, peer} - + .... + ``` c.net.Dialer.Register(peer, peerID) - - // We create an initial allocation which defines the starting balances. - initAlloc := channel.NewAllocation(2, c.currency) - initAlloc.SetAssetBalances(c.currency, []channel.Bal{ - EthToWei(big.NewFloat(amount)), // Our initial balance. - big.NewInt(0), // Peer's initial balance. - }) + ``` + ... // Prepare the channel proposal by defining the channel parameters. challengeDuration := uint64(10) // On-chain challenge duration in seconds. @@ -79,7 +71,26 @@ func (c *PaymentClient) OpenChannel(peer wire.Address, peerID string, amount flo return newPaymentChannel(ch, c.currency) } -``` +```` + +## Address Ressolver +A default address resolver was already built-in on the Perun-Relay-Server. You can use the provided APIs in order to `Register`, `Query`, `Deregister` your On-chain (L1) Address (Implementation of [Go-Perun] `wallet.Address`) to get the peer's `wire.Address` (`Peer.ID` of [Go-Libp2p]) + +**Example:** +````go +// Should be used in the initialization of Perun-Client. +err := acc.RegisterOnChainAddress(onChainAddr) + + +// Query the peer's wire address, given its on-chain address. +peerID, err := acc.QueryOnChainAddress(peerOnChainAddr) + + +// Deregister the on-chain address, to be used before closing Perun-Client, +err = acc.DeregisterOnChainAddress(onChainAddr) + +```` + ## Test Some unit tests are provided: ``` diff --git a/p2p/account.go b/p2p/account.go index ef04e21..6378e90 100644 --- a/p2p/account.go +++ b/p2p/account.go @@ -1,8 +1,11 @@ package p2p import ( + "bufio" "context" "crypto/sha256" + "encoding/json" + "fmt" "math/rand" "net" @@ -12,10 +15,17 @@ import ( "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" "github.com/pkg/errors" + "perun.network/go-perun/wallet" "perun.network/go-perun/wire" ) -const relayID = "QmVCPfUMr98PaaM8qbAQBgJ9jqc7XHpGp7AsyragdFDmgm" +const ( + relayID = "QmVCPfUMr98PaaM8qbAQBgJ9jqc7XHpGp7AsyragdFDmgm" + + queryProtocol = "/address-book/query/1.0.0" // Protocol for querying the relay-server for a peerID. + registerProtocol = "/address-book/register/1.0.0" // Protocol for registering an on-chain address with the relay-server. + removeProtocol = "/address-book/remove/1.0.0" // Protocol for deregistering an on-chain address with the relay-server. +) // Account represents a libp2p wire account. type Account struct { @@ -47,27 +57,9 @@ func (acc *Account) Sign(data []byte) ([]byte, error) { // NewRandomAccount generates a new random account. func NewRandomAccount(rng *rand.Rand) *Account { - id, err := peer.Decode(relayID) - if err != nil { - err = errors.WithMessage(err, "decoding peer id of relay server") - return nil - } - - // Get the IP address of the relay server. - ip, err := net.LookupIP("relay.perun.network") + relayInfo, relayAddr, err := getRelayServerInfo() if err != nil { - panic(errors.WithMessage(err, "looking up IP address of relay.perun.network")) - } - relayAddr := "/ip4/" + ip[0].String() + "/tcp/5574" - - relayMultiaddr, err := ma.NewMultiaddr(relayAddr) - if err != nil { - panic(errors.WithMessage(err, "parsing relay multiadress")) - } - - relayInfo := peer.AddrInfo{ - ID: id, - Addrs: []ma.Multiaddr{relayMultiaddr}, + panic(err) } // Creates a new RSA key pair for this host. @@ -88,8 +80,138 @@ func NewRandomAccount(rng *rand.Rand) *Account { panic(err) } - if err := client.Connect(context.Background(), relayInfo); err != nil { + if err := client.Connect(context.Background(), *relayInfo); err != nil { panic(errors.WithMessage(err, "connecting to the relay server")) } return &Account{client, relayAddr, prvKey} } + +// RegisterOnChainAddress registers an on-chain address with the account to the relay-server's address book. +func (acc *Account) RegisterOnChainAddress(onChainAddr wallet.Address) error { + id, err := peer.Decode(relayID) + if err != nil { + err = errors.WithMessage(err, "decoding peer id of relay server") + return err + } + + s, err := acc.NewStream(context.Background(), id, registerProtocol) + if err != nil { + return errors.WithMessage(err, "creating new stream") + } + defer s.Close() + + var registerData struct { + OnChainAddress string + PeerID string + } + if onChainAddr == nil { + return errors.New("on-chain address is nil") + } + registerData.OnChainAddress = onChainAddr.String() + registerData.PeerID = acc.ID().String() + + data, err := json.Marshal(registerData) + if err != nil { + return errors.WithMessage(err, "marshalling register data") + } + + _, err = s.Write(data) + if err != nil { + return errors.WithMessage(err, "writing register data") + } + + return nil +} + +// DeregisterOnChainAddress deregisters an on-chain address with the account from the relay-server's address book. +func (acc *Account) DeregisterOnChainAddress(onChainAddr wallet.Address) error { + relayInfo, _, err := getRelayServerInfo() + if err != nil { + return errors.WithMessage(err, "getting relay server info") + } + + s, err := acc.NewStream(context.Background(), relayInfo.ID, removeProtocol) + if err != nil { + return errors.WithMessage(err, "creating new stream") + } + defer s.Close() + + var unregisterData struct { + OnChainAddress string + PeerID string + } + unregisterData.OnChainAddress = onChainAddr.String() + unregisterData.PeerID = acc.ID().String() + + data, err := json.Marshal(unregisterData) + if err != nil { + return errors.WithMessage(err, "marshalling register data") + } + + _, err = s.Write(data) + if err != nil { + return errors.WithMessage(err, "writing register data") + } + + return nil +} + +// QueryOnChainAddress queries the relay-server for the peerID of a peer given its on-chain address. +func (acc *Account) QueryOnChainAddress(onChainAddr wallet.Address) (*Address, error) { + id, err := peer.Decode(relayID) + if err != nil { + err = errors.WithMessage(err, "decoding peer id of relay server") + return nil, err + } + + s, err := acc.NewStream(context.Background(), id, queryProtocol) + if err != nil { + return nil, errors.WithMessage(err, "creating new stream") + } + defer s.Close() + + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + rw.WriteString(fmt.Sprintf("%s\n", onChainAddr)) + rw.Flush() + + str, _ := rw.ReadString('\n') + if str == "" { + return nil, errors.New("empty response from relay server") + } + peerIDstr := str[:len(str)-1] + peerID, err := peer.Decode(peerIDstr) + if err != nil { + return nil, errors.WithMessage(err, "decoding peer id") + } + + return &Address{peerID}, nil +} + +func getRelayServerInfo() (*peer.AddrInfo, string, error) { + id, err := peer.Decode(relayID) + if err != nil { + err = errors.WithMessage(err, "decoding peer id of relay server") + return nil, "", err + } + + // Get the IP address of the relay server. + ip, err := net.LookupIP("relay.perun.network") + if err != nil { + err = errors.WithMessage(err, "looking up IP address of relay.perun.network") + return nil, "", err + } + relayAddr := "/ip4/" + ip[0].String() + "/tcp/5574" + + relayMultiaddr, err := ma.NewMultiaddr(relayAddr) + if err != nil { + err = errors.WithMessage(err, "parsing relay multiadress") + return nil, "", err + } + + relayInfo := &peer.AddrInfo{ + ID: id, + Addrs: []ma.Multiaddr{relayMultiaddr}, + } + + return relayInfo, relayAddr, nil +} diff --git a/p2p/account_test.go b/p2p/account_test.go index 83985fa..5bfb4fd 100644 --- a/p2p/account_test.go +++ b/p2p/account_test.go @@ -2,8 +2,11 @@ package p2p import ( "testing" + "time" "github.com/stretchr/testify/assert" + sim_wallet "perun.network/go-perun/backend/sim/wallet" + "perun.network/go-perun/wallet" pkgtest "polycry.pt/poly-go/test" ) @@ -19,3 +22,190 @@ func getHost(t *testing.T) *Account { assert.NotNil(t, acc) return acc } + +func TestAddressBookRegister(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + onChainAddr := sim_wallet.NewRandomAddress(rng) + + err := acc.RegisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) +} + +func TestAddressBookRegisterEmptyAddress(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + emptyAddr := &sim_wallet.Address{} + + assert.Panics(t, func() { acc.RegisterOnChainAddress(emptyAddr) }) + + var nilAddr wallet.Address + err := acc.RegisterOnChainAddress(nilAddr) + assert.Error(t, err) +} + +func TestAddressBookDeregister(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + onChainAddr := sim_wallet.NewRandomAddress(rng) + + err := acc.RegisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + err = acc.DeregisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) + + // Trying to query it again will fail + _, err = acc.QueryOnChainAddress(onChainAddr) + assert.Error(t, err) +} + +func TestAddressBookDeregisterPeer(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + peer := NewRandomAccount(rng) + assert.NotNil(t, peer) + onChainAddr := sim_wallet.NewRandomAddress(rng) + peerOnChainAddr := sim_wallet.NewRandomAddress(rng) + + err := acc.RegisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + time.Sleep(1 * time.Millisecond) + + err = peer.RegisterOnChainAddress(peerOnChainAddr) + assert.NoError(t, err) + + err = acc.DeregisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + // Trying to deregister the peer's address will not fail, but the server will not allow it. + err = acc.DeregisterOnChainAddress(peerOnChainAddr) + assert.NoError(t, err) + + // Trying to query it again will be okay + peerID, err := acc.QueryOnChainAddress(peerOnChainAddr) + assert.NoError(t, err) + + addr := peer.Address() + assert.Equal(t, peerID, addr) + + err = peer.DeregisterOnChainAddress(peerOnChainAddr) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) +} + +func TestAddressBookQuery_Fail(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + onChainAddr := sim_wallet.NewRandomAddress(rng) + + _, err := acc.QueryOnChainAddress(onChainAddr) + assert.Error(t, err) +} + +func TestAddressBookQuery(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + onChainAddr := sim_wallet.NewRandomAddress(rng) + + err := acc.RegisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + time.Sleep(10 * time.Millisecond) + peerID, err := acc.QueryOnChainAddress(onChainAddr) + assert.NoError(t, err) + + addr := acc.Address() + assert.Equal(t, peerID, addr) + + err = acc.DeregisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) +} + +func TestAddressBookQueryPeer(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + peer := NewRandomAccount(rng) + assert.NotNil(t, peer) + onChainAddr := sim_wallet.NewRandomAddress(rng) + peerOnChainAddr := sim_wallet.NewRandomAddress(rng) + + err := acc.RegisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + err = peer.RegisterOnChainAddress(peerOnChainAddr) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) + peerID, err := acc.QueryOnChainAddress(peerOnChainAddr) + assert.NoError(t, err) + + addr := peer.Address() + assert.Equal(t, peerID, addr) + + err = acc.DeregisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + err = acc.DeregisterOnChainAddress(peerOnChainAddr) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) +} + +func TestAddressBookRegisterQueryMultiple(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + onChainAddr := sim_wallet.NewRandomAddress(rng) + onChainAddr2 := sim_wallet.NewRandomAddress(rng) + + err := acc.RegisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + err = acc.RegisterOnChainAddress(onChainAddr2) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) + + accID, err := acc.QueryOnChainAddress(onChainAddr) + assert.NoError(t, err) + + accID2, err := acc.QueryOnChainAddress(onChainAddr2) + assert.NoError(t, err) + + addr := acc.Address() + assert.Equal(t, accID, addr) + assert.Equal(t, accID2, addr) + + // Clean up + err = acc.DeregisterOnChainAddress(onChainAddr) + assert.NoError(t, err) + + err = acc.DeregisterOnChainAddress(onChainAddr2) + assert.NoError(t, err) + + time.Sleep(1 * time.Second) +} From 02197591e948682e881406e75165060996aff88d Mon Sep 17 00:00:00 2001 From: Minh Huy Tran Date: Tue, 25 Jun 2024 12:23:24 +0200 Subject: [PATCH 2/3] add methods to marshal/unmarshal private account's private key Signed-off-by: Minh Huy Tran --- p2p/account.go | 42 ++++++++++++++++++++++++++++++++++++++++++ p2p/account_test.go | 15 +++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/p2p/account.go b/p2p/account.go index 6378e90..292f8d9 100644 --- a/p2p/account.go +++ b/p2p/account.go @@ -55,6 +55,41 @@ func (acc *Account) Sign(data []byte) ([]byte, error) { } +// MarshalPrivateKey marshals the account's private key to binary. +func (acc *Account) MarshalPrivateKey() ([]byte, error) { + return crypto.MarshalPrivateKey(acc.privateKey) +} + +// NewAccountFromPrivateKeyBytes creates a new account from a given private key. +func NewAccountFromPrivateKeyBytes(prvKeyBytes []byte) (*Account, error) { + prvKey, err := crypto.UnmarshalPrivateKey(prvKeyBytes) + if err != nil { + return nil, errors.WithMessage(err, "unmarshalling private key") + } + + relayInfo, relayAddr, err := getRelayServerInfo() + if err != nil { + panic(err) + } + // Construct a new libp2p client for our relay-server. + // Identity(prvKey) - Use a RSA private key to generate the ID of the host. + // EnableRelay() - Enable relay system and configures itself as a node behind a NAT + client, err := libp2p.New( + context.Background(), + libp2p.Identity(prvKey), + libp2p.EnableRelay(), + ) + if err != nil { + return nil, errors.WithMessage(err, "creating new libp2p client") + } + + if err := client.Connect(context.Background(), *relayInfo); err != nil { + client.Close() + return nil, errors.WithMessage(err, "connecting to the relay server") + } + return &Account{client, relayAddr, prvKey}, nil +} + // NewRandomAccount generates a new random account. func NewRandomAccount(rng *rand.Rand) *Account { relayInfo, relayAddr, err := getRelayServerInfo() @@ -77,10 +112,12 @@ func NewRandomAccount(rng *rand.Rand) *Account { libp2p.EnableRelay(), ) if err != nil { + client.Close() panic(err) } if err := client.Connect(context.Background(), *relayInfo); err != nil { + client.Close() panic(errors.WithMessage(err, "connecting to the relay server")) } return &Account{client, relayAddr, prvKey} @@ -123,6 +160,11 @@ func (acc *Account) RegisterOnChainAddress(onChainAddr wallet.Address) error { return nil } +// Close closes the account. +func (acc *Account) Close() error { + return acc.Close() +} + // DeregisterOnChainAddress deregisters an on-chain address with the account from the relay-server's address book. func (acc *Account) DeregisterOnChainAddress(onChainAddr wallet.Address) error { relayInfo, _, err := getRelayServerInfo() diff --git a/p2p/account_test.go b/p2p/account_test.go index 5bfb4fd..277bb4a 100644 --- a/p2p/account_test.go +++ b/p2p/account_test.go @@ -209,3 +209,18 @@ func TestAddressBookRegisterQueryMultiple(t *testing.T) { time.Sleep(1 * time.Second) } + +// Test NewAccountFromPrivateKey +func TestNewAccountFromPrivateKey(t *testing.T) { + rng := pkgtest.Prng(t) + acc := NewRandomAccount(rng) + assert.NotNil(t, acc) + + keyBytes, err := acc.MarshalPrivateKey() + assert.NoError(t, err) + + acc2, err := NewAccountFromPrivateKeyBytes(keyBytes) + assert.NoError(t, err) + assert.NotNil(t, acc2) + assert.Equal(t, acc.ID(), acc2.ID()) +} From abb88016a7095b8fc4073825774dc02aff1c949b Mon Sep 17 00:00:00 2001 From: Minh Huy Tran Date: Wed, 26 Jun 2024 11:03:12 +0200 Subject: [PATCH 3/3] test: add recreate account unit test + update doc Signed-off-by: Minh Huy Tran --- README.md | 33 ++------------------------------- p2p/account_test.go | 1 + 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 162adca..8f3cfe9 100644 --- a/README.md +++ b/README.md @@ -40,37 +40,8 @@ The `Dialer` requires the other peers to be already "registered" to connect with Example (register peer before propose a channel with them): ````go -// OpenChannel opens a new channel with the specified peer and funding. -func (c *PaymentClient) OpenChannel(peer wire.Address, peerID string, amount float64) *PaymentChannel { - .... - ``` - c.net.Dialer.Register(peer, peerID) - ``` - ... - - // Prepare the channel proposal by defining the channel parameters. - challengeDuration := uint64(10) // On-chain challenge duration in seconds. - proposal, err := client.NewLedgerChannelProposal( - challengeDuration, - c.account, - initAlloc, - participants, - ) - if err != nil { - panic(err) - } - - // Send the proposal. - ch, err := c.perunClient.ProposeChannel(context.TODO(), proposal) - if err != nil { - panic(err) - } - - // Start the on-chain event watcher. It automatically handles disputes. - c.startWatching(ch) - - return newPaymentChannel(ch, c.currency) -} +// Must be called at least once before attempting to connect with peer. +net.Dialer.Register(peer, peerID) ```` ## Address Ressolver diff --git a/p2p/account_test.go b/p2p/account_test.go index 277bb4a..32a9e1a 100644 --- a/p2p/account_test.go +++ b/p2p/account_test.go @@ -223,4 +223,5 @@ func TestNewAccountFromPrivateKey(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, acc2) assert.Equal(t, acc.ID(), acc2.ID()) + assert.Equal(t, acc.Address(), acc2.Address()) }