Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

Commit

Permalink
sdk/agent: add a first happy path test (#255)
Browse files Browse the repository at this point in the history
Add a first happy path test that exercises two agents that are connected to each other and successfully open, pay, and close.

The agent code was ported over from the console example. The console example was really written to be throw away code, but the agent code within it was well formed enough to be something to build on. It's important we add tests early for the agent before we build too much more into it. This is a first step to closing #225.
  • Loading branch information
leighmcculloch authored Aug 27, 2021
1 parent d9efee9 commit 6521706
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 14 deletions.
30 changes: 18 additions & 12 deletions sdk/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,20 +254,26 @@ func (a *Agent) Close() error {
return nil
}

func (a *Agent) loop() {
var err error
func (a *Agent) receive() error {
recv := msg.NewDecoder(io.TeeReader(a.conn, a.LogWriter))
send := msg.NewEncoder(io.MultiWriter(a.conn, a.LogWriter))
m := msg.Message{}
err := recv.Decode(&m)
if err != nil {
return fmt.Errorf("reading and decoding: %v\n", err)
}
err = a.handle(m, send)
if err != nil {
return fmt.Errorf("handling message: %v\n", err)
}
return nil
}

func (a *Agent) receiveLoop() {
for {
m := msg.Message{}
err = recv.Decode(&m)
if err != nil {
fmt.Fprintf(a.LogWriter, "error reading: %v\n", err)
break
}
err = a.handle(m, send)
err := a.receive()
if err != nil {
fmt.Fprintf(a.LogWriter, "error handling message: %v\n", err)
fmt.Fprintf(a.LogWriter, "error receiving: %v\n", err)
}
}
}
Expand All @@ -276,11 +282,11 @@ func (a *Agent) handle(m msg.Message, send *msg.Encoder) error {
fmt.Fprintf(a.LogWriter, "handling %v\n", m.Type)
handler := handlerMap[m.Type]
if handler == nil {
return fmt.Errorf("unrecognized message type %v", m.Type)
return fmt.Errorf("handling message %d: unrecognized message type", m.Type)
}
err := handler(a, m, send)
if err != nil {
return fmt.Errorf("handling message type %v: %w", m.Type, err)
return fmt.Errorf("handling message %d: %w", m.Type, err)
}
return nil
}
Expand Down
170 changes: 170 additions & 0 deletions sdk/agent/agent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package agent

import (
"bytes"
"io"
"testing"
"time"

"github.com/stellar/experimental-payment-channels/sdk/state"
"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
"github.com/stellar/go/txnbuild"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type sequenceNumberCollector func(accountID *keypair.FromAddress) (int64, error)

func (f sequenceNumberCollector) GetSequenceNumber(accountID *keypair.FromAddress) (int64, error) {
return f(accountID)
}

type balanceCollectorFunc func(accountID *keypair.FromAddress, asset state.Asset) (int64, error)

func (f balanceCollectorFunc) GetBalance(accountID *keypair.FromAddress, asset state.Asset) (int64, error) {
return f(accountID, asset)
}

type submitterFunc func(tx *txnbuild.Transaction) error

func (f submitterFunc) SubmitTx(tx *txnbuild.Transaction) error {
return f(tx)
}

func TestAgent_openPaymentClose(t *testing.T) {
localEscrow := keypair.MustRandom()
localSigner := keypair.MustRandom()
remoteEscrow := keypair.MustRandom()
remoteSigner := keypair.MustRandom()

// Setup the local agent.
localVars := struct {
submittedTx *txnbuild.Transaction
}{}
localAgent := &Agent{
ObservationPeriodTime: 20 * time.Second,
ObservationPeriodLedgerGap: 1,
MaxOpenExpiry: 5 * time.Minute,
NetworkPassphrase: network.TestNetworkPassphrase,
SequenceNumberCollector: sequenceNumberCollector(func(accountID *keypair.FromAddress) (int64, error) {
return 1, nil
}),
BalanceCollector: balanceCollectorFunc(func(accountID *keypair.FromAddress, asset state.Asset) (int64, error) {
return 100_0000000, nil
}),
Submitter: submitterFunc(func(tx *txnbuild.Transaction) error {
localVars.submittedTx = tx
return nil
}),
EscrowAccountKey: localEscrow.FromAddress(),
EscrowAccountSigner: localSigner,
LogWriter: io.Discard,
}

// Setup the remote agent.
remoteVars := struct {
submittedTx *txnbuild.Transaction
}{}
remoteAgent := &Agent{
ObservationPeriodTime: 20 * time.Second,
ObservationPeriodLedgerGap: 1,
MaxOpenExpiry: 5 * time.Minute,
NetworkPassphrase: network.TestNetworkPassphrase,
SequenceNumberCollector: sequenceNumberCollector(func(accountID *keypair.FromAddress) (int64, error) {
return 1, nil
}),
BalanceCollector: balanceCollectorFunc(func(accountID *keypair.FromAddress, asset state.Asset) (int64, error) {
return 100_0000000, nil
}),
Submitter: submitterFunc(func(tx *txnbuild.Transaction) error {
remoteVars.submittedTx = tx
return nil
}),
EscrowAccountKey: remoteEscrow.FromAddress(),
EscrowAccountSigner: remoteSigner,
LogWriter: io.Discard,
}

// Connect the two agents.
type ReadWriter struct {
io.Reader
io.Writer
}
localMsgs := bytes.Buffer{}
remoteMsgs := bytes.Buffer{}
localAgent.conn = ReadWriter{
Reader: &remoteMsgs,
Writer: &localMsgs,
}
remoteAgent.conn = ReadWriter{
Reader: &localMsgs,
Writer: &remoteMsgs,
}
err := localAgent.hello()
require.NoError(t, err)
err = remoteAgent.receive()
require.NoError(t, err)
err = remoteAgent.hello()
require.NoError(t, err)
err = localAgent.receive()
require.NoError(t, err)

// Open the channel.
err = localAgent.Open()
require.NoError(t, err)
err = remoteAgent.receive()
require.NoError(t, err)
err = localAgent.receive()
require.NoError(t, err)

// Expect the open tx to have been submitted.
openTx, err := localAgent.channel.OpenTx()
require.NoError(t, err)
assert.Equal(t, openTx, localVars.submittedTx)
localVars.submittedTx = nil

// Make a payment.
err = localAgent.Payment("50.0")
require.NoError(t, err)
err = remoteAgent.receive()
require.NoError(t, err)
err = localAgent.receive()
require.NoError(t, err)

// Make another payment.
err = remoteAgent.Payment("20.0")
require.NoError(t, err)
err = localAgent.receive()
require.NoError(t, err)
err = remoteAgent.receive()
require.NoError(t, err)

// Expect no txs to have been submitted for payments.
assert.Nil(t, localVars.submittedTx)
assert.Nil(t, remoteVars.submittedTx)

// Declare the close, and start negotiating for an early close.
err = localAgent.DeclareClose()
require.NoError(t, err)

// Expect the declaration tx to have been submitted.
localDeclTx, _, err := localAgent.channel.CloseTxs()
require.NoError(t, err)
assert.Equal(t, localDeclTx, localVars.submittedTx)

// Receive the declaration at the remote and complete negotiation.
err = remoteAgent.receive()
require.NoError(t, err)
err = localAgent.receive()
require.NoError(t, err)

// Expect the close tx to have been submitted.
_, localCloseTx, err := localAgent.channel.CloseTxs()
require.NoError(t, err)
_, remoteCloseTx, err := remoteAgent.channel.CloseTxs()
require.NoError(t, err)
assert.Equal(t, localCloseTx, remoteCloseTx)
assert.Equal(t, localCloseTx, localVars.submittedTx)
assert.Equal(t, remoteCloseTx, remoteVars.submittedTx)
}
4 changes: 2 additions & 2 deletions sdk/agent/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (a *Agent) ServeTCP(addr string) error {
if err != nil {
return fmt.Errorf("sending hello: %w", err)
}
go a.loop()
go a.receiveLoop()
return nil
}

Expand All @@ -42,6 +42,6 @@ func (a *Agent) ConnectTCP(addr string) error {
if err != nil {
return fmt.Errorf("sending hello: %w", err)
}
go a.loop()
go a.receiveLoop()
return nil
}

0 comments on commit 6521706

Please sign in to comment.