From 341add847eee07b6843de4e230c330335a1c7fe0 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 31 Oct 2023 19:29:40 +0100 Subject: [PATCH 01/51] channel: Add Funder, Adjudicator interfaces, event handling channel/env: Add StellarClient to connect to the Stellar blockchain and Soroban. client: Implement payment channel workflow by adding PaymentChannel and PaymentClient services/horizon/docker, testdata: Add docker files and Stellar client binaries to ensure Soroban functionality, as imeplemented in Protocol 20 in the stellar/go SDK. --- channel/adjudicator.go | 311 ++++++++++++++ channel/adjudicator_sub.go | 234 +++++++++++ channel/adjudicator_test.go | 219 ++++++++++ channel/deploy.go | 89 ++++ channel/env/client.go | 72 ++++ channel/env/integration.go | 141 +++++++ channel/env/transaction.go | 115 +++++ channel/event.go | 394 ++++++++++++++++++ channel/funder.go | 350 ++++++++++++++++ channel/funder_test.go | 222 ++++++++++ channel/subscribe.go | 73 ++++ channel/test/setup.go | 51 +++ channel/timeout.go | 102 +++++ client/channel.go | 124 ++++++ client/client.go | 202 +++++++++ client/handle.go | 105 +++++ go.mod | 68 ++- go.sum | 243 ++++++++++- main.go | 52 +++ services/horizon/docker/Dockerfile | 21 + services/horizon/docker/Dockerfile.dev | 28 ++ services/horizon/docker/Makefile | 32 ++ ...captive-core-classic-integration-tests.cfg | 13 + .../docker/captive-core-integration-tests.cfg | 15 + ...ive-core-integration-tests.soroban-rpc.cfg | 14 + ...ingest-range-classic-integration-tests.cfg | 9 + ...-core-reingest-range-integration-tests.cfg | 10 + .../docker/captive-core-standalone.cfg | 12 + services/horizon/docker/core-start.sh | 29 ++ ...-compose.integration-tests.soroban-rpc.yml | 20 + .../docker-compose.integration-tests.yml | 32 ++ .../horizon/docker/docker-compose.pubnet.yml | 6 + .../docker/docker-compose.standalone.yml | 53 +++ services/horizon/docker/docker-compose.yml | 38 ++ services/horizon/docker/start.sh | 28 ++ ...stellar-core-classic-integration-tests.cfg | 24 ++ .../docker/stellar-core-integration-tests.cfg | 25 ++ .../horizon/docker/stellar-core-pubnet.cfg | 202 +++++++++ .../docker/stellar-core-standalone.cfg | 25 ++ .../horizon/docker/stellar-core-testnet.cfg | 41 ++ testdata/perun_soroban_contract.wasm | Bin 0 -> 24044 bytes .../perun_soroban_contract_noapprove.wasm | Bin 0 -> 24135 bytes util/util.go | 76 ++++ wire/balances.go | 1 + wire/params.go | 22 +- 45 files changed, 3929 insertions(+), 14 deletions(-) create mode 100644 channel/adjudicator.go create mode 100644 channel/adjudicator_sub.go create mode 100644 channel/adjudicator_test.go create mode 100644 channel/deploy.go create mode 100644 channel/env/client.go create mode 100644 channel/env/integration.go create mode 100644 channel/env/transaction.go create mode 100644 channel/event.go create mode 100644 channel/funder.go create mode 100644 channel/funder_test.go create mode 100644 channel/subscribe.go create mode 100644 channel/test/setup.go create mode 100644 channel/timeout.go create mode 100644 client/channel.go create mode 100644 client/client.go create mode 100644 client/handle.go create mode 100644 main.go create mode 100644 services/horizon/docker/Dockerfile create mode 100644 services/horizon/docker/Dockerfile.dev create mode 100644 services/horizon/docker/Makefile create mode 100644 services/horizon/docker/captive-core-classic-integration-tests.cfg create mode 100644 services/horizon/docker/captive-core-integration-tests.cfg create mode 100644 services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg create mode 100644 services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg create mode 100644 services/horizon/docker/captive-core-reingest-range-integration-tests.cfg create mode 100644 services/horizon/docker/captive-core-standalone.cfg create mode 100755 services/horizon/docker/core-start.sh create mode 100644 services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml create mode 100644 services/horizon/docker/docker-compose.integration-tests.yml create mode 100644 services/horizon/docker/docker-compose.pubnet.yml create mode 100644 services/horizon/docker/docker-compose.standalone.yml create mode 100644 services/horizon/docker/docker-compose.yml create mode 100755 services/horizon/docker/start.sh create mode 100644 services/horizon/docker/stellar-core-classic-integration-tests.cfg create mode 100644 services/horizon/docker/stellar-core-integration-tests.cfg create mode 100644 services/horizon/docker/stellar-core-pubnet.cfg create mode 100644 services/horizon/docker/stellar-core-standalone.cfg create mode 100644 services/horizon/docker/stellar-core-testnet.cfg create mode 100755 testdata/perun_soroban_contract.wasm create mode 100755 testdata/perun_soroban_contract_noapprove.wasm create mode 100644 util/util.go diff --git a/channel/adjudicator.go b/channel/adjudicator.go new file mode 100644 index 0000000..07ffd9e --- /dev/null +++ b/channel/adjudicator.go @@ -0,0 +1,311 @@ +package channel + +import ( + "context" + "errors" + "github.com/stellar/go/keypair" + //"github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/xdr" + + "perun.network/go-perun/log" + + pchannel "perun.network/go-perun/channel" + pwallet "perun.network/go-perun/wallet" + "perun.network/perun-stellar-backend/channel/env" + //"perun.network/perun-stellar-backend/client" + "perun.network/perun-stellar-backend/wallet" + + "perun.network/perun-stellar-backend/wire" + "perun.network/perun-stellar-backend/wire/scval" + "time" +) + +var ErrChannelAlreadyClosed = errors.New("nonce values was out of range") + +type Adjudicator struct { + log log.Embedding + //integrEnv env.IntegrationTestEnv + stellarClient *env.StellarClient + acc *wallet.Account + kpFull *keypair.Full + maxIters int + pollingInterval time.Duration +} + +// NewAdjudicator returns a new Adjudicator. + +func NewAdjudicator(acc *wallet.Account, stellarClient *env.StellarClient) *Adjudicator { + return &Adjudicator{ + stellarClient: stellarClient, + acc: acc, + maxIters: MaxIterationsUntilAbort, + pollingInterval: DefaultPollingInterval, + } +} + +func (a Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel.AdjudicatorSubscription, error) { + c := a.stellarClient + return NewAdjudicatorSub(ctx, cid, c), nil +} + +func (a Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { + + cid := req.Tx.ID + + txSigner := a.stellarClient + + if req.Tx.State.IsFinal { + log.Println("Withdraw called") + // we listen for the close event + + // a.isConcluded + + evSub := NewAdjudicatorSub(ctx, req.Tx.ID, txSigner) + defer evSub.Close() + + err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) + if err != nil { + if err == ErrChannelAlreadyClosed { + return a.withdraw(ctx, req) + } else { + return err + } + } + // close has been called, now we wait for the event + err = a.waitForClosed(ctx, evSub, cid) + if err != nil { + return err + } + //... after the event has arrived, we conclude + return a.withdraw(ctx, req) + + } else { + err := a.ForceClose(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) + if err != nil { + if err == ErrChannelAlreadyClosed { + return a.withdraw(ctx, req) + } else { + return err + } + } + + err = a.withdraw(ctx, req) + } + return nil +} + +func (a Adjudicator) waitForClosed(ctx context.Context, evsub *AdjEventSub, cid pchannel.ID) error { + a.log.Log().Tracef("Waiting for the channel closing event") +loop: + for { + + select { + case event := <-evsub.Events(): + + _, ok := event.(*CloseEvent) + + if !ok { + continue loop + } + + evsub.Close() + return nil + + case <-ctx.Done(): + return ctx.Err() + case err := <-evsub.PanicErr(): + return err + default: + continue loop + } + + } +} + +func (a Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVec, error) { + + // build withdrawalargs + chanIDStellar := req.Tx.ID[:] + var chanid xdr.ScBytes + copy(chanid, chanIDStellar) + channelID, err := scval.WrapScBytes(chanIDStellar) + if err != nil { + return xdr.ScVec{}, err + } + + withdrawArgs := xdr.ScVec{ + channelID, + } + return withdrawArgs, nil + +} + +func (a Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) error { + + contractAddress := a.stellarClient.GetContractIDAddress() + kp := a.kpFull + hzAcc := a.stellarClient.GetHorizonAcc() + //chanId := req.Tx.State.ID + + // generate tx to open the channel + withdrawTxArgs, err := a.BuildWithdrawTxArgs(req) + if err != nil { + return errors.New("error while building fund tx") + } + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "withdraw", withdrawTxArgs, contractAddress, kp) + + // build withdrawalargs + + // env := a.integrEnv + // contractAddress := env.GetContractIDAddress() + // kp := a.kpFull + // acc := env.AccountDetails(kp) + // generate tx to open the channel + + // call fct. + // contractAddr := a.integrEnv.GetContractIDAddress() + // //caller := a.integrEnv.Client() + // kp := a.kpFull + // acc := a.integrEnv.AccountDetails(kp) + + // invokeHostFunctionOp := env.BuildContractCallOp(acc, "withdraw", withdrawTxArgs, contractAddr) + + // preFlightOp, minFee := a.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) + + // tx, err := a.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) + // if err != nil { + // return errors.New("error while submitting operations with fee") + // } + + // // read out decoded Events and interpret them + // txMeta, err := env.DecodeTxMeta(tx) + // if err != nil { + // return errors.New("error while decoding tx meta") + // } + _, err = DecodeEvents(txMeta) + if err != nil { + return errors.New("error while decoding events") + } + + return nil +} + +func (a Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { + //env := a.integrEnv + + contractAddress := a.stellarClient.GetContractIDAddress() + kp := a.kpFull + hzAcc := a.stellarClient.GetHorizonAcc() + closeTxArgs, err := BuildCloseTxArgs(*state, sigs) + if err != nil { + return errors.New("error while building fund tx") + } + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "close", closeTxArgs, contractAddress, kp) + + // contractAddress := a.integrEnv.GetContractIDAddress() + // kp := a.kpFull + // acc := a.integrEnv.AccountDetails(kp) + // // generate tx to open the channel + // fundTxArgs, err := BuildCloseTxArgs(*state, sigs) + // if err != nil { + // return errors.New("error while building fund tx") + // } + // invokeHostFunctionOp := env.BuildContractCallOp(acc, "close", fundTxArgs, contractAddress) + + // preFlightOp, minFee := a.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) + + // tx, err := a.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) + // if err != nil { + // return errors.New("error while submitting operations with fee") + // } + + // // read out decoded Events and interpret them + // txMeta, err := env.DecodeTxMeta(tx) + // if err != nil { + // return errors.New("error while decoding tx meta") + // } + _, err = DecodeEvents(txMeta) + if err != nil { + return errors.New("error while decoding events") + } + return nil +} + +// Register registers and disputes a channel. +func (a Adjudicator) Register(ctx context.Context, req pchannel.AdjudicatorReq, states []pchannel.SignedState) error { + panic("implement me") +} + +func (a Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { + + contractAddress := a.stellarClient.GetContractIDAddress() + kp := a.kpFull + hzAcc := a.stellarClient.GetHorizonAcc() + forceCloseTxArgs, err := env.BuildForceCloseTxArgs(id) + if err != nil { + return errors.New("error while building fund tx") + } + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "force_close", forceCloseTxArgs, contractAddress, kp) + + // //env := a.integrEnv + // contractAddress := a.integrEnv.GetContractIDAddress() + // kp := a.kpFull + // acc := a.integrEnv.AccountDetails(kp) + // // generate tx to open the channel + // fundTxArgs, err := BuildCloseTxArgs(*state, sigs) + // if err != nil { + // return errors.New("error while building fund tx") + // } + // invokeHostFunctionOp := env.BuildContractCallOp(acc, "force_close", fundTxArgs, contractAddress) + + // preFlightOp, minFee := a.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) + + // tx, err := a.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) + // if err != nil { + // return errors.New("error while submitting operations with fee") + // } + + // // read out decoded Events and interpret them + // txMeta, err := env.DecodeTxMeta(tx) + // if err != nil { + // return errors.New("error while decoding tx meta") + // } + _, err = DecodeEvents(txMeta) + if err != nil { + return errors.New("error while decoding events") + } + return nil +} + +func BuildCloseTxArgs(state pchannel.State, sigs []pwallet.Sig) (xdr.ScVec, error) { + + wireState, err := wire.MakeState(state) + if err != nil { + return xdr.ScVec{}, err + } + + sigAXdr, err := scval.WrapScBytes(sigs[0]) + if err != nil { + return xdr.ScVec{}, err + } + sigBXdr, err := scval.WrapScBytes(sigs[1]) + if err != nil { + return xdr.ScVec{}, err + } + xdrState, err := wireState.ToScVal() + if err != nil { + return xdr.ScVec{}, err + } + + fundArgs := xdr.ScVec{ + xdrState, + sigAXdr, + sigBXdr, + } + return fundArgs, nil +} + +func (a Adjudicator) Progress(ctx context.Context, req pchannel.ProgressReq) error { + // only relevant for AppChannels + return nil +} diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go new file mode 100644 index 0000000..1fbcd6e --- /dev/null +++ b/channel/adjudicator_sub.go @@ -0,0 +1,234 @@ +package channel + +import ( + "context" + "errors" + "github.com/stellar/go/xdr" + pchannel "perun.network/go-perun/channel" + log "perun.network/go-perun/log" + "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/wire" + pkgsync "polycry.pt/poly-go/sync" + "time" +) + +const ( + DefaultBufferSize = 3 + DefaultSubscriptionPollingInterval = time.Duration(4) * time.Second +) + +type AdjEvent interface { + // Sets the necessary event data from the channel information + EventDataFromChannel(cchanState wire.Channel, timestamp uint64) error + ID() pchannel.ID + Timeout() pchannel.Timeout + Version() Version + Tstamp() uint64 +} + +// AdjudicatorSub implements the AdjudicatorSubscription interface. +type AdjEventSub struct { + env *env.IntegrationTestEnv + queryChanArgs xdr.ScVec + // current state of the channel + //chanState wire.Channel + chanControl wire.Control + cid pchannel.ID + events chan AdjEvent + Ev []AdjEvent + err error + panicErr chan error + cancel context.CancelFunc + closer *pkgsync.Closer + pollInterval time.Duration + log log.Embedding +} + +func (e *AdjEventSub) GetState() <-chan AdjEvent { + return e.events +} + +func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env.StellarClient) *AdjEventSub { + getChanArgs, err := env.BuildGetChannelTxArgs(cid) + if err != nil { + panic(err) + } + + sub := &AdjEventSub{ + queryChanArgs: getChanArgs, + //agent: conn.PerunAgent, + chanControl: wire.Control{}, + events: make(chan AdjEvent, DefaultBufferSize), + Ev: make([]AdjEvent, 0), + panicErr: make(chan error, 1), + pollInterval: DefaultSubscriptionPollingInterval, + closer: new(pkgsync.Closer), + log: log.MakeEmbedding(log.Default()), + } + + ctx, sub.cancel = context.WithCancel(ctx) + go sub.run(ctx) + return sub + +} + +func (s *AdjEventSub) run(ctx context.Context) { + s.log.Log().Info("Listening for channel state changes") + finish := func(err error) { + s.err = err + close(s.events) + + } +polling: + for { + s.log.Log().Debug("AdjudicatorSub is listening for Adjudicator Events") + select { + case <-ctx.Done(): + finish(nil) + return + case <-s.events: + finish(nil) + return + case <-time.After(s.pollInterval): + + newChanControl, err := s.getChanControl() + + if err != nil { + // if query was not successful, simply repeat + continue polling + } + // decode channel state difference to events + adjEvent, err := DifferencesInControls(newChanControl, s.chanControl) + if err != nil { + s.panicErr <- err + } + + if adjEvent == nil { + s.log.Log().Debug("No events yet, continuing polling...") + continue polling + + } else { + s.log.Log().Debug("Event detected, evaluating events...") + + // Store the event + + s.log.Log().Debugf("Found event: %v", adjEvent) + s.events <- adjEvent + return + } + } + } +} + +func (s *AdjEventSub) getChanControl() (wire.Control, error) { + // query channel state + + getChanArgs, err := env.BuildGetChannelTxArgs(s.cid) + //kpStellar := s.env.GetStellarClient().GetAccount() + + chanMeta, err := s.env.GetChannelState(getChanArgs) + if err != nil { + return wire.Control{}, err + } + + retVal := chanMeta.V3.SorobanMeta.ReturnValue + var chanState wire.Channel + + err = chanState.FromScVal(retVal) + if err != nil { + return wire.Control{}, err + } + + chanControl := chanState.Control + + return chanControl, nil +} + +func (s *AdjEventSub) chanStateToEvent(newState wire.Control) (AdjEvent, error) { + // query channel state + + currControl := s.chanControl + + newCControl, err := s.getChanControl() + if err != nil { + return nil, err + } + + sameChannel := IdenticalControls(currControl, newCControl) + + if sameChannel { + return CloseEvent{}, nil + } + + // state has changed: we evaluate the differences + adjEvents, err := DifferencesInControls(currControl, newCControl) + if err != nil { + return nil, err + } + return adjEvents, nil + +} + +func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, error) { + + if controlCurr.FundedA != controlNext.FundedA { + if controlCurr.FundedA { + return nil, errors.New("channel cannot be unfunded before withdrawal") + } + if controlNext.WithdrawnA && controlNext.WithdrawnB { + return FundEvent{}, nil + } + } + + if controlCurr.FundedB != controlNext.FundedB { + if controlCurr.FundedB { + return nil, errors.New("channel cannot be unfunded before withdrawal") + } + if controlNext.WithdrawnA && controlNext.WithdrawnB { + return FundEvent{}, nil + } + } + + if controlCurr.Closed != controlNext.Closed { + if controlCurr.Closed { + return nil, errors.New("channel cannot be reopened after closing") + } + return CloseEvent{}, nil + } + + if controlCurr.WithdrawnA != controlNext.WithdrawnA { + if controlCurr.WithdrawnA { + return nil, errors.New("channel cannot be unwithdrawn") + } + if controlNext.WithdrawnA && controlNext.WithdrawnB { + return WithdrawnEvent{}, nil + } + } + + if controlCurr.WithdrawnB != controlNext.WithdrawnB { + if controlCurr.WithdrawnB { + return nil, errors.New("channel cannot be unwithdrawn") + } + if controlNext.WithdrawnA && controlNext.WithdrawnB { + return WithdrawnEvent{}, nil + } + } + + if controlCurr.Disputed != controlNext.Disputed { + if controlCurr.Disputed { + return nil, errors.New("channel cannot be undisputed") + } + return DisputedEvent{}, nil + } + + return nil, nil +} + +func IdenticalControls(controlCurr, controlNext wire.Control) bool { + return controlCurr.FundedA == controlNext.FundedA && + controlCurr.FundedB == controlNext.FundedB && + controlCurr.Closed == controlNext.Closed && + controlCurr.WithdrawnA == controlNext.WithdrawnA && + controlCurr.WithdrawnB == controlNext.WithdrawnB && + controlCurr.Disputed == controlNext.Disputed +} diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go new file mode 100644 index 0000000..b26fb4e --- /dev/null +++ b/channel/adjudicator_test.go @@ -0,0 +1,219 @@ +package channel_test + +import ( + "fmt" + + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + pwallet "perun.network/go-perun/wallet" + pkgtest "polycry.pt/poly-go/test" + + chtest "perun.network/perun-stellar-backend/channel/test" + //client "perun.network/perun-stellar-backend/client" + "perun.network/perun-stellar-backend/wallet" + + _ "perun.network/perun-stellar-backend/wallet/test" + "perun.network/perun-stellar-backend/wire" + + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/env" + + "testing" +) + +func TestCloseChannel(t *testing.T) { + itest := env.NewBackendEnv() + + kps, _ := itest.CreateAccounts(2, "10000000") + + kpAlice := kps[0] + kpBob := kps[1] + + reqAlice := itest.AccountDetails(kpAlice) + reqBob := itest.AccountDetails(kpBob) + + fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) + + installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), channel.PerunContractPath) + preFlightOp, minFee := itest.PreflightHostFunctions(&reqAlice, *installContractOp) + _ = itest.MustSubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + + // Create the contract + + createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), channel.PerunContractPath, "a1", itest.GetPassPhrase()) + preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *createContractOp) + _, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + + require.NoError(t, err) + + // contract has been deployed, now invoke 'open' fn on the contract + contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + require.NotNil(t, contractID) + contractIDAddress := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: contractID, + } + + // make L2 Accounts for signing states: + + rng := pkgtest.Prng(t) + + wAlice := wallet.NewEphemeralWallet() + accAlice, _, err := wAlice.AddNewAccount(rng) + require.NoError(t, err) + addrAlice := accAlice.Address() + + wBob := wallet.NewEphemeralWallet() + accBob, _, err := wBob.AddNewAccount(rng) + require.NoError(t, err) + addrBob := accBob.Address() + + addrList := []pwallet.Address{addrAlice, addrBob} + + // invoke open-channel function + + perunFirstParams, perunFirstState := chtest.NewParamsWithAddressState(t, addrList) + + openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) + + invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress) + + preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + + tx, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + require.NoError(t, err) + + clientTx, err := itest.Client().TransactionDetail(tx.Hash) + fmt.Println("clientTx: ", clientTx) + require.NoError(t, err) + + assert.Equal(t, tx.Hash, clientTx.Hash) + var txResult xdr.TransactionResult + err = xdr.SafeUnmarshalBase64(clientTx.ResultXdr, &txResult) + require.NoError(t, err) + + opResults, ok := txResult.OperationResults() + + assert.True(t, ok) + assert.Equal(t, len(opResults), 1) + invokeHostFunctionResult, ok := opResults[0].MustTr().GetInvokeHostFunctionResult() + assert.True(t, ok) + assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) + + txMeta, err := env.DecodeTxMeta(tx) + require.NoError(t, err) + + perunEvents, err := channel.DecodeEvents(txMeta) + require.NoError(t, err) + + fmt.Println("perunEvents: ", perunEvents) + + // fund the channel after it is open + + chanID := perunFirstParams.ID() + aliceIdx := false + + fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) + require.NoError(t, err) + + invokeHostFunctionOpFund := env.BuildContractCallOp(reqAlice, "fund", fundArgsAlice, contractIDAddress) + + preFlightOpFund, minFeeFund := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpFund) + + txFund, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeFund, &preFlightOpFund) + fmt.Println("minFeeFund: ", minFeeFund) + require.NoError(t, err) + + txMetaFund, err := env.DecodeTxMeta(txFund) + require.NoError(t, err) + + perunEventsFund, err := channel.DecodeEvents(txMetaFund) + require.NoError(t, err) + fmt.Println("perunEventsFund: ", perunEventsFund) + + bobIdx := true + fundArgsBob, err := env.BuildFundTxArgs(chanID, bobIdx) + require.NoError(t, err) + invokeHostFunctionOpFund2 := env.BuildContractCallOp(reqBob, "fund", fundArgsBob, contractIDAddress) + + preFlightOpFund2, minFeeFund2 := itest.PreflightHostFunctions(&reqBob, *invokeHostFunctionOpFund2) + fmt.Println("minFeeFund2: ", minFeeFund2) + + txFund2, err := itest.SubmitOperationsWithFee(&reqBob, kpBob, minFeeFund2, &preFlightOpFund2) + + require.NoError(t, err) + txMetaFund2, err := env.DecodeTxMeta(txFund2) + require.NoError(t, err) + fmt.Println("txMetaFund2: ", txMetaFund2) + perunEventsFund2, err := channel.DecodeEvents(txMetaFund2) + require.NoError(t, err) + fmt.Println("perunEventsFund2: ", perunEventsFund2) + + // qurey channel state + + getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) + require.NoError(t, err) + + invokeHostFunctionOpGetCh := env.BuildContractCallOp(reqAlice, "get_channel", getChannelArgs, contractIDAddress) + preFlightOpGetCh, minFeeGetCh := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpGetCh) + txGetCh, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeGetCh, &preFlightOpGetCh) + require.NoError(t, err) + + txMetaGetCh, err := env.DecodeTxMeta(txGetCh) + require.NoError(t, err) + fmt.Println("txMetaGetCh: ", txMetaGetCh) + retVal := txMetaGetCh.V3.SorobanMeta.ReturnValue + fmt.Println("retVal: ", retVal) + + var getChan wire.Channel + + err = getChan.FromScVal(retVal) + require.NoError(t, err) + fmt.Println("getChan: ", getChan) + + // close the channel + + currStellarState := getChan.State + + currStellarState.Finalized = true + + currPerunState, err := wire.ToState(currStellarState) + + require.NoError(t, err) + fmt.Println("currStellarState: ", currStellarState) + + sigA, err := channel.Backend.Sign(accAlice, &currPerunState) + require.NoError(t, err) + + sigB, err := channel.Backend.Sign(accBob, &currPerunState) + require.NoError(t, err) + + sigs := []pwallet.Sig{sigA, sigB} + + // addrAlice := kpAlice.FromAddress() + // channel.Backend.Sign(addrAlice, currStellarState) + + closeArgs, err := channel.BuildCloseTxArgs(currPerunState, sigs) + require.NoError(t, err) + + invokeHostFunctionOpClose := env.BuildContractCallOp(reqAlice, "close", closeArgs, contractIDAddress) + + preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpClose) + + txClose, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeClose, &preFlightOpClose) + + require.NoError(t, err) + + txMetaClose, err := env.DecodeTxMeta(txClose) + + require.NoError(t, err) + + perunEventsClose, err := channel.DecodeEvents(txMetaClose) + + require.NoError(t, err) + fmt.Println("perunEventsClose: ", perunEventsClose) + + // now both users can withdraw their funds + +} diff --git a/channel/deploy.go b/channel/deploy.go new file mode 100644 index 0000000..bf5dea4 --- /dev/null +++ b/channel/deploy.go @@ -0,0 +1,89 @@ +package channel + +import ( + "crypto/sha256" + "encoding/hex" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + "log" + "os" + //"perun.network/perun-stellar-backend/channel/env" +) + +const PerunContractPath = "../testdata/perun_soroban_contract.wasm" + +// func InstallAndCreateContract(reqAlice *YourReqType, kpAlice YourKeyType) error { // Assuming "YourReqType" and "YourKeyType" are the types of `reqAlice` and `kpAlice` respectively. +// // Install the contract +// installContractOp := AssembleInstallContractCodeOp(kpAlice.Address(), PerunContractPath) +// preFlightOp, minFee := env.PreflightHostFunctions(reqAlice, *installContractOp) +// if err := itest.MustSubmitOperationsWithFee(reqAlice, kpAlice, 5*minFee, &preFlightOp); err != nil { +// return err +// } + +// // Create the contract +// createContractOp := client.AssembleCreateContractOp(kpAlice.Address(), client.PerunContractPath, "a1", itest.GetPassPhrase()) +// preFlightOp, minFee = itest.PreflightHostFunctions(reqAlice, *createContractOp) +// if _, err := itest.SubmitOperationsWithFee(reqAlice, kpAlice, minFee, &preFlightOp); err != nil { +// return err +// } + +// return nil +// } + +func AssembleInstallContractCodeOp(sourceAccount string, wasmFileName string) *txnbuild.InvokeHostFunction { + // Assemble the InvokeHostFunction UploadContractWasm operation: + // CAP-0047 - https://github.com/stellar/stellar-protocol/blob/master/core/cap-0047.md#creating-a-contract-using-invokehostfunctionop + + contract, err := os.ReadFile(wasmFileName) + if err != nil { + panic(err) + } + + return &txnbuild.InvokeHostFunction{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm, + Wasm: &contract, + }, + SourceAccount: sourceAccount, + } +} + +func AssembleCreateContractOp(sourceAccount string, wasmFileName string, contractSalt string, passPhrase string) *txnbuild.InvokeHostFunction { + // Assemble the InvokeHostFunction CreateContract operation: + // CAP-0047 - https://github.com/stellar/stellar-protocol/blob/master/core/cap-0047.md#creating-a-contract-using-invokehostfunctionop + + contract, err := os.ReadFile(wasmFileName) + if err != nil { + panic(err) + } + + salt := sha256.Sum256([]byte(contractSalt)) + log.Printf("Salt hash: %v", hex.EncodeToString(salt[:])) + saltParameter := xdr.Uint256(salt) + + accountId := xdr.MustAddress(sourceAccount) + contractHash := xdr.Hash(sha256.Sum256(contract)) + + return &txnbuild.InvokeHostFunction{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeCreateContract, + CreateContract: &xdr.CreateContractArgs{ + ContractIdPreimage: xdr.ContractIdPreimage{ + Type: xdr.ContractIdPreimageTypeContractIdPreimageFromAddress, + FromAddress: &xdr.ContractIdPreimageFromAddress{ + Address: xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeAccount, + AccountId: &accountId, + }, + Salt: saltParameter, + }, + }, + Executable: xdr.ContractExecutable{ + Type: xdr.ContractExecutableTypeContractExecutableWasm, + WasmHash: &contractHash, + }, + }, + }, + SourceAccount: sourceAccount, + } +} diff --git a/channel/env/client.go b/channel/env/client.go new file mode 100644 index 0000000..dfbdd70 --- /dev/null +++ b/channel/env/client.go @@ -0,0 +1,72 @@ +package env + +import ( + "errors" + "fmt" + "github.com/stellar/go/keypair" + "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/xdr" +) + +type StellarClient struct { + stellarEnv *IntegrationTestEnv + account *keypair.Full + hzAccount horizon.Account + passphrase string + contractIDAddress xdr.ScAddress +} + +func NewStellarClient(stellarEnv *IntegrationTestEnv, account *keypair.Full) *StellarClient { + passphrase := stellarEnv.GetPassPhrase() + return &StellarClient{ + stellarEnv: stellarEnv, + account: account, + passphrase: passphrase, + } +} + +func (s StellarClient) GetAccount() *keypair.Full { + return s.account +} +func (s StellarClient) GetHorizonAcc() horizon.Account { + return s.hzAccount +} + +func (s StellarClient) GetPassPhrase() string { + return s.passphrase +} + +func (s StellarClient) GetContractIDAddress() xdr.ScAddress { + return s.contractIDAddress +} + +func (s StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { + // Build contract call operation + fnameXdr := xdr.ScSymbol(fname) + invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr) + + // Preflight host functions + preFlightOp, minFee := s.stellarEnv.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) + + // Submit operations with fee + tx, err := s.stellarEnv.SubmitOperationsWithFee(&horizonAcc, kp, minFee, &preFlightOp) + if err != nil { + return xdr.TransactionMeta{}, errors.New("error while submitting operations with fee") + } + + // Decode transaction metadata + txMeta, err := DecodeTxMeta(tx) + if err != nil { + return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") + } + + fmt.Println("txMeta: ", txMeta) + + // // Decode events + // _, err = DecodeSorEvents(txMeta) + // if err != nil { + // return errors.New("error while decoding events") + // } + + return txMeta, nil +} diff --git a/channel/env/integration.go b/channel/env/integration.go new file mode 100644 index 0000000..0f3a9ff --- /dev/null +++ b/channel/env/integration.go @@ -0,0 +1,141 @@ +package env + +import ( + "errors" + "fmt" + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" + "github.com/stellar/go/protocols/horizon" + testenv "github.com/stellar/go/services/horizon/pkg/test/integration" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + "testing" +) + +const PtotocolVersion = 20 +const EnableSorobanRPC = true +const StandaloneAuth = "Standalone Network ; February 2017" +const SorobanRPCPort = 8080 + +type IntegrationTestEnv struct { + testEnv *testenv.Test + contractIDAddress xdr.ScAddress + stellarClient *StellarClient +} + +func NewBackendEnv() *IntegrationTestEnv { + // Create a dummy testing.T object. + // This might not be appropriate depending on your use case. + t := &testing.T{} + + cfg := testenv.Config{ + ProtocolVersion: PtotocolVersion, + EnableSorobanRPC: EnableSorobanRPC, + } + itest := IntegrationTestEnv{testEnv: testenv.NewTest(t, cfg)} + return &itest +} + +func NewIntegrationEnv(t *testing.T) *IntegrationTestEnv { + cfg := testenv.Config{ProtocolVersion: PtotocolVersion, + EnableSorobanRPC: EnableSorobanRPC, + } + itest := IntegrationTestEnv{testEnv: testenv.NewTest(t, cfg)} + return &itest +} + +// func (it *IntegrationTestEnv) GetStellarClient() *StellarClient { +// return it.stellarClient +// } + +func (it *IntegrationTestEnv) CreateAccounts(numAccounts int, initBalance string) ([]*keypair.Full, []txnbuild.Account) { + kps, accs := it.testEnv.CreateAccounts(numAccounts, initBalance) + + return kps, accs +} + +func (it *IntegrationTestEnv) AccountDetails(acc *keypair.Full) horizon.Account { + accountReq := horizonclient.AccountRequest{AccountID: acc.Address()} + hzAccount, err := it.testEnv.Client().AccountDetail(accountReq) + if err != nil { + panic(err) + } + return hzAccount +} + +func (it *IntegrationTestEnv) PreflightHostFunctions(sourceAccount txnbuild.Account, function txnbuild.InvokeHostFunction) (txnbuild.InvokeHostFunction, int64) { + return it.testEnv.PreflightHostFunctions(sourceAccount, function) +} + +func (it *IntegrationTestEnv) MustSubmitOperationsWithFee( + source txnbuild.Account, + signer *keypair.Full, + fee int64, + ops ...txnbuild.Operation, +) horizon.Transaction { + return it.testEnv.MustSubmitOperationsWithFee(source, signer, fee, ops...) +} + +func (it *IntegrationTestEnv) SubmitOperationsWithFee( + source txnbuild.Account, + signer *keypair.Full, + fee int64, + ops ...txnbuild.Operation, +) (horizon.Transaction, error) { + return it.testEnv.SubmitOperationsWithFee(source, signer, fee, ops...) +} + +func (it *IntegrationTestEnv) Client() *horizonclient.Client { + return it.testEnv.Client() +} + +func (it *IntegrationTestEnv) GetPassPhrase() string { + return it.testEnv.GetPassPhrase() +} + +func (it *IntegrationTestEnv) GetContractIDAddress() xdr.ScAddress { + return it.contractIDAddress +} + +func (it *IntegrationTestEnv) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { + // Build contract call operation + fnameXdr := xdr.ScSymbol(fname) + invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr) + + // Preflight host functions + preFlightOp, minFee := it.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) + + // Submit operations with fee + tx, err := it.SubmitOperationsWithFee(&horizonAcc, kp, minFee, &preFlightOp) + if err != nil { + return xdr.TransactionMeta{}, errors.New("error while submitting operations with fee") + } + + // Decode transaction metadata + txMeta, err := DecodeTxMeta(tx) + if err != nil { + return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") + } + + fmt.Println("txMeta: ", txMeta) + + // // Decode events + // _, err = DecodeSorEvents(txMeta) + // if err != nil { + // return errors.New("error while decoding events") + // } + + return txMeta, nil +} + +func (it *IntegrationTestEnv) GetChannelState(getChanArgs xdr.ScVec) (xdr.TransactionMeta, error) { + // query channel state + kp := it.stellarClient.account + + acc := it.AccountDetails(kp) + chanState, err := it.InvokeAndProcessHostFunction(acc, "get_channel", getChanArgs, xdr.ScAddress{}, nil) + if err != nil { + return xdr.TransactionMeta{}, err + } + return chanState, nil +} diff --git a/channel/env/transaction.go b/channel/env/transaction.go new file mode 100644 index 0000000..1690e30 --- /dev/null +++ b/channel/env/transaction.go @@ -0,0 +1,115 @@ +package env + +import ( + "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + + pchannel "perun.network/go-perun/channel" + "perun.network/perun-stellar-backend/wire" + "perun.network/perun-stellar-backend/wire/scval" +) + +func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xdr.ScVec, contractIdAddress xdr.ScAddress) *txnbuild.InvokeHostFunction { + return &txnbuild.InvokeHostFunction{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, + InvokeContract: &xdr.InvokeContractArgs{ + ContractAddress: contractIdAddress, + FunctionName: fName, + Args: callArgs, + }, + }, + SourceAccount: caller.AccountID, + } +} + +func BuildOpenTxArgs(params *pchannel.Params, state *pchannel.State) xdr.ScVec { + paramsStellar, err := wire.MakeParams(*params) + if err != nil { + panic(err) + } + stateStellar, err := wire.MakeState(*state) + if err != nil { + panic(err) + } + paramsXdr, err := paramsStellar.ToScVal() + if err != nil { + panic(err) + } + stateXdr, err := stateStellar.ToScVal() + if err != nil { + panic(err) + } + openArgs := xdr.ScVec{ + paramsXdr, + stateXdr, + } + return openArgs +} + +func BuildFundTxArgs(chanID pchannel.ID, funderIdx bool) (xdr.ScVec, error) { + + chanIDStellar := chanID[:] + var chanid xdr.ScBytes + copy(chanid[:], chanIDStellar) + channelID, err := scval.WrapScBytes(chanIDStellar) + if err != nil { + return xdr.ScVec{}, err + } + + userIdStellar, err := scval.WrapBool(funderIdx) + if err != nil { + return xdr.ScVec{}, err + } + + fundArgs := xdr.ScVec{ + channelID, + userIdStellar, + } + return fundArgs, nil +} + +func BuildGetChannelTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { + + chanIDStellar := chanID[:] + var chanid xdr.ScBytes + copy(chanid[:], chanIDStellar) + channelID, err := scval.WrapScBytes(chanIDStellar) + if err != nil { + return xdr.ScVec{}, err + } + + getChannelArgs := xdr.ScVec{ + channelID, + } + return getChannelArgs, nil +} + +func BuildForceCloseTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { + + chanIDStellar := chanID[:] + var chanid xdr.ScBytes + copy(chanid[:], chanIDStellar) + channelID, err := scval.WrapScBytes(chanIDStellar) + if err != nil { + return xdr.ScVec{}, err + } + + getChannelArgs := xdr.ScVec{ + channelID, + } + return getChannelArgs, nil +} + +//InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) + +func DecodeTxMeta(tx horizon.Transaction) (xdr.TransactionMeta, error) { + var transactionMeta xdr.TransactionMeta + err := xdr.SafeUnmarshalBase64(tx.ResultMetaXdr, &transactionMeta) + if err != nil { + return xdr.TransactionMeta{}, err + } + + return transactionMeta, nil +} diff --git a/channel/event.go b/channel/event.go new file mode 100644 index 0000000..29ce53e --- /dev/null +++ b/channel/event.go @@ -0,0 +1,394 @@ +package channel + +import ( + "errors" + "fmt" + "github.com/stellar/go/xdr" + pchannel "perun.network/go-perun/channel" + "perun.network/perun-stellar-backend/wire" + "time" +) + +type Version = uint64 +type Event = xdr.ContractEvent +type EventType int + +const ( + EventTypeOpen EventType = iota + EventTypeFundChannel // participant/s funding channel + EventTypeFundedChannel // participants have funded channel + EventTypeClosed // channel closed -> withdrawing enabled + EventTypeWithdrawing // participant/s withdrawing + EventTypeWithdrawn // participants have withdrawn + EventTypeForceClosed // participant has force closed the channel + EventTypeDisputed // participant has disputed the channel +) + +const AssertPerunSymbol = "perun" + +var ( + STELLAR_PERUN_CHANNEL_CONTRACT_TOPICS = map[xdr.ScSymbol]EventType{ + xdr.ScSymbol("open"): EventTypeOpen, + xdr.ScSymbol("fund"): EventTypeFundChannel, + xdr.ScSymbol("fund_c"): EventTypeFundedChannel, + xdr.ScSymbol("closed"): EventTypeClosed, + xdr.ScSymbol("withdraw"): EventTypeWithdrawing, + xdr.ScSymbol("pay_c"): EventTypeWithdrawn, + xdr.ScSymbol("f_closed"): EventTypeForceClosed, + xdr.ScSymbol("dispute"): EventTypeDisputed, + } + + ErrNotStellarPerunContract = errors.New("event was not from a Perun payment channel contract") + ErrEventUnsupported = errors.New("this type of event is unsupported") + ErrEventIntegrity = errors.New("contract ID does not match payment channel + passphrase") +) + +type controlsState map[string]bool + +type ( + + // PerunEvent is a Perun event. + PerunEvent interface { + ID() pchannel.ID + Timeout() pchannel.Timeout + Version() Version + } + + OpenEvent struct { + Channel wire.Channel + IDV pchannel.ID + VersionV Version + Timestamp uint64 + } + FundEvent struct { + Channel wire.Channel + IDV pchannel.ID + VersionV Version + Timestamp uint64 + } + + CloseEvent struct { + Channel wire.Channel + IDV pchannel.ID + VersionV Version + Timestamp uint64 + } + + WithdrawnEvent struct { + Channel wire.Channel + IDV pchannel.ID + VersionV Version + Timestamp uint64 + } + + DisputedEvent struct { + Channel wire.Channel + IDV pchannel.ID + VersionV Version + Timestamp uint64 + } + + // ConcludedEvent struct { + // Finalized bool + // Alloc [2]uint64 + // Tout uint64 + // Timestamp uint64 + // concluded bool + // VersionV Version + // IDV pchannel.ID + // } +) + +type StellarEvent struct { + Type EventType + ChannelState wire.Channel +} + +func (e StellarEvent) GetType() EventType { + return e.Type +} + +func (e OpenEvent) GetChannel() wire.Channel { + return e.Channel +} + +func (e OpenEvent) ID() pchannel.ID { + return e.IDV +} +func (e OpenEvent) Version() Version { + return e.VersionV +} +func (e OpenEvent) Tstamp() uint64 { + return e.Timestamp +} + +func (e OpenEvent) Timeout() pchannel.Timeout { + when := time.Now().Add(10 * time.Second) + pollInterval := 1 * time.Second + return NewTimeout(when, pollInterval) +} + +func (e WithdrawnEvent) GetChannel() wire.Channel { + return e.Channel +} + +func (e WithdrawnEvent) ID() pchannel.ID { + return e.IDV +} +func (e WithdrawnEvent) Version() Version { + return e.VersionV +} +func (e WithdrawnEvent) Tstamp() uint64 { + return e.Timestamp +} + +func (e WithdrawnEvent) Timeout() pchannel.Timeout { + when := time.Now().Add(10 * time.Second) + pollInterval := 1 * time.Second + return NewTimeout(when, pollInterval) +} + +func (e CloseEvent) GetChannel() wire.Channel { + return e.Channel +} + +func (e CloseEvent) ID() pchannel.ID { + return e.IDV +} +func (e CloseEvent) Version() Version { + return e.VersionV +} +func (e CloseEvent) Tstamp() uint64 { + return e.Timestamp +} + +func (e CloseEvent) Timeout() pchannel.Timeout { + when := time.Now().Add(10 * time.Second) + pollInterval := 1 * time.Second + return NewTimeout(when, pollInterval) +} + +func (e FundEvent) Timeout() pchannel.Timeout { + when := time.Now().Add(10 * time.Second) + pollInterval := 1 * time.Second + return NewTimeout(when, pollInterval) +} + +func (e FundEvent) ID() pchannel.ID { + return e.IDV +} +func (e FundEvent) Version() Version { + return e.VersionV +} +func (e FundEvent) Tstamp() uint64 { + return e.Timestamp +} + +func (e DisputedEvent) Timeout() pchannel.Timeout { + when := time.Now().Add(10 * time.Second) + pollInterval := 1 * time.Second + return NewTimeout(when, pollInterval) +} + +func (e DisputedEvent) ID() pchannel.ID { + return e.IDV +} +func (e DisputedEvent) Version() Version { + return e.VersionV +} +func (e DisputedEvent) Tstamp() uint64 { + return e.Timestamp +} + +func DecodeEvents(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { + evs := make([]PerunEvent, 0) + + txEvents := txMeta.V3.SorobanMeta.Events + + fmt.Println("txEvents: ", txEvents) + + for _, ev := range txEvents { + sev := StellarEvent{} + topics := ev.Body.V0.Topics + + if len(topics) < 2 { + panic(ErrNotStellarPerunContract) + } + perunString, ok := topics[0].GetSym() + if perunString != AssertPerunSymbol { + panic(ErrNotStellarPerunContract) + } + if !ok { + return []PerunEvent{}, ErrNotStellarPerunContract + } + + fn, ok := topics[1].GetSym() + if !ok { + return []PerunEvent{}, ErrNotStellarPerunContract + } + + if eventType, found := STELLAR_PERUN_CHANNEL_CONTRACT_TOPICS[fn]; !found { + return []PerunEvent{}, ErrNotStellarPerunContract + } else { + sev.Type = eventType + } + + switch sev.GetType() { + case EventTypeOpen: + + openEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) + if err != nil { + return []PerunEvent{}, err + } + + controlsOpen := initControlState(openEventchanStellar.Control) + + err = checkOpen(controlsOpen) + if err != nil { + fmt.Println(err) + } + + openEvent := OpenEvent{ + Channel: openEventchanStellar, + } + + evs = append(evs, openEvent) + + case EventTypeFundChannel: + fundEventchanStellar, _, err := GetChannelBoolFromEvents(ev.Body.V0.Data) + if err != nil { + return []PerunEvent{}, err + } + + fundEvent := FundEvent{ + Channel: fundEventchanStellar, + } + evs = append(evs, fundEvent) + + case EventTypeClosed: + closedEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) + if err != nil { + return []PerunEvent{}, err + } + + closeEvent := CloseEvent{ + Channel: closedEventchanStellar, + } + evs = append(evs, closeEvent) + } + + } + return evs, nil +} + +func initControlState(control wire.Control) controlsState { + return controlsState{ + "ControlFundedA": control.FundedA, + "ControlFundedB": control.FundedB, + "ControlClosed": control.Closed, + "ControlWithdrawnA": control.WithdrawnA, + "ControlWithdrawnB": control.WithdrawnB, + "ControlDisputed": control.Disputed, + } +} + +func GetChannelFromEvents(evData xdr.ScVal) (wire.Channel, error) { + var chanStellar wire.Channel + + err := chanStellar.FromScVal(evData) + if err != nil { + return wire.Channel{}, err + } + + if err != nil { + return wire.Channel{}, err + + } + + return chanStellar, nil +} + +func GetChannelBoolFromEvents(evData xdr.ScVal) (wire.Channel, bool, error) { + var chanStellar wire.Channel + + mvec, ok := evData.GetVec() + if !ok { + return wire.Channel{}, false, errors.New("expected vec") + } + + vecVals := *mvec + eventBool := vecVals[1] + eventControl := vecVals[0] + err := chanStellar.FromScVal(eventControl) + if err != nil { + return wire.Channel{}, false, err + } + + boolIdx, ok := eventBool.GetB() + if !ok { + return wire.Channel{}, false, errors.New("expected bool") + } + + return chanStellar, boolIdx, nil +} + +func checkOpen(cState controlsState) error { + for key, val := range cState { + if val { + return errors.New(key + " is not false") + } + } + return nil +} + +func (e CloseEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { + + chanID := chanState.State.ChannelID + var cid [32]byte + copy(cid[:], chanID[:]) + + e.IDV = cid + //e.VersionV = version + e.Timestamp = timestamp + e.Channel = chanState + return nil +} + +func (e FundEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { + + chanID := chanState.State.ChannelID + var cid [32]byte + copy(cid[:], chanID[:]) + + e.IDV = cid + //e.VersionV = version + e.Timestamp = timestamp + e.Channel = chanState + return nil +} + +func (e WithdrawnEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { + + chanID := chanState.State.ChannelID + var cid [32]byte + copy(cid[:], chanID[:]) + + e.IDV = cid + //e.VersionV = version + e.Timestamp = timestamp + e.Channel = chanState + return nil +} + +func (e DisputedEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { + + chanID := chanState.State.ChannelID + var cid [32]byte + copy(cid[:], chanID[:]) + + e.IDV = cid + //e.VersionV = version + e.Timestamp = timestamp + e.Channel = chanState + return nil +} diff --git a/channel/funder.go b/channel/funder.go new file mode 100644 index 0000000..f7c2912 --- /dev/null +++ b/channel/funder.go @@ -0,0 +1,350 @@ +package channel + +import ( + "context" + "errors" + //"fmt" + "github.com/stellar/go/keypair" + "log" + pchannel "perun.network/go-perun/channel" + "perun.network/perun-stellar-backend/channel/env" + //"perun.network/perun-stellar-backend/client" + "perun.network/perun-stellar-backend/wallet" + "perun.network/perun-stellar-backend/wire" + + "time" +) + +const MaxIterationsUntilAbort = 10 +const DefaultPollingInterval = time.Duration(5) * time.Second + +type Funder struct { + //integrEnv env.IntegrationTestEnv + stellarClient *env.StellarClient + acc *wallet.Account + kpFull *keypair.Full + maxIters int + pollingInterval time.Duration +} + +func NewFunder(acc *wallet.Account, stellarClient *env.StellarClient) *Funder { + return &Funder{ + //integrEnv: *env.NewBackendEnv(), + stellarClient: stellarClient, + acc: acc, + maxIters: MaxIterationsUntilAbort, + pollingInterval: DefaultPollingInterval, + } +} + +func (f Funder) Fund(ctx context.Context, req pchannel.FundingReq) error { + log.Println("Fund called") + + switch req.Idx { + case 0: + return f.fundPartyA(ctx, req) + case 1: + return f.fundPartyB(ctx, req) + default: + return errors.New("invalid index") + } +} + +func (f Funder) fundPartyA(ctx context.Context, req pchannel.FundingReq) error { + err := f.OpenChannel(ctx, req.Params, req.State) + if err != nil { + return err + } + + // fund the channel: + + err = f.FundChannel(ctx, req.Params, req.State, false) + if err != nil { + return err + } + + // look for channel ID in events + //chanID := req.State.ID + + // await response from party B + +polling: + for i := 0; i < f.maxIters; i++ { + select { + case <-ctx.Done(): + return f.AbortChannel(ctx, req.Params, req.State) + case <-time.After(f.pollingInterval): + chanState, err := f.GetChannelState(ctx, req.Params, req.State) + if err != nil { + continue polling + } + + if chanState.Control.FundedA && chanState.Control.FundedB { + return nil + } + + } + } + return f.AbortChannel(ctx, req.Params, req.State) +} + +func (f Funder) fundPartyB(ctx context.Context, req pchannel.FundingReq) error { +polling: + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(f.pollingInterval): + log.Println("Party B: Polling for opened channel...") + chanState, err := f.GetChannelState(ctx, req.Params, req.State) + if err != nil { + log.Println("Party B: Error while polling for opened channel:", err) + continue polling + } + log.Println("Party B: Found opened channel!") + // Optional: make some channel checks here + if chanState.Control.FundedA && chanState.Control.FundedB { + return nil + } + return f.FundChannel(ctx, req.Params, req.State, true) + } + } +} + +func (f Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { + + //env := f.integrEnv + + contractAddress := f.stellarClient.GetContractIDAddress() + kp := f.kpFull + reqAlice := f.stellarClient.GetHorizonAcc() + + // generate tx to open the channel + openTxArgs := env.BuildOpenTxArgs(params, state) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "open", openTxArgs, contractAddress, kp) + + // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) + + // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + + // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) + // if err != nil { + // return errors.New("error while submitting operations with fee") + // } + + // read out decoded Events and interpret them + // txMeta, err := env.DecodeTxMeta(tx) + // if err != nil { + // return errors.New("error while decoding tx meta") + // } + _, err = DecodeEvents(txMeta) + if err != nil { + return errors.New("error while decoding events") + } + + return nil +} + +func (f Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { + + //env := f.integrEnv + + contractAddress := f.stellarClient.GetContractIDAddress() + kp := f.kpFull + reqAlice := f.stellarClient.GetHorizonAcc() + chanId := state.ID + + // generate tx to open the channel + openTxArgs, err := env.BuildFundTxArgs(chanId, funderIdx) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "fund", openTxArgs, contractAddress, kp) + + // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) + + // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + + // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) + // if err != nil { + // return errors.New("error while submitting operations with fee") + // } + + // read out decoded Events and interpret them + // txMeta, err := env.DecodeTxMeta(tx) + // if err != nil { + // return errors.New("error while decoding tx meta") + // } + _, err = DecodeEvents(txMeta) + if err != nil { + return errors.New("error while decoding events") + } + + return nil +} + +// func (f Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { + +// //env := f.integrEnv +// contractAddress := f.integrEnv.GetContractIDAddress() +// kp := f.kpFull +// acc := f.integrEnv.AccountDetails(kp) +// chanID := state.ID +// // generate tx to open the channel +// fundTxArgs, err := env.BuildFundTxArgs(chanID, funderIdx) +// if err != nil { +// return errors.New("error while building fund tx") +// } +// invokeHostFunctionOp := env.BuildContractCallOp(acc, "fund", fundTxArgs, contractAddress) + +// preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) + +// tx, err := f.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) +// if err != nil { +// return errors.New("error while submitting operations with fee") +// } + +// // read out decoded Events and interpret them +// txMeta, err := env.DecodeTxMeta(tx) +// if err != nil { +// return errors.New("error while decoding tx meta") +// } +// _, err = DecodeEvents(txMeta) +// if err != nil { +// return errors.New("error while decoding events") +// } + +// return nil +// } + +func (f Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { + + contractAddress := f.stellarClient.GetContractIDAddress() + kp := f.kpFull + reqAlice := f.stellarClient.GetHorizonAcc() + chanId := state.ID + + // generate tx to open the channel + openTxArgs, err := env.BuildGetChannelTxArgs(chanId) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp) + + // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) + + // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + + // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) + // if err != nil { + // return errors.New("error while submitting operations with fee") + // } + + // read out decoded Events and interpret them + // txMeta, err := env.DecodeTxMeta(tx) + // if err != nil { + // return errors.New("error while decoding tx meta") + // } + _, err = DecodeEvents(txMeta) + if err != nil { + return errors.New("error while decoding events") + } + + return nil +} + +// func (f Funder) Abort(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { + +// //env := f.integrEnv +// contractAddress := f.integrEnv.GetContractIDAddress() +// kp := f.kpFull +// reqAlice := f.integrEnv.AccountDetails(kp) +// // generate tx to open the channel +// openTxArgs := env.BuildGetChannelTxArgs(chanId) +// invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "abort_channel", openTxArgs, contractAddress) + +// preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + +// tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) +// if err != nil { +// return errors.New("error while submitting operations with fee") +// } + +// // read out decoded Events and interpret them +// txMeta, err := env.DecodeTxMeta(tx) +// if err != nil { +// return errors.New("error while decoding tx meta") +// } +// _, err = DecodeEvents(txMeta) +// if err != nil { +// return errors.New("error while decoding events") +// } + +// return nil +// } + +func (f Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { + + //env := f.integrEnv + + contractAddress := f.stellarClient.GetContractIDAddress() + kp := f.kpFull + reqAlice := f.stellarClient.GetHorizonAcc() + chanId := state.ID + + // generate tx to open the channel + openTxArgs, err := env.BuildGetChannelTxArgs(chanId) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp) + + // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) + + // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + + // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) + // if err != nil { + // return errors.New("error while submitting operations with fee") + // } + + // read out decoded Events and interpret them + + retVal := txMeta.V3.SorobanMeta.ReturnValue + var getChan wire.Channel + + err = getChan.FromScVal(retVal) + if err != nil { + return wire.Channel{}, errors.New("error while decoding return value") + } + return getChan, nil + +} + +// func (f Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { + +// //env := f.integrEnv +// contractAddress := f.integrEnv.GetContractIDAddress() +// kp := f.kpFull +// acc := f.integrEnv.AccountDetails(kp) +// chanID := state.ID +// // generate tx to open the channel +// getChTxArgs, err := env.BuildGetChannelTxArgs(chanID) +// if err != nil { +// return wire.Channel{}, errors.New("error while building fund tx") +// } +// invokeHostFunctionOp := env.BuildContractCallOp(acc, "get_channel", getChTxArgs, contractAddress) + +// preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) + +// tx, err := f.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) +// if err != nil { +// return wire.Channel{}, errors.New("error while submitting operations with fee") +// } + +// txMeta, err := env.DecodeTxMeta(tx) +// if err != nil { +// return wire.Channel{}, errors.New("error while decoding tx meta") +// } + +// retVal := txMeta.V3.SorobanMeta.ReturnValue +// var getChan wire.Channel + +// err = getChan.FromScVal(retVal) +// if err != nil { +// return wire.Channel{}, errors.New("error while decoding return value") +// } +// return wire.Channel{}, nil +// } diff --git a/channel/funder_test.go b/channel/funder_test.go new file mode 100644 index 0000000..7f86d30 --- /dev/null +++ b/channel/funder_test.go @@ -0,0 +1,222 @@ +package channel_test + +import ( + "fmt" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chtest "perun.network/perun-stellar-backend/channel/test" + + //client "perun.network/perun-stellar-backend/client" + _ "perun.network/perun-stellar-backend/wallet/test" + "perun.network/perun-stellar-backend/wire" + + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/env" + + "testing" +) + +func TestOpenChannel(t *testing.T) { + itest := env.NewIntegrationEnv(t) + + kps, _ := itest.CreateAccounts(2, "10000000") + + kpAlice := kps[0] + kpBob := kps[1] + + reqAlice := itest.AccountDetails(kpAlice) + _ = itest.AccountDetails(kpBob) + + installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), channel.PerunContractPath) + preFlightOp, minFee := itest.PreflightHostFunctions(&reqAlice, *installContractOp) + _ = itest.MustSubmitOperationsWithFee(&reqAlice, kpAlice, 5*minFee, &preFlightOp) + + // Create the contract + + createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), channel.PerunContractPath, "a1", itest.GetPassPhrase()) + preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *createContractOp) + _, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + + require.NoError(t, err) + + // contract has been deployed, now invoke 'open' fn on the contract + contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + require.NotNil(t, contractID) + contractIDAddress := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: contractID, + } + + perunFirstParams, perunFirstState := chtest.NewParamsState(t) + + openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) + + invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress) + + preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + + tx, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + require.NoError(t, err) + + //clientTx, err := itest.Client().TransactionDetail(tx.Hash) + //fmt.Println("clientTx: ", clientTx) + //require.NoError(t, err) + + //assert.Equal(t, tx.Hash, clientTx.Hash) + //var txResult xdr.TransactionResult + //err = xdr.SafeUnmarshalBase64(clientTx.ResultXdr, &txResult) + //require.NoError(t, err) + + //opResults, ok := txResult.OperationResults() + + // assert.True(t, ok) + // assert.Equal(t, len(opResults), 1) + // invokeHostFunctionResult, ok := opResults[0].MustTr().GetInvokeHostFunctionResult() + // assert.True(t, ok) + //assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) + + txMeta, err := env.DecodeTxMeta(tx) + require.NoError(t, err) + + perunEvents, err := channel.DecodeEvents(txMeta) + require.NoError(t, err) + + fmt.Println("perunEvents: ", perunEvents) + +} + +func TestFundChannel(t *testing.T) { + itest := env.NewBackendEnv() + + kps, _ := itest.CreateAccounts(2, "10000000") + + kpAlice := kps[0] + kpBob := kps[1] + + reqAlice := itest.AccountDetails(kpAlice) + reqBob := itest.AccountDetails(kpBob) + + fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) + + installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), channel.PerunContractPath) + preFlightOp, minFee := itest.PreflightHostFunctions(&reqAlice, *installContractOp) + _ = itest.MustSubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + + // Create the contract + + createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), channel.PerunContractPath, "a1", itest.GetPassPhrase()) + preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *createContractOp) + _, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + + require.NoError(t, err) + + // contract has been deployed, now invoke 'open' fn on the contract + contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + require.NotNil(t, contractID) + contractIDAddress := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: contractID, + } + + // invoke open-channel function + + perunFirstParams, perunFirstState := chtest.NewParamsState(t) + + openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) + + invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress) + + preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + + tx, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + require.NoError(t, err) + + clientTx, err := itest.Client().TransactionDetail(tx.Hash) + fmt.Println("clientTx: ", clientTx) + require.NoError(t, err) + + assert.Equal(t, tx.Hash, clientTx.Hash) + var txResult xdr.TransactionResult + err = xdr.SafeUnmarshalBase64(clientTx.ResultXdr, &txResult) + require.NoError(t, err) + + opResults, ok := txResult.OperationResults() + + assert.True(t, ok) + assert.Equal(t, len(opResults), 1) + invokeHostFunctionResult, ok := opResults[0].MustTr().GetInvokeHostFunctionResult() + assert.True(t, ok) + assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) + + txMeta, err := env.DecodeTxMeta(tx) + require.NoError(t, err) + + perunEvents, err := channel.DecodeEvents(txMeta) + require.NoError(t, err) + + fmt.Println("perunEvents: ", perunEvents) + + // fund the channel after it is open + + chanID := perunFirstParams.ID() + aliceIdx := false + + fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) + require.NoError(t, err) + + invokeHostFunctionOpFund := env.BuildContractCallOp(reqAlice, "fund", fundArgsAlice, contractIDAddress) + + preFlightOpFund, minFeeFund := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpFund) + + txFund, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeFund, &preFlightOpFund) + fmt.Println("minFeeFund: ", minFeeFund) + require.NoError(t, err) + + txMetaFund, err := env.DecodeTxMeta(txFund) + require.NoError(t, err) + + perunEventsFund, err := channel.DecodeEvents(txMetaFund) + require.NoError(t, err) + fmt.Println("perunEventsFund: ", perunEventsFund) + + bobIdx := true + fundArgsBob, err := env.BuildFundTxArgs(chanID, bobIdx) + require.NoError(t, err) + invokeHostFunctionOpFund2 := env.BuildContractCallOp(reqBob, "fund", fundArgsBob, contractIDAddress) + + preFlightOpFund2, minFeeFund2 := itest.PreflightHostFunctions(&reqBob, *invokeHostFunctionOpFund2) + fmt.Println("minFeeFund2: ", minFeeFund2) + + txFund2, err := itest.SubmitOperationsWithFee(&reqBob, kpBob, minFeeFund2, &preFlightOpFund2) + + require.NoError(t, err) + txMetaFund2, err := env.DecodeTxMeta(txFund2) + require.NoError(t, err) + fmt.Println("txMetaFund2: ", txMetaFund2) + perunEventsFund2, err := channel.DecodeEvents(txMetaFund2) + require.NoError(t, err) + fmt.Println("perunEventsFund2: ", perunEventsFund2) + + // qurey channel state + + getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) + require.NoError(t, err) + + invokeHostFunctionOpGetCh := env.BuildContractCallOp(reqAlice, "get_channel", getChannelArgs, contractIDAddress) + preFlightOpGetCh, minFeeGetCh := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpGetCh) + txGetCh, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeGetCh, &preFlightOpGetCh) + require.NoError(t, err) + + txMetaGetCh, err := env.DecodeTxMeta(txGetCh) + require.NoError(t, err) + fmt.Println("txMetaGetCh: ", txMetaGetCh) + retVal := txMetaGetCh.V3.SorobanMeta.ReturnValue + fmt.Println("retVal: ", retVal) + + var getChan wire.Channel + + err = getChan.FromScVal(retVal) + require.NoError(t, err) + fmt.Println("getChan: ", getChan) +} diff --git a/channel/subscribe.go b/channel/subscribe.go new file mode 100644 index 0000000..c8422d1 --- /dev/null +++ b/channel/subscribe.go @@ -0,0 +1,73 @@ +package channel + +import ( + pchannel "perun.network/go-perun/channel" +) + +// Next implements the AdjudicatorSub.Next function. +func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { + if s.closer.IsClosed() { + return nil + } + + if s.Events() == nil { + return nil + } + select { + case event := <-s.Events(): + if event == nil { + return nil + } + + timestamp := event.Tstamp() + + switch e := event.(type) { + case *DisputedEvent: + + dispEvent := pchannel.AdjudicatorEventBase{ + VersionV: e.Version(), + IDV: e.ID(), + TimeoutV: MakeTimeout(timestamp), + } + + ddn := &pchannel.RegisteredEvent{AdjudicatorEventBase: dispEvent, + State: nil, + Sigs: nil} + s.closer.Close() + return ddn + case *CloseEvent: + conclEvent := pchannel.AdjudicatorEventBase{ + VersionV: e.Version(), + IDV: e.ID(), + TimeoutV: MakeTimeout(timestamp), + } + ccn := &pchannel.ConcludedEvent{ + AdjudicatorEventBase: conclEvent, + } + s.closer.Close() + return ccn + default: + return nil + } + + case <-s.closer.Closed(): + return nil + } +} + +func (s *AdjEventSub) Close() error { + s.closer.Close() + return nil +} + +func (s *AdjEventSub) Events() <-chan AdjEvent { + return s.events +} + +func (s *AdjEventSub) Err() error { + return s.err +} + +func (s *AdjEventSub) PanicErr() <-chan error { + return s.panicErr +} diff --git a/channel/test/setup.go b/channel/test/setup.go new file mode 100644 index 0000000..6200b82 --- /dev/null +++ b/channel/test/setup.go @@ -0,0 +1,51 @@ +package test + +import ( + "math/big" + "testing" + + //"perun.network/go-perun/backend/sim/wallet" + pchannel "perun.network/go-perun/channel" + pwallet "perun.network/go-perun/wallet" + //pwtest "perun.network/go-perun/wallet/test" + + ptest "perun.network/go-perun/channel/test" + pkgtest "polycry.pt/poly-go/test" +) + +func NewParamsState(t *testing.T) (*pchannel.Params, *pchannel.State) { + + rng := pkgtest.Prng(t) + + numParts := 2 + + return ptest.NewRandomParamsAndState(rng, ptest.WithNumLocked(0).Append( + ptest.WithVersion(0), + ptest.WithNumParts(numParts), + ptest.WithIsFinal(false), + ptest.WithLedgerChannel(true), + ptest.WithVirtualChannel(false), + ptest.WithNumAssets(1), + ptest.WithoutApp(), + ptest.WithBalancesInRange(big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000)), big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000))), + )) +} + +func NewParamsWithAddressState(t *testing.T, partsAddr []pwallet.Address) (*pchannel.Params, *pchannel.State) { + + rng := pkgtest.Prng(t) + + numParts := 2 + + return ptest.NewRandomParamsAndState(rng, ptest.WithNumLocked(0).Append( + ptest.WithVersion(0), + ptest.WithNumParts(numParts), + ptest.WithParts(partsAddr...), + ptest.WithIsFinal(false), + ptest.WithLedgerChannel(true), + ptest.WithVirtualChannel(false), + ptest.WithNumAssets(1), + ptest.WithoutApp(), + ptest.WithBalancesInRange(big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000)), big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000))), + )) +} diff --git a/channel/timeout.go b/channel/timeout.go new file mode 100644 index 0000000..a23889b --- /dev/null +++ b/channel/timeout.go @@ -0,0 +1,102 @@ +// Copyright 2023 - See NOTICE file for copyright holders. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package channel + +import ( + "context" + "perun.network/go-perun/log" + "time" +) + +type ( + // ExpiredTimeout is always expired. + // Implements the Perun Timeout interface. + ExpiredTimeout struct{} + + // Timeout can be used to wait until a specific timepoint is reached by + // the blockchain. Implements the Perun Timeout interface. + Timeout struct { + log.Embedding + + when time.Time + pollInterval time.Duration + } + + // TimePoint as defined by pallet Timestamp. + TimePoint uint64 +) + +// DefaultTimeoutPollInterval default value for the PollInterval of a Timeout. +const DefaultTimeoutPollInterval = 1 * time.Second + +// NewExpiredTimeout returns a new ExpiredTimeout. +func NewExpiredTimeout() *ExpiredTimeout { + return &ExpiredTimeout{} +} + +func (*ExpiredTimeout) IsElapsed(context.Context) bool { + return true +} + +// Wait returns nil. +func (*ExpiredTimeout) Wait(context.Context) error { + return nil +} + +// NewTimeout returns a new Timeout which expires at the given time. +func NewTimeout(when time.Time, pollInterval time.Duration) *Timeout { + return &Timeout{log.MakeEmbedding(log.Default()), when, pollInterval} +} + +// IsElapsed returns whether the timeout is elapsed. +func (t *Timeout) IsElapsed(ctx context.Context) bool { + now := time.Now() + + elapsed := t.when.Before(now) || t.when.Equal(now) + + delta := now.Sub(t.when) + if elapsed { + t.Log().Printf("Timeout elapsed since %v", delta) + } else { + t.Log().Printf("Timeout target in %v", delta) + } + + return elapsed +} + +// Wait waits for the timeout or until the context is cancelled. +func (t *Timeout) Wait(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(t.pollInterval): + if t.IsElapsed(ctx) { + return nil + } + } + } +} + +// MakeTimeout creates a new timeout. +func MakeTimeout(challDurSec uint64) *Timeout { + expirationTime := time.Now().Add(MakeTime(challDurSec)) + return NewTimeout(expirationTime, DefaultTimeoutPollInterval) +} + +// MakeTime creates a new time from the argument. +func MakeTime(challDurSec uint64) time.Duration { + return time.Duration(challDurSec) * time.Second +} diff --git a/client/channel.go b/client/channel.go new file mode 100644 index 0000000..ee52a3e --- /dev/null +++ b/client/channel.go @@ -0,0 +1,124 @@ +package client + +import ( + "context" + "encoding/hex" + "fmt" + "log" + "math/big" + + "perun.network/go-perun/channel" + "perun.network/go-perun/client" + "strconv" +) + +// PaymentChannel is a wrapper for a Perun channel for the payment use case. +type PaymentChannel struct { + ch *client.Channel + currency channel.Asset +} + +func (c *PaymentChannel) GetChannel() *client.Channel { + return c.ch +} +func (c *PaymentChannel) GetChannelParams() *channel.Params { + return c.ch.Params() +} + +func (c *PaymentChannel) GetChannelState() *channel.State { + return c.ch.State() +} + +// newPaymentChannel creates a new payment channel. +func newPaymentChannel(ch *client.Channel, currency channel.Asset) *PaymentChannel { + return &PaymentChannel{ + ch: ch, + currency: currency, + } +} + +// SendPayment sends a payment to the channel peer. +func (c PaymentChannel) SendPayment(amount int64) { + // Transfer the given amount from us to peer. + // Use UpdateBy to update the channel state. + err := c.ch.Update(context.TODO(), func(state *channel.State) { + icp := big.NewInt(amount) + actor := c.ch.Idx() + peer := 1 - actor + state.Allocation.TransferBalance(actor, peer, c.currency, icp) + }) + if err != nil { + panic(err) // We panic on error to keep the code simple. + } +} + +// func (p *PaymentClient) SendPaymentToPeer(amount float64) { +// if !p.HasOpenChannel() { +// return +// } +// amountInt64 := int64(amount) +// p.Channel.SendPayment(amountInt64) +// } + +// Settle settles the payment channel and withdraws the funds. +func (c PaymentChannel) Settle() { + // If the channel is not finalized: Finalize the channel to enable fast settlement. + + if !c.ch.State().IsFinal { + err := c.ch.Update(context.TODO(), func(state *channel.State) { + state.IsFinal = true + }) + if err != nil { + panic(err) + } + } + + // Settle concludes the channel and withdraws the funds. + err := c.ch.Settle(context.TODO(), false) + if err != nil { + panic(err) + } + + // Close frees up channel resources. + c.ch.Close() +} + +func FormatState(c *PaymentChannel, state *channel.State) string { + id := c.ch.ID() + parties := c.ch.Params().Parts + + bigIntA := state.Allocation.Balance(0, c.currency) + bigFloatA := new(big.Float).SetInt(bigIntA) + balA, _ := bigFloatA.Float64() + balAStr := strconv.FormatFloat(balA, 'f', 4, 64) + + fstPartyPaymentAddr := parties[0].String() + sndPartyPaymentAddr := parties[1].String() + + bigIntB := state.Allocation.Balance(1, c.currency) + bigFloatB := new(big.Float).SetInt(bigIntB) + balB, _ := bigFloatB.Float64() + + balBStr := strconv.FormatFloat(balB, 'f', 4, 64) + if len(parties) != 2 { + log.Fatalf("invalid parties length: " + strconv.Itoa(len(parties))) + } + ret := fmt.Sprintf( + "Channel ID: [green]%s[white]\nBalances:\n %s: [green]%s[white] IC Token\n %s: [green]%s[white] IC Token\nFinal: [green]%t[white]\nVersion: [green]%d[white]", + hex.EncodeToString(id[:]), + fstPartyPaymentAddr, + balAStr, + sndPartyPaymentAddr, + balBStr, + state.IsFinal, + state.Version, + ) + return ret +} + +// func (p *PaymentClient) Settle() { +// if !p.HasOpenChannel() { +// return +// } +// p.Channel.Settle() +// } diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..53ed96f --- /dev/null +++ b/client/client.go @@ -0,0 +1,202 @@ +package client + +import ( + "fmt" + "github.com/stellar/go/keypair" + "math/big" + "perun.network/go-perun/wire/net/simple" + + "errors" + pchannel "perun.network/go-perun/channel" + "perun.network/go-perun/client" + "perun.network/go-perun/watcher/local" + "perun.network/go-perun/wire" + + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/types" + + "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/wallet" +) + +type PaymentClient struct { + perunClient *client.Client + account *wallet.Account + currency pchannel.Asset + channels chan *PaymentChannel + Channel *PaymentChannel + //ICConn *chanconn.Connector + wAddr wire.Address + balance *big.Int +} + +func SetupPaymentClient( + stellarEnv *env.IntegrationTestEnv, + w *wallet.EphemeralWallet, // w is the wallet used to resolve addresses to accounts for channels. + acc *wallet.Account, + stellarKp *keypair.Full, + stellarTokenID types.StellarAsset, + //acc *wallet.Account, + bus *wire.LocalBus, + +) (*PaymentClient, error) { + + // Connect to Perun pallet and get funder + adjudicator from it. + + perunConn := env.NewStellarClient(stellarEnv, stellarKp) + + funder := channel.NewFunder(acc, perunConn) + adj := channel.NewAdjudicator(acc, perunConn) + + // Setup dispute watcher. + watcher, err := local.NewWatcher(adj) + if err != nil { + return nil, fmt.Errorf("intializing watcher: %w", err) + } + + // Setup Perun client. + wireAddr := simple.NewAddress(acc.Address().String()) + perunClient, err := client.New(wireAddr, bus, funder, adj, w, watcher) + if err != nil { + return nil, errors.New("creating client") + } + //asset := types.NewStellarAsset(big.NewInt(int64(chainID)), common.Address(assetaddr)) + + // Create client and start request handler. + c := &PaymentClient{ + perunClient: perunClient, + account: acc, + currency: &stellarTokenID, + channels: make(chan *PaymentChannel, 1), + //ICConn: perunConn, + wAddr: wireAddr, + balance: big.NewInt(0), + } + + //go c.PollBalances() + go perunClient.Handle(c, c) + return c, nil +} + +// startWatching starts the dispute watcher for the specified channel. +func (c *PaymentClient) startWatching(ch *client.Channel) { + go func() { + err := ch.Watch(c) + if err != nil { + fmt.Printf("Watcher returned with error: %v", err) + } + }() +} + +// func SetupPaymentClient( +// name string, +// w *wallet.FsWallet, // w is the wallet used to resolve addresses to accounts for channels. +// bus *wire.LocalBus, +// perunID string, +// ledgerID string, +// host string, +// port int, +// accountPath string, +// ) (*PaymentClient, error) { + +// acc := w.NewAccount() + +// // Connect to Perun pallet and get funder + adjudicator from it. + +// perunConn := chanconn.NewICConnector(perunID, ledgerID, accountPath, host, port) + +// funder := channel.NewFunder(acc, perunConn) +// adj := channel.NewAdjudicator(acc, perunConn) + +// // Setup dispute watcher. +// watcher, err := local.NewWatcher(adj) +// if err != nil { +// return nil, fmt.Errorf("intializing watcher: %w", err) +// } + +// // Setup Perun client. +// wireAddr := simple.NewAddress(acc.Address().String()) +// perunClient, err := client.New(wireAddr, bus, funder, adj, w, watcher) +// if err != nil { +// return nil, errors.WithMessage(err, "creating client") +// } + +// // Create client and start request handler. +// c := &PaymentClient{ +// Name: name, +// perunClient: perunClient, +// account: &acc, +// currency: channel.Asset, +// channels: make(chan *PaymentChannel, 1), +// ICConn: perunConn, +// wAddr: wireAddr, +// balance: big.NewInt(0), +// } + +// go c.PollBalances() +// go perunClient.Handle(c, c) +// return c, nil +// } + +// // SetupPaymentClient creates a new payment client. +// func SetupPaymentClient( +// bus wire.Bus, // bus is used of off-chain communication. +// w *swallet.Wallet, // w is the wallet used for signing transactions. +// acc common.Address, // acc is the address of the account to be used for signing transactions. +// eaddress *ethwallet.Address, // eaddress is the address of the Ethereum account to be used for signing transactions. +// nodeURL string, // nodeURL is the URL of the blockchain node. +// chainID uint64, // chainID is the identifier of the blockchain. +// adjudicator common.Address, // adjudicator is the address of the adjudicator. +// assetaddr ethwallet.Address, // asset is the address of the asset holder for our payment channels. +// ) (*PaymentClient, error) { +// // Create Ethereum client and contract backend. +// cb, err := CreateContractBackend(nodeURL, chainID, w) +// if err != nil { +// return nil, fmt.Errorf("creating contract backend: %w", err) +// } + +// // Validate contracts. +// err = ethchannel.ValidateAdjudicator(context.TODO(), cb, adjudicator) +// if err != nil { +// return nil, fmt.Errorf("validating adjudicator: %w", err) +// } +// err = ethchannel.ValidateAssetHolderETH(context.TODO(), cb, common.Address(assetaddr), adjudicator) +// if err != nil { +// return nil, fmt.Errorf("validating adjudicator: %w", err) +// } + +// // Setup funder. +// funder := ethchannel.NewFunder(cb) +// dep := ethchannel.NewETHDepositor() +// ethAcc := accounts.Account{Address: acc} +// asset := ethchannel.NewAsset(big.NewInt(int64(chainID)), common.Address(assetaddr)) +// funder.RegisterAsset(*asset, dep, ethAcc) + +// // Setup adjudicator. +// adj := ethchannel.NewAdjudicator(cb, adjudicator, acc, ethAcc) + +// // Setup dispute watcher. +// watcher, err := local.NewWatcher(adj) +// if err != nil { +// return nil, fmt.Errorf("intializing watcher: %w", err) +// } + +// // Setup Perun client. +// waddr := ðwire.Address{Address: eaddress} +// perunClient, err := client.New(waddr, bus, funder, adj, w, watcher) +// if err != nil { +// return nil, errors.WithMessage(err, "creating client") +// } + +// // Create client and start request handler. +// c := &PaymentClient{ +// perunClient: perunClient, +// account: eaddress, +// waddress: waddr, +// currency: asset, +// channels: make(chan *PaymentChannel, 1), +// } +// go perunClient.Handle(c, c) + +// return c, nil +// } diff --git a/client/handle.go b/client/handle.go new file mode 100644 index 0000000..fdc4a57 --- /dev/null +++ b/client/handle.go @@ -0,0 +1,105 @@ +package client + +import ( + "context" + "fmt" + "log" + "time" + + "perun.network/go-perun/channel" + "perun.network/go-perun/client" +) + +// HandleProposal is the callback for incoming channel proposals. +func (c *PaymentClient) HandleProposal(p client.ChannelProposal, r *client.ProposalResponder) { + lcp, err := func() (*client.LedgerChannelProposalMsg, error) { + // Ensure that we got a ledger channel proposal. + lcp, ok := p.(*client.LedgerChannelProposalMsg) + if !ok { + return nil, fmt.Errorf("Invalid proposal type: %T\n", p) + } + + // Check that we have the correct number of participants. + if lcp.NumPeers() != 2 { + return nil, fmt.Errorf("Invalid number of participants: %d", lcp.NumPeers()) + } + + // Check that the channel has the expected assets and funding balances. + const assetIdx, clientIdx, peerIdx = 0, 0, 1 + if err := channel.AssertAssetsEqual(lcp.InitBals.Assets, []channel.Asset{c.currency}); err != nil { + return nil, fmt.Errorf("Invalid assets: %v\n", err) + } else if lcp.FundingAgreement[assetIdx][clientIdx].Cmp(lcp.FundingAgreement[assetIdx][peerIdx]) != 0 { + return nil, fmt.Errorf("Invalid funding balance") + } + return lcp, nil + }() + if err != nil { + errReject := r.Reject(context.TODO(), err.Error()) + if errReject != nil { + // Log the error or take other action as needed + fmt.Printf("Error rejecting proposal: %v\n", errReject) + } + } + + // Create a channel accept message and send it. + accept := lcp.Accept( + c.account.Address(), // The account we use in the channel. + client.WithRandomNonce(), // Our share of the channel nonce. + ) + ch, err := r.Accept(context.TODO(), accept) + if err != nil { + fmt.Printf("Error accepting channel proposal: %v\n", err) + return + } + + // Start the on-chain event watcher. It automatically handles disputes. + c.startWatching(ch) + + // Store channel. + c.channels <- newPaymentChannel(ch, c.currency) + //c.AcceptedChannel() + +} + +// HandleUpdate is the callback for incoming channel updates. +func (c *PaymentClient) HandleUpdate(cur *channel.State, next client.ChannelUpdate, r *client.UpdateResponder) { + // We accept every update that increases our balance. + err := func() error { + err := channel.AssertAssetsEqual(cur.Assets, next.State.Assets) + if err != nil { + return fmt.Errorf("Invalid assets: %v", err) + } + + receiverIdx := 1 - next.ActorIdx // This works because we are in a two-party channel. + curBal := cur.Allocation.Balance(receiverIdx, c.currency) + nextBal := next.State.Allocation.Balance(receiverIdx, c.currency) + if nextBal.Cmp(curBal) < 0 { + return fmt.Errorf("Invalid balance: %v", nextBal) + } + return nil + }() + if err != nil { + r.Reject(context.TODO(), err.Error()) //nolint:errcheck // It's OK if rejection fails. + } + + // Send the acceptance message. + err = r.Accept(context.TODO()) + if err != nil { + panic(err) + } +} + +// HandleAdjudicatorEvent is the callback for smart contract events. +func (c *PaymentClient) HandleAdjudicatorEvent(e channel.AdjudicatorEvent) { + log.Printf("Adjudicator event: type = %T, client = %v", e, c.account.Address()) +} + +func (c *PaymentClient) GetChannel() (*PaymentChannel, error) { + select { + case channel := <-c.channels: + c.channels <- channel // Put the channel back into the channels channel. + return channel, nil + case <-time.After(time.Second): // Set a timeout duration (e.g., 1 second). + return nil, fmt.Errorf("no channel available") + } +} diff --git a/go.mod b/go.mod index 3687df9..46c97db 100644 --- a/go.mod +++ b/go.mod @@ -2,19 +2,83 @@ module perun.network/perun-stellar-backend go 1.19 -require github.com/stellar/go v0.0.0-20230905170723-6e18a2f2f583 +require github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 require ( - github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee + github.com/stellar/go-xdr v0.0.0-20230919160922-6c7b68458206 github.com/stretchr/testify v1.8.1 perun.network/go-perun v0.10.6 polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b ) +//replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => github.com/perun-network/go v0.0.0-20231003185205-facabfc2f4c4 +replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => ../stellarfork/go + require ( + github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 // indirect + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/Masterminds/squirrel v1.5.0 // indirect + github.com/Microsoft/go-winio v0.4.14 // indirect + github.com/adjust/goautoneg v0.0.0-20150426214442-d788f35a0315 // indirect + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/aws/aws-sdk-go v1.44.326 // indirect + github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect + github.com/creachadair/jrpc2 v0.41.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1 // indirect + github.com/go-chi/chi v4.0.3+incompatible // indirect + github.com/go-errors/errors v0.0.0-20150906023321-a41850380601 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/gorilla/schema v1.1.0 // indirect + github.com/guregu/null v2.1.3-0.20151024101046-79c5bd36b615+incompatible // indirect + github.com/hashicorp/golang-lru v0.5.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/holiman/uint256 v1.2.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmoiron/sqlx v1.2.0 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.2.0 // indirect + github.com/magiconair/properties v1.8.0 // indirect + github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/pelletier/go-toml v1.9.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 // indirect + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect + github.com/prometheus/common v0.2.0 // indirect + github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 // indirect + github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect + github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079 // indirect + github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/afero v1.1.2 // indirect + github.com/spf13/cast v1.3.0 // indirect + github.com/spf13/cobra v0.0.5 // indirect + github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/spf13/pflag v1.0.3 // indirect + github.com/spf13/viper v1.3.2 // indirect + github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/xdrpp/goxdr v0.1.1 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/gorp.v1 v1.7.1 // indirect + gopkg.in/tylerb/graceful.v1 v1.2.13 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ddebfba..64e1282 100644 --- a/go.sum +++ b/go.sum @@ -1,41 +1,276 @@ +github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 h1:vvMDiVd621MU1Djr7Ep7OXu8gHOtsdwrI4tjnIGvpTg= +github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95/go.mod h1:Gv4NIpY67KDahg+DtIG5/2Ok4l8vzYEekiirSCH+IGA= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= +github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/adjust/goautoneg v0.0.0-20150426214442-d788f35a0315 h1:zje9aPr1kQ5nKwjO5MC0S/jehRtNrjfYuLfFRWZH6kY= +github.com/adjust/goautoneg v0.0.0-20150426214442-d788f35a0315/go.mod h1:4U522XvlkqOY2AVBUM7ISHODDb6tdB+KAXfGaBDsWts= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.44.326 h1:/6xD/9mKZ2RMTDfbhh9qCxw+CaTbJRvfHJ/NHPFbI38= +github.com/aws/aws-sdk-go v1.44.326/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creachadair/jrpc2 v0.41.1 h1:GnSQNk+vt8B/oayJlfOXVRi4hg8DuB9NsppFGe8iVJ0= +github.com/creachadair/jrpc2 v0.41.1/go.mod h1:k2mGfjsgE2h2Vo12C9NzZguUzzl3gnfGCmLIvg84pVE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955 h1:gmtGRvSexPU4B1T/yYo0sLOKzER1YT+b4kPxPpm0Ty4= +github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1 h1:qIqziX4EA/OBdmMgtaqdKBWWOZIfyXYClCoa56NgVEk= +github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= +github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v0.0.0-20150906023321-a41850380601 h1:jxTbmDuqQUTI6MscgbqB39vtxGfr2fi61nYIcFQUnlE= +github.com/go-errors/errors v0.0.0-20150906023321-a41850380601/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= +github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 h1:oERTZ1buOUYlpmKaqlO5fYmz8cZ1rYu5DieJzF4ZVmU= +github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/guregu/null v2.1.3-0.20151024101046-79c5bd36b615+incompatible h1:SZmF1M6CdAm4MmTPYYTG+x9EC8D3FOxUq9S4D37irQg= +github.com/guregu/null v2.1.3-0.20151024101046-79c5bd36b615+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jarcoal/httpmock v0.0.0-20161210151336-4442edb3db31 h1:Aw95BEvxJ3K6o9GGv5ppCd1P8hkeIeEJ30FO+OhOJpM= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 h1:ykXz+pRRTibcSjG1yRhpdSHInF8yZY/mfn+Rz2Nd1rE= +github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739/go.mod h1:zUx1mhth20V3VKgL5jbd1BSQcW4Fy6Qs4PZvQwRFwzM= +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moul/http2curl v0.0.0-20161031194548-4e24498b31db h1:eZgFHVkk9uOTaOQLC6tgjkzdp7Ays8eEVecBcfHZlJQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0= +github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/stellar/go v0.0.0-20230905170723-6e18a2f2f583 h1:oC7naR8IBqtWV/b+G91gTOe+jR52+GpkXhCxEyYawYY= -github.com/stellar/go v0.0.0-20230905170723-6e18a2f2f583/go.mod h1:5/qoLl0pexA5OPi0BZvDsOc3532CJlHuRg1dnBxbsGg= -github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee h1:fbVs0xmXpBvVS4GBeiRmAE3Le70ofAqFMch1GTiq/e8= -github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079 h1:xPeaaIHjF9j8jbYQ5xdvLnFp+lpmGYFG1uBPtXNBHno= +github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 h1:S4OC0+OBKz6mJnzuHioeEat74PuQ4Sgvbf8eus695sc= +github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2/go.mod h1:8zLRYR5npGjaOXgPSKat5+oOh+UHd8OdbS18iqX9F6Y= +github.com/sergi/go-diff v0.0.0-20161205080420-83532ca1c1ca h1:oR/RycYTFTVXzND5r4FdsvbnBn0HJXSVeNAnwaTXRwk= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stellar/go-xdr v0.0.0-20230919160922-6c7b68458206 h1:UFuvvpbWL8+jqO1QmKYWSVhiMp4MRiIFd8/zQlUINH0= +github.com/stellar/go-xdr v0.0.0-20230919160922-6c7b68458206/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= +github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible h1:jMXXAcz6xTarGDQ4VtVbtERogcmDQw4RaE85Cr9CgoQ= +github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible/go.mod h1:7CJ23pXirXBJq45DqvO6clzTEGM/l1SfKrgrzLry8b4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= github.com/xdrpp/goxdr v0.1.1 h1:E1B2c6E8eYhOVyd7yEpOyopzTPirUeF6mVOfXfGyJyc= +github.com/xdrpp/goxdr v0.1.1/go.mod h1:dXo1scL/l6s7iME1gxHWo2XCppbHEKZS7m/KyYWkNzA= +github.com/xeipuuv/gojsonpointer v0.0.0-20151027082146-e0fe6f683076 h1:KM4T3G70MiR+JtqplcYkNVoNz7pDwYaBxWBXQK804So= +github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c h1:XZWnr3bsDQWAZg4Ne+cPoXRPILrNlPNQfxBuwLl43is= +github.com/xeipuuv/gojsonschema v0.0.0-20161231055540-f06f290571ce h1:cVSRGH8cOveJNwFEEZLXtB+XMnRqKLjUP6V/ZFYQCXI= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20150812003900-31a79c7593bb h1:06WAhQa+mYv7BiOk13B/ywyTlkoE/S7uu6TBKU6FHnE= +github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M= +github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce h1:888GrqRxabUce7lj4OaoShPxodm3kXOMpSa85wdYzfY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/gavv/httpexpect.v1 v1.0.0-20170111145843-40724cf1e4a0 h1:r5ptJ1tBxVAeqw4CrYWhXIMr0SybY3CDHuIbCg5CFVw= +gopkg.in/gorp.v1 v1.7.1 h1:GBB9KrWRATQZh95HJyVGUZrWwOPswitEYEyqlK8JbAA= +gopkg.in/gorp.v1 v1.7.1/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tylerb/graceful.v1 v1.2.13 h1:UWJlWJHZepntB0PJ9RTgW3X+zVLjfmWbx/V1X/V/XoA= +gopkg.in/tylerb/graceful.v1 v1.2.13/go.mod h1:yBhekWvR20ACXVObSSdD3u6S9DeSylanL2PAbAC/uJ8= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..3aa60af --- /dev/null +++ b/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "perun.network/go-perun/wire" + "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/client" + "perun.network/perun-stellar-backend/util" +) + +func main() { + + // Create a Backend to interact with the Stellar network: integration test environment from the stellar/go sdk + stellarEnv := env.NewBackendEnv() + + // Create two Stellar L1 accounts + kps, _ := stellarEnv.CreateAccounts(3, "10000000") + + kpAlice := kps[0] + kpBob := kps[1] + kpDeployer := kps[2] + + hzAlice := stellarEnv.AccountDetails(kpAlice) + hzBob := stellarEnv.AccountDetails(kpBob) + hzDeployer := stellarEnv.AccountDetails(kpDeployer) + + fmt.Println("hzAlice, hzBob, hzDeployer: ", hzAlice, hzBob, hzDeployer) + + // Deploy the contract + + contractIDAddress := util.Deploy(stellarEnv, kpDeployer, hzDeployer) + + fmt.Println("Deployed contractIDAddress: ", contractIDAddress) + + // Generate L2 accounts for the payment channel + wAlice, accAlice, _ := util.MakeRandPerunWallet() + wBob, accBob, _ := util.MakeRandPerunWallet() + assetContractID := util.NewRandAsset() + bus := wire.NewLocalBus() + alicePerun, err := client.SetupPaymentClient(stellarEnv, wAlice, accAlice, kpAlice, assetContractID, bus) + if err != nil { + panic(err) + } + + bobPerun, err := client.SetupPaymentClient(stellarEnv, wBob, accBob, kpBob, assetContractID, bus) + if err != nil { + panic(err) + } + + fmt.Println("alicePerun, bobPerun: ", alicePerun, bobPerun) + +} diff --git a/services/horizon/docker/Dockerfile b/services/horizon/docker/Dockerfile new file mode 100644 index 0000000..3c090d2 --- /dev/null +++ b/services/horizon/docker/Dockerfile @@ -0,0 +1,21 @@ +FROM ubuntu:focal + +ARG VERSION +ARG STELLAR_CORE_VERSION +ARG DEBIAN_FRONTEND=noninteractive +ARG ALLOW_CORE_UNSTABLE=no + +RUN apt-get update && apt-get install -y wget apt-transport-https gnupg2 && \ + wget -qO /etc/apt/trusted.gpg.d/SDF.asc https://apt.stellar.org/SDF.asc && \ + echo "deb https://apt.stellar.org focal stable" | tee -a /etc/apt/sources.list.d/SDF.list && \ + if [ "${ALLOW_CORE_UNSTABLE}" = "yes" ]; then echo "deb https://apt.stellar.org focal unstable" | tee -a /etc/apt/sources.list.d/SDF.list; fi && \ + cat /etc/apt/sources.list.d/SDF.list && \ + apt-get update && apt-cache madison stellar-core && eval "apt-get install -y stellar-core${STELLAR_CORE_VERSION+=$STELLAR_CORE_VERSION}" && \ + if [ "${ALLOW_CORE_UNSTABLE}" = "yes" ]; then sed -i '/unstable/d' /etc/apt/sources.list.d/SDF.list; fi && \ + cat /etc/apt/sources.list.d/SDF.list && \ + echo "deb https://apt.stellar.org focal testing" | tee -a /etc/apt/sources.list.d/SDF.list && \ + apt-get update && apt-cache madison stellar-horizon && apt-get install -y stellar-horizon=${VERSION} && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /var/log/*.log /var/log/*/*.log + +EXPOSE 8000 +ENTRYPOINT ["/usr/bin/stellar-horizon"] diff --git a/services/horizon/docker/Dockerfile.dev b/services/horizon/docker/Dockerfile.dev new file mode 100644 index 0000000..1d1be8d --- /dev/null +++ b/services/horizon/docker/Dockerfile.dev @@ -0,0 +1,28 @@ +FROM golang:1.20-bullseye AS builder + +ARG VERSION="devel" +WORKDIR /go/src/github.com/stellar/go +COPY go.mod go.sum ./ +RUN go mod download +COPY . ./ +ENV GOFLAGS="-ldflags=-X=github.com/stellar/go/support/app.version=${VERSION}-(built-from-source)" +RUN go install github.com/stellar/go/services/horizon + +FROM ubuntu:22.04 +ARG STELLAR_CORE_VERSION +ENV STELLAR_CORE_VERSION=${STELLAR_CORE_VERSION:-*} +ENV STELLAR_CORE_BINARY_PATH /usr/bin/stellar-core + +ENV DEBIAN_FRONTEND=noninteractive +# ca-certificates are required to make tls connections +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl wget gnupg apt-utils +RUN wget -qO - https://apt.stellar.org/SDF.asc | APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=true apt-key add - +RUN echo "deb https://apt.stellar.org focal stable" >/etc/apt/sources.list.d/SDF.list +RUN echo "deb https://apt.stellar.org focal unstable" >/etc/apt/sources.list.d/SDF-unstable.list +RUN apt-get update && apt-get install -y stellar-core=${STELLAR_CORE_VERSION} +RUN apt-get clean + +COPY --from=builder /go/bin/horizon ./ + +ENTRYPOINT ["./horizon"] + diff --git a/services/horizon/docker/Makefile b/services/horizon/docker/Makefile new file mode 100644 index 0000000..3207445 --- /dev/null +++ b/services/horizon/docker/Makefile @@ -0,0 +1,32 @@ +SUDO := $(shell docker version >/dev/null 2>&1 || echo "sudo") + +# https://github.com/opencontainers/image-spec/blob/master/annotations.md +BUILD_DATE := $(shell date -u +%FT%TZ) + +TAG ?= stellar/stellar-horizon:$(VERSION) +ifeq ($(ALLOW_CORE_UNSTABLE),yes) + TAG := $(TAG)-UNSTABLE +endif + +docker-build: +ifndef VERSION + $(error VERSION environment variable must be set. For example VERSION=2.4.1-101 ) +endif +ifndef STELLAR_CORE_VERSION + $(SUDO) docker build --pull $(DOCKER_OPTS) \ + --label org.opencontainers.image.created="$(BUILD_DATE)" \ + --build-arg VERSION=$(VERSION) --build-arg ALLOW_CORE_UNSTABLE=$(ALLOW_CORE_UNSTABLE) \ + -t $(TAG) . +else + $(SUDO) docker build --pull $(DOCKER_OPTS) \ + --label org.opencontainers.image.created="$(BUILD_DATE)" \ + --build-arg VERSION=$(VERSION) --build-arg STELLAR_CORE_VERSION=$(STELLAR_CORE_VERSION) \ + --build-arg ALLOW_CORE_UNSTABLE=$(ALLOW_CORE_UNSTABLE) \ + -t $(TAG) . +endif + +docker-push: +ifndef TAG + $(error Must set VERSION or TAG environment variable. For example VERSION=2.4.1-101 ) +endif + $(SUDO) docker push $(TAG) diff --git a/services/horizon/docker/captive-core-classic-integration-tests.cfg b/services/horizon/docker/captive-core-classic-integration-tests.cfg new file mode 100644 index 0000000..ed8ac3e --- /dev/null +++ b/services/horizon/docker/captive-core-classic-integration-tests.cfg @@ -0,0 +1,13 @@ +PEER_PORT=11725 +ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true + +UNSAFE_QUORUM=true +FAILURE_SAFETY=0 + +[[VALIDATORS]] +NAME="local_core" +HOME_DOMAIN="core.local" +# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" +PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" +ADDRESS="localhost" +QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-integration-tests.cfg b/services/horizon/docker/captive-core-integration-tests.cfg new file mode 100644 index 0000000..2215cb9 --- /dev/null +++ b/services/horizon/docker/captive-core-integration-tests.cfg @@ -0,0 +1,15 @@ +PEER_PORT=11725 +ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true +ENABLE_SOROBAN_DIAGNOSTIC_EVENTS=true +TESTING_SOROBAN_HIGH_LIMIT_OVERRIDE=true + +UNSAFE_QUORUM=true +FAILURE_SAFETY=0 + +[[VALIDATORS]] +NAME="local_core" +HOME_DOMAIN="core.local" +# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" +PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" +ADDRESS="localhost" +QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg b/services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg new file mode 100644 index 0000000..2e3dd34 --- /dev/null +++ b/services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg @@ -0,0 +1,14 @@ +PEER_PORT=11725 +ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true +ENABLE_SOROBAN_DIAGNOSTIC_EVENTS=true + +UNSAFE_QUORUM=true +FAILURE_SAFETY=0 + +[[VALIDATORS]] +NAME="local_core" +HOME_DOMAIN="core.local" +# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" +PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" +ADDRESS="core" +QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg b/services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg new file mode 100644 index 0000000..4902cf8 --- /dev/null +++ b/services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg @@ -0,0 +1,9 @@ +ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true + +[[VALIDATORS]] +NAME="local_core" +HOME_DOMAIN="core.local" +# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" +PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" +ADDRESS="localhost" +QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-reingest-range-integration-tests.cfg b/services/horizon/docker/captive-core-reingest-range-integration-tests.cfg new file mode 100644 index 0000000..26a4cd6 --- /dev/null +++ b/services/horizon/docker/captive-core-reingest-range-integration-tests.cfg @@ -0,0 +1,10 @@ +ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true +TESTING_SOROBAN_HIGH_LIMIT_OVERRIDE=true + +[[VALIDATORS]] +NAME="local_core" +HOME_DOMAIN="core.local" +# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" +PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" +ADDRESS="localhost" +QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-standalone.cfg b/services/horizon/docker/captive-core-standalone.cfg new file mode 100644 index 0000000..d54b0ec --- /dev/null +++ b/services/horizon/docker/captive-core-standalone.cfg @@ -0,0 +1,12 @@ +PEER_PORT=11725 + +UNSAFE_QUORUM=true +FAILURE_SAFETY=0 + +[[VALIDATORS]] +NAME="local_core" +HOME_DOMAIN="core.local" +# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" +PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" +ADDRESS="host.docker.internal" +QUALITY="MEDIUM" \ No newline at end of file diff --git a/services/horizon/docker/core-start.sh b/services/horizon/docker/core-start.sh new file mode 100755 index 0000000..9dd89ba --- /dev/null +++ b/services/horizon/docker/core-start.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -e +set -x + +source /etc/profile +# work within the current docker working dir +if [ ! -f "./stellar-core.cfg" ]; then + cp /stellar-core.cfg ./ +fi + +echo "using config:" +cat stellar-core.cfg + +# initialize new db +stellar-core new-db + +if [ "$1" = "standalone" ]; then + # initialize for new history archive path, remove any pre-existing on same path from base image + rm -rf ./history + stellar-core new-hist vs + + # serve history archives to horizon on port 1570 + pushd ./history/vs/ + python3 -m http.server 1570 & + popd +fi + +exec stellar-core run --console diff --git a/services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml b/services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml new file mode 100644 index 0000000..940c340 --- /dev/null +++ b/services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml @@ -0,0 +1,20 @@ +version: '3' +services: + soroban-rpc: + platform: linux/amd64 + image: ${SOROBAN_RPC_IMAGE:-stellar/soroban-rpc} + depends_on: + - core + restart: on-failure + ports: + - "8080:8080" + environment: + - ENDPOINT=:8080 + - NETWORK_PASSPHRASE=Standalone Network ; February 2017 + - CAPTIVE_CORE_CONFIG_PATH=/captive-core.cfg + - HISTORY_ARCHIVE_URLS=http://core:1570 + - CHECKPOINT_FREQUENCY=8 + - LOG_LEVEL=debug + volumes: + - ./captive-core-integration-tests.soroban-rpc.cfg:/captive-core.cfg + diff --git a/services/horizon/docker/docker-compose.integration-tests.yml b/services/horizon/docker/docker-compose.integration-tests.yml new file mode 100644 index 0000000..efe27ca --- /dev/null +++ b/services/horizon/docker/docker-compose.integration-tests.yml @@ -0,0 +1,32 @@ +version: '3' +services: + core-postgres: + image: postgres:9.6.17-alpine + restart: on-failure + environment: + - POSTGRES_PASSWORD=mysecretpassword + - POSTGRES_DB=stellar + ports: + - "5641:5641" + command: ["-p", "5641"] + core: + platform: linux/amd64 + # Note: Please keep the image pinned to an immutable tag matching the Captive Core version. + # This avoid implicit updates which break compatibility between + # the Core container and captive core. + image: ${CORE_IMAGE:-chowbao/stellar-core:19.11.1-1357.e38ee728d.focal-sorobanP10} + depends_on: + - core-postgres + restart: on-failure + environment: + - TRACY_NO_INVARIANT_CHECK=1 + ports: + - "11625:11625" + - "11626:11626" + # add extra port for history archive server + - "1570:1570" + entrypoint: /usr/bin/env + command: /start standalone + volumes: + - ./${CORE_CONFIG_FILE:-stellar-core-integration-tests.cfg}:/stellar-core.cfg + - ./core-start.sh:/start diff --git a/services/horizon/docker/docker-compose.pubnet.yml b/services/horizon/docker/docker-compose.pubnet.yml new file mode 100644 index 0000000..169045d --- /dev/null +++ b/services/horizon/docker/docker-compose.pubnet.yml @@ -0,0 +1,6 @@ +version: '3' +services: + horizon: + platform: linux/amd64 + environment: + - NETWORK=pubnet \ No newline at end of file diff --git a/services/horizon/docker/docker-compose.standalone.yml b/services/horizon/docker/docker-compose.standalone.yml new file mode 100644 index 0000000..a537be0 --- /dev/null +++ b/services/horizon/docker/docker-compose.standalone.yml @@ -0,0 +1,53 @@ +version: '3' +services: + core-postgres: + image: postgres:9.6.17-alpine + restart: on-failure + environment: + - POSTGRES_PASSWORD=mysecretpassword + - POSTGRES_DB=stellar + ports: + - "5641:5641" + command: ["-p", "5641"] + volumes: + - "core-db-data:/var/lib/postgresql/data" + + core: + platform: linux/amd64 + image: ${CORE_IMAGE:-stellar/stellar-core:19.11.0-1323.7fb6d5e88.focal} + depends_on: + - core-postgres + - core-upgrade + restart: on-failure + ports: + - "11625:11625" + - "11626:11626" + # add extra port for history archive server + - "1570:1570" + entrypoint: /usr/bin/env + command: /start standalone + volumes: + - ./stellar-core-standalone.cfg:/stellar-core.cfg + - ./core-start.sh:/start + extra_hosts: + - "host.docker.internal:host-gateway" + + horizon: + environment: + - HISTORY_ARCHIVE_URLS=http://host.docker.internal:1570 + - NETWORK_PASSPHRASE=Standalone Network ; February 2017 + - CAPTIVE_CORE_CONFIG_APPEND_PATH=/captive-core-standalone.cfg + - STELLAR_CORE_URL=http://host.docker.internal:11626 + volumes: + - ./captive-core-standalone.cfg:/captive-core-standalone.cfg + + # this container will invoke a request to upgrade stellar core to protocol 17 (by default) + core-upgrade: + restart: on-failure + image: curlimages/curl:7.69.1 + command: ["-v", "-f", "http://host.docker.internal:11626/upgrades?mode=set&upgradetime=1970-01-01T00:00:00Z&protocolversion=${PROTOCOL_VERSION:-18}"] + extra_hosts: + - "host.docker.internal:host-gateway" + +volumes: + core-db-data: diff --git a/services/horizon/docker/docker-compose.yml b/services/horizon/docker/docker-compose.yml new file mode 100644 index 0000000..309df94 --- /dev/null +++ b/services/horizon/docker/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3' +services: + horizon-postgres: + platform: linux/amd64 + image: postgres:12-bullseye + restart: on-failure + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + - POSTGRES_DB=horizon + ports: + - "5432:5432" + volumes: + - "horizon-db-data:/var/lib/postgresql/data" + + horizon: + platform: linux/amd64 + depends_on: + - horizon-postgres + build: + # set build context to the root directory of the go monorepo + context: ../../../ + args: + STELLAR_CORE_VERSION: ${STELLAR_CORE_VERSION:-} + dockerfile: services/horizon/docker/Dockerfile.dev + restart: on-failure + ports: + - "8000:8000" + - "11725:11725" + environment: + - DATABASE_URL=postgres://postgres@host.docker.internal:5432/horizon?sslmode=disable + - NETWORK=testnet + - PER_HOUR_RATE_LIMIT=0 + command: ["--apply-migrations"] + extra_hosts: + - "host.docker.internal:host-gateway" + +volumes: + horizon-db-data: diff --git a/services/horizon/docker/start.sh b/services/horizon/docker/start.sh new file mode 100755 index 0000000..824fa19 --- /dev/null +++ b/services/horizon/docker/start.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e + +NETWORK=${1:-testnet} + +case $NETWORK in + standalone) + DOCKER_FLAGS="-f docker-compose.yml -f docker-compose.standalone.yml" + echo "running on standalone network" + ;; + + pubnet) + DOCKER_FLAGS="-f docker-compose.yml -f docker-compose.pubnet.yml" + echo "running on public network" + ;; + + testnet) + echo "running on test network" + ;; + + *) + echo "$1 is not a supported option " + exit 1 + ;; +esac + +docker-compose $DOCKER_FLAGS up --build -d \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-classic-integration-tests.cfg b/services/horizon/docker/stellar-core-classic-integration-tests.cfg new file mode 100644 index 0000000..e27cfe1 --- /dev/null +++ b/services/horizon/docker/stellar-core-classic-integration-tests.cfg @@ -0,0 +1,24 @@ +ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true + +NETWORK_PASSPHRASE="Standalone Network ; February 2017" + +PEER_PORT=11625 +HTTP_PORT=11626 +PUBLIC_HTTP_PORT=true + +NODE_SEED="SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" + +NODE_IS_VALIDATOR=true +UNSAFE_QUORUM=true +FAILURE_SAFETY=0 + +DATABASE="postgresql://user=postgres password=mysecretpassword host=core-postgres port=5641 dbname=stellar" + +[QUORUM_SET] +THRESHOLD_PERCENT=100 +VALIDATORS=["GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"] + +[HISTORY.vs] +get="cp history/vs/{0} {1}" +put="cp {0} history/vs/{1}" +mkdir="mkdir -p history/vs/{0}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-integration-tests.cfg b/services/horizon/docker/stellar-core-integration-tests.cfg new file mode 100644 index 0000000..53d5a98 --- /dev/null +++ b/services/horizon/docker/stellar-core-integration-tests.cfg @@ -0,0 +1,25 @@ +ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true +TESTING_SOROBAN_HIGH_LIMIT_OVERRIDE=true + +NETWORK_PASSPHRASE="Standalone Network ; February 2017" + +PEER_PORT=11625 +HTTP_PORT=11626 +PUBLIC_HTTP_PORT=true + +NODE_SEED="SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" + +NODE_IS_VALIDATOR=true +UNSAFE_QUORUM=true +FAILURE_SAFETY=0 + +DATABASE="postgresql://user=postgres password=mysecretpassword host=core-postgres port=5641 dbname=stellar" + +[QUORUM_SET] +THRESHOLD_PERCENT=100 +VALIDATORS=["GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"] + +[HISTORY.vs] +get="cp history/vs/{0} {1}" +put="cp {0} history/vs/{1}" +mkdir="mkdir -p history/vs/{0}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-pubnet.cfg b/services/horizon/docker/stellar-core-pubnet.cfg new file mode 100644 index 0000000..b851fbb --- /dev/null +++ b/services/horizon/docker/stellar-core-pubnet.cfg @@ -0,0 +1,202 @@ +# simple configuration for a standalone test "network" +# see stellar-core_example.cfg for a description of the configuration parameters + +HTTP_PORT=11626 +PUBLIC_HTTP_PORT=true +LOG_FILE_PATH="" + +DATABASE="postgresql://user=postgres password=mysecretpassword host=host.docker.internal port=5641 dbname=stellar" +NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015" +CATCHUP_RECENT=100 + +[HISTORY.cache] +get="cp /opt/stellar/history-cache/{0} {1}" + +[[HOME_DOMAINS]] +HOME_DOMAIN="stellar.org" +QUALITY="HIGH" + +[[HOME_DOMAINS]] +HOME_DOMAIN="satoshipay.io" +QUALITY="HIGH" + +[[HOME_DOMAINS]] +HOME_DOMAIN="lobstr.co" +QUALITY="HIGH" + +[[HOME_DOMAINS]] +HOME_DOMAIN="www.coinqvest.com" +QUALITY="HIGH" + +[[HOME_DOMAINS]] +HOME_DOMAIN="publicnode.org" +QUALITY="HIGH" + +[[HOME_DOMAINS]] +HOME_DOMAIN="stellar.blockdaemon.com" +QUALITY="HIGH" + +[[HOME_DOMAINS]] +HOME_DOMAIN = "www.franklintempleton.com" +QUALITY = "HIGH" + +[[VALIDATORS]] +NAME="sdf_1" +HOME_DOMAIN="stellar.org" +PUBLIC_KEY="GCGB2S2KGYARPVIA37HYZXVRM2YZUEXA6S33ZU5BUDC6THSB62LZSTYH" +ADDRESS="core-live-a.stellar.org:11625" +HISTORY="curl -sf https://history.stellar.org/prd/core-live/core_live_001/{0} -o {1}" + +[[VALIDATORS]] +NAME="sdf_2" +HOME_DOMAIN="stellar.org" +PUBLIC_KEY="GCM6QMP3DLRPTAZW2UZPCPX2LF3SXWXKPMP3GKFZBDSF3QZGV2G5QSTK" +ADDRESS="core-live-b.stellar.org:11625" +HISTORY="curl -sf https://history.stellar.org/prd/core-live/core_live_002/{0} -o {1}" + +[[VALIDATORS]] +NAME="sdf_3" +HOME_DOMAIN="stellar.org" +PUBLIC_KEY="GABMKJM6I25XI4K7U6XWMULOUQIQ27BCTMLS6BYYSOWKTBUXVRJSXHYQ" +ADDRESS="core-live-c.stellar.org:11625" +HISTORY="curl -sf https://history.stellar.org/prd/core-live/core_live_003/{0} -o {1}" + +[[VALIDATORS]] +NAME="satoshipay_singapore" +HOME_DOMAIN="satoshipay.io" +PUBLIC_KEY="GBJQUIXUO4XSNPAUT6ODLZUJRV2NPXYASKUBY4G5MYP3M47PCVI55MNT" +ADDRESS="stellar-sg-sin.satoshipay.io:11625" +HISTORY="curl -sf https://stellar-history-sg-sin.satoshipay.io/{0} -o {1}" + +[[VALIDATORS]] +NAME="satoshipay_iowa" +HOME_DOMAIN="satoshipay.io" +PUBLIC_KEY="GAK6Z5UVGUVSEK6PEOCAYJISTT5EJBB34PN3NOLEQG2SUKXRVV2F6HZY" +ADDRESS="stellar-us-iowa.satoshipay.io:11625" +HISTORY="curl -sf https://stellar-history-us-iowa.satoshipay.io/{0} -o {1}" + +[[VALIDATORS]] +NAME="satoshipay_frankfurt" +HOME_DOMAIN="satoshipay.io" +PUBLIC_KEY="GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE" +ADDRESS="stellar-de-fra.satoshipay.io:11625" +HISTORY="curl -sf https://stellar-history-de-fra.satoshipay.io/{0} -o {1}" + +[[VALIDATORS]] +NAME="lobstr_1_europe" +HOME_DOMAIN="lobstr.co" +PUBLIC_KEY="GCFONE23AB7Y6C5YZOMKUKGETPIAJA4QOYLS5VNS4JHBGKRZCPYHDLW7" +ADDRESS="v1.stellar.lobstr.co:11625" +HISTORY="curl -sf https://stellar-archive-1-lobstr.s3.amazonaws.com/{0} -o {1}" + +[[VALIDATORS]] +NAME="lobstr_2_europe" +HOME_DOMAIN="lobstr.co" +PUBLIC_KEY="GDXQB3OMMQ6MGG43PWFBZWBFKBBDUZIVSUDAZZTRAWQZKES2CDSE5HKJ" +ADDRESS="v2.stellar.lobstr.co:11625" +HISTORY="curl -sf https://stellar-archive-2-lobstr.s3.amazonaws.com/{0} -o {1}" + +[[VALIDATORS]] +NAME="lobstr_3_north_america" +HOME_DOMAIN="lobstr.co" +PUBLIC_KEY="GD5QWEVV4GZZTQP46BRXV5CUMMMLP4JTGFD7FWYJJWRL54CELY6JGQ63" +ADDRESS="v3.stellar.lobstr.co:11625" +HISTORY="curl -sf https://stellar-archive-3-lobstr.s3.amazonaws.com/{0} -o {1}" + +[[VALIDATORS]] +NAME="lobstr_4_asia" +HOME_DOMAIN="lobstr.co" +PUBLIC_KEY="GA7TEPCBDQKI7JQLQ34ZURRMK44DVYCIGVXQQWNSWAEQR6KB4FMCBT7J" +ADDRESS="v4.stellar.lobstr.co:11625" +HISTORY="curl -sf https://stellar-archive-4-lobstr.s3.amazonaws.com/{0} -o {1}" + +[[VALIDATORS]] +NAME="lobstr_5_australia" +HOME_DOMAIN="lobstr.co" +PUBLIC_KEY="GA5STBMV6QDXFDGD62MEHLLHZTPDI77U3PFOD2SELU5RJDHQWBR5NNK7" +ADDRESS="v5.stellar.lobstr.co:11625" +HISTORY="curl -sf https://stellar-archive-5-lobstr.s3.amazonaws.com/{0} -o {1}" + +[[VALIDATORS]] +NAME="coinqvest_hong_kong" +HOME_DOMAIN="www.coinqvest.com" +PUBLIC_KEY="GAZ437J46SCFPZEDLVGDMKZPLFO77XJ4QVAURSJVRZK2T5S7XUFHXI2Z" +ADDRESS="hongkong.stellar.coinqvest.com:11625" +HISTORY="curl -sf https://hongkong.stellar.coinqvest.com/history/{0} -o {1}" + +[[VALIDATORS]] +NAME="coinqvest_germany" +HOME_DOMAIN="www.coinqvest.com" +PUBLIC_KEY="GD6SZQV3WEJUH352NTVLKEV2JM2RH266VPEM7EH5QLLI7ZZAALMLNUVN" +ADDRESS="germany.stellar.coinqvest.com:11625" +HISTORY="curl -sf https://germany.stellar.coinqvest.com/history/{0} -o {1}" + +[[VALIDATORS]] +NAME="coinqvest_finland" +HOME_DOMAIN="www.coinqvest.com" +PUBLIC_KEY="GADLA6BJK6VK33EM2IDQM37L5KGVCY5MSHSHVJA4SCNGNUIEOTCR6J5T" +ADDRESS="finland.stellar.coinqvest.com:11625" +HISTORY="curl -sf https://finland.stellar.coinqvest.com/history/{0} -o {1}" + +[[VALIDATORS]] +NAME="bootes" +HOME_DOMAIN="publicnode.org" +PUBLIC_KEY="GCVJ4Z6TI6Z2SOGENSPXDQ2U4RKH3CNQKYUHNSSPYFPNWTLGS6EBH7I2" +ADDRESS="bootes.publicnode.org" +HISTORY="curl -sf https://bootes-history.publicnode.org/{0} -o {1}" + +[[VALIDATORS]] +NAME="hercules" +HOME_DOMAIN="publicnode.org" +PUBLIC_KEY="GBLJNN3AVZZPG2FYAYTYQKECNWTQYYUUY2KVFN2OUKZKBULXIXBZ4FCT" +ADDRESS="hercules.publicnode.org" +HISTORY="curl -sf https://hercules-history.publicnode.org/{0} -o {1}" + +[[VALIDATORS]] +NAME="lyra" +HOME_DOMAIN="publicnode.org" +PUBLIC_KEY="GCIXVKNFPKWVMKJKVK2V4NK7D4TC6W3BUMXSIJ365QUAXWBRPPJXIR2Z" +ADDRESS="lyra.publicnode.org" +HISTORY="curl -sf https://lyra-history.publicnode.org/{0} -o {1}" + +[[VALIDATORS]] +NAME="Blockdaemon_Validator_1" +HOME_DOMAIN="stellar.blockdaemon.com" +PUBLIC_KEY="GAAV2GCVFLNN522ORUYFV33E76VPC22E72S75AQ6MBR5V45Z5DWVPWEU" +ADDRESS="stellar-full-validator1.bdnodes.net" +HISTORY="curl -sf https://stellar-full-history1.bdnodes.net/{0} -o {1}" + +[[VALIDATORS]] +NAME="Blockdaemon_Validator_2" +HOME_DOMAIN="stellar.blockdaemon.com" +PUBLIC_KEY="GAVXB7SBJRYHSG6KSQHY74N7JAFRL4PFVZCNWW2ARI6ZEKNBJSMSKW7C" +ADDRESS="stellar-full-validator2.bdnodes.net" +HISTORY="curl -sf https://stellar-full-history2.bdnodes.net/{0} -o {1}" + +[[VALIDATORS]] +NAME="Blockdaemon_Validator_3" +HOME_DOMAIN="stellar.blockdaemon.com" +PUBLIC_KEY="GAYXZ4PZ7P6QOX7EBHPIZXNWY4KCOBYWJCA4WKWRKC7XIUS3UJPT6EZ4" +ADDRESS="stellar-full-validator3.bdnodes.net" +HISTORY="curl -sf https://stellar-full-history3.bdnodes.net/{0} -o {1}" + +[[VALIDATORS]] +NAME = "FT_SCV_1" +HOME_DOMAIN = "www.franklintempleton.com" +PUBLIC_KEY = "GARYGQ5F2IJEBCZJCBNPWNWVDOFK7IBOHLJKKSG2TMHDQKEEC6P4PE4V" +ADDRESS = "stellar1.franklintempleton.com:11625" +HISTORY = "curl -sf https://stellar-history-usw.franklintempleton.com/azuswshf401/{0} -o {1}" + +[[VALIDATORS]] +NAME = "FT_SCV_2" +HOME_DOMAIN = "www.franklintempleton.com" +PUBLIC_KEY = "GCMSM2VFZGRPTZKPH5OABHGH4F3AVS6XTNJXDGCZ3MKCOSUBH3FL6DOB" +ADDRESS = "stellar2.franklintempleton.com:11625" +HISTORY = "curl -sf https://stellar-history-usc.franklintempleton.com/azuscshf401/{0} -o {1}" + +[[VALIDATORS]] +NAME = "FT_SCV_3" +HOME_DOMAIN = "www.franklintempleton.com" +PUBLIC_KEY = "GA7DV63PBUUWNUFAF4GAZVXU2OZMYRATDLKTC7VTCG7AU4XUPN5VRX4A" +ADDRESS = "stellar3.franklintempleton.com:11625" +HISTORY = "curl -sf https://stellar-history-ins.franklintempleton.com/azinsshf401/{0} -o {1}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-standalone.cfg b/services/horizon/docker/stellar-core-standalone.cfg new file mode 100644 index 0000000..a2b7e80 --- /dev/null +++ b/services/horizon/docker/stellar-core-standalone.cfg @@ -0,0 +1,25 @@ +# simple configuration for a standalone test "network" +# see stellar-core_example.cfg for a description of the configuration parameters + +NETWORK_PASSPHRASE="Standalone Network ; February 2017" + +PEER_PORT=11625 +HTTP_PORT=11626 +PUBLIC_HTTP_PORT=true + +NODE_SEED="SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" + +NODE_IS_VALIDATOR=true +UNSAFE_QUORUM=true +FAILURE_SAFETY=0 + +DATABASE="postgresql://user=postgres password=mysecretpassword host=host.docker.internal port=5641 dbname=stellar" + +[QUORUM_SET] +THRESHOLD_PERCENT=100 +VALIDATORS=["GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"] + +[HISTORY.vs] +get="cp history/vs/{0} {1}" +put="cp {0} history/vs/{1}" +mkdir="mkdir -p history/vs/{0}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-testnet.cfg b/services/horizon/docker/stellar-core-testnet.cfg new file mode 100644 index 0000000..cf8546a --- /dev/null +++ b/services/horizon/docker/stellar-core-testnet.cfg @@ -0,0 +1,41 @@ +# simple configuration for a standalone test "network" +# see stellar-core_example.cfg for a description of the configuration parameters + +HTTP_PORT=11626 +PUBLIC_HTTP_PORT=true +LOG_FILE_PATH="" + +NETWORK_PASSPHRASE="Test SDF Network ; September 2015" + +DATABASE="postgresql://user=postgres password=mysecretpassword host=host.docker.internal port=5641 dbname=stellar" +UNSAFE_QUORUM=true +FAILURE_SAFETY=1 +CATCHUP_RECENT=100 + +[HISTORY.cache] +get="cp /opt/stellar/history-cache/{0} {1}" + +[[HOME_DOMAINS]] +HOME_DOMAIN="testnet.stellar.org" +QUALITY="HIGH" + +[[VALIDATORS]] +NAME="sdf_testnet_1" +HOME_DOMAIN="testnet.stellar.org" +PUBLIC_KEY="GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y" +ADDRESS="core-testnet1.stellar.org" +HISTORY="curl -sf http://history.stellar.org/prd/core-testnet/core_testnet_001/{0} -o {1}" + +[[VALIDATORS]] +NAME="sdf_testnet_2" +HOME_DOMAIN="testnet.stellar.org" +PUBLIC_KEY="GCUCJTIYXSOXKBSNFGNFWW5MUQ54HKRPGJUTQFJ5RQXZXNOLNXYDHRAP" +ADDRESS="core-testnet2.stellar.org" +HISTORY="curl -sf http://history.stellar.org/prd/core-testnet/core_testnet_002/{0} -o {1}" + +[[VALIDATORS]] +NAME="sdf_testnet_3" +HOME_DOMAIN="testnet.stellar.org" +PUBLIC_KEY="GC2V2EFSXN6SQTWVYA5EPJPBWWIMSD2XQNKUOHGEKB535AQE2I6IXV2Z" +ADDRESS="core-testnet3.stellar.org" +HISTORY="curl -sf http://history.stellar.org/prd/core-testnet/core_testnet_003/{0} -o {1}" diff --git a/testdata/perun_soroban_contract.wasm b/testdata/perun_soroban_contract.wasm new file mode 100755 index 0000000000000000000000000000000000000000..3696a62ec79671197dad033b57e6756e3bebdd1d GIT binary patch literal 24044 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 KNls#Ooc{~KXIiNM literal 0 HcmV?d00001 diff --git a/testdata/perun_soroban_contract_noapprove.wasm b/testdata/perun_soroban_contract_noapprove.wasm new file mode 100755 index 0000000000000000000000000000000000000000..1ae547c44d5de98ce4130b57646319cd2925786e GIT binary patch literal 24135 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 BYVrU8 literal 0 HcmV?d00001 diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..a67c088 --- /dev/null +++ b/util/util.go @@ -0,0 +1,76 @@ +package util + +import ( + "github.com/stellar/go/keypair" + "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/xdr" + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/channel/types" + "perun.network/perun-stellar-backend/wallet" + + //pkgtest "polycry.pt/poly-go/test" + "crypto/rand" + "encoding/binary" + mathrand "math/rand" +) + +const PerunContractPath = "./testdata/perun_soroban_contract.wasm" + +func Deploy(stellarEnv *env.IntegrationTestEnv, kpAlice *keypair.Full, hzDeployer horizon.Account) xdr.ScAddress { + // Install contract + installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), PerunContractPath) + preFlightOp, minFee := stellarEnv.PreflightHostFunctions(&hzDeployer, *installContractOp) + _ = stellarEnv.MustSubmitOperationsWithFee(&hzDeployer, kpAlice, minFee, &preFlightOp) + + // Create the contract + createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), PerunContractPath, "a1", stellarEnv.GetPassPhrase()) + preFlightOp, minFee = stellarEnv.PreflightHostFunctions(&hzDeployer, *createContractOp) + _, err := stellarEnv.SubmitOperationsWithFee(&hzDeployer, kpAlice, minFee, &preFlightOp) + if err != nil { + panic(err) + } + contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + contractIDAddress := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: contractID, + } + return contractIDAddress + +} + +func MakeRandPerunWallet() (*wallet.EphemeralWallet, *wallet.Account, *keypair.Full) { + w := wallet.NewEphemeralWallet() + + // Read 8 bytes from crypto/rand + var b [8]byte + _, err := rand.Read(b[:]) + if err != nil { + panic(err) + } + + // Convert 8 bytes to uint64 for seeding math/rand + seed := binary.LittleEndian.Uint64(b[:]) + + // Create a math/rand.Rand with the seed + r := mathrand.New(mathrand.NewSource(int64(seed))) + + acc, kp, err := w.AddNewAccount(r) + if err != nil { + panic(err) + } + return w, acc, kp +} + +func NewRandAsset() types.StellarAsset { + var contractID xdr.Hash + _, err := rand.Read(contractID[:]) + if err != nil { + panic(err) + } + + stellarAsset := types.NewStellarAsset(contractID) + + return *stellarAsset + +} diff --git a/wire/balances.go b/wire/balances.go index b63f210..04b5055 100644 --- a/wire/balances.go +++ b/wire/balances.go @@ -231,6 +231,7 @@ func makeAllocation(asset channel.Asset, balA, balB *big.Int) (*channel.Allocati alloc := channel.NewAllocation(2, asset) alloc.SetBalance(0, asset, balA) alloc.SetBalance(1, asset, balB) + alloc.Locked = make([]channel.SubAlloc, 0) if err := alloc.Valid(); err != nil { return nil, err diff --git a/wire/params.go b/wire/params.go index 9dfc68e..98c30b5 100644 --- a/wire/params.go +++ b/wire/params.go @@ -204,14 +204,20 @@ func ToParams(params Params) (channel.Params, error) { if err != nil { return channel.Params{}, err } - return channel.Params{ - LedgerChannel: true, - VirtualChannel: false, - App: channel.NoApp(), - Parts: []wallet.Address{&participantA, &participantB}, - Nonce: ToNonce(params.Nonce), - ChallengeDuration: uint64(params.ChallengeDuration), - }, nil + + challengeDuration := uint64(params.ChallengeDuration) + parts := []wallet.Address{&participantA, &participantB} + app := channel.NoApp() + nonce := ToNonce(params.Nonce) + ledgerChannel := true + virtualChannel := false + + perunParams, err := channel.NewParams(challengeDuration, parts, app, nonce, ledgerChannel, virtualChannel) + if err != nil { + return channel.Params{}, err + } + + return *perunParams, nil } func MakeNonce(nonce channel.Nonce) xdr.ScBytes { From e84e03ac2e8c8ac4b048d72a57ddf9eb64b0b87a Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 31 Oct 2023 19:30:59 +0100 Subject: [PATCH 02/51] .gitignore: Add vscode parameters to ignore in git --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 66fd13c..3a9f218 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +settings.json +launch.json \ No newline at end of file From 5485e6a09ee9ce6125851f20ea39861998115db2 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Mon, 6 Nov 2023 13:00:13 +0100 Subject: [PATCH 03/51] main: Finalize structure for Demo workflow channel: Finalize Funder workflow, simplify Stellar transactions channel/env: Change StellarClient interaction util: Remove global contract path constant --- channel/adjudicator.go | 123 +++++------------ channel/adjudicator_sub.go | 137 ++++++++++--------- channel/adjudicator_test.go | 261 +++++++++++++++++++++--------------- channel/deploy.go | 19 --- channel/env/client.go | 64 ++++++--- channel/env/integration.go | 28 +--- channel/env/transaction.go | 8 +- channel/event.go | 74 +++++----- channel/funder.go | 189 ++++++++------------------ channel/funder_test.go | 222 +++++++++++++++--------------- channel/subscribe.go | 22 +-- channel/test/fund.go | 23 ++++ client/channel.go | 9 +- client/client.go | 196 ++++++++++----------------- main.go | 19 ++- util/util.go | 17 +-- 16 files changed, 662 insertions(+), 749 deletions(-) create mode 100644 channel/test/fund.go diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 07ffd9e..057c3f7 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -3,11 +3,12 @@ package channel import ( "context" "errors" + "fmt" "github.com/stellar/go/keypair" //"github.com/stellar/go/protocols/horizon" "github.com/stellar/go/xdr" - "perun.network/go-perun/log" + "reflect" pchannel "perun.network/go-perun/channel" pwallet "perun.network/go-perun/wallet" @@ -23,8 +24,7 @@ import ( var ErrChannelAlreadyClosed = errors.New("nonce values was out of range") type Adjudicator struct { - log log.Embedding - //integrEnv env.IntegrationTestEnv + log log.Embedding stellarClient *env.StellarClient acc *wallet.Account kpFull *keypair.Full @@ -34,21 +34,23 @@ type Adjudicator struct { // NewAdjudicator returns a new Adjudicator. -func NewAdjudicator(acc *wallet.Account, stellarClient *env.StellarClient) *Adjudicator { +func NewAdjudicator(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient) *Adjudicator { return &Adjudicator{ stellarClient: stellarClient, acc: acc, + kpFull: kp, maxIters: MaxIterationsUntilAbort, pollingInterval: DefaultPollingInterval, + log: log.MakeEmbedding(log.Default()), } } -func (a Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel.AdjudicatorSubscription, error) { +func (a *Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel.AdjudicatorSubscription, error) { c := a.stellarClient return NewAdjudicatorSub(ctx, cid, c), nil } -func (a Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { +func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { cid := req.Tx.ID @@ -90,23 +92,29 @@ func (a Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, } err = a.withdraw(ctx, req) + if err != nil { + return err + } } return nil } -func (a Adjudicator) waitForClosed(ctx context.Context, evsub *AdjEventSub, cid pchannel.ID) error { +func (a *Adjudicator) waitForClosed(ctx context.Context, evsub *AdjEventSub, cid pchannel.ID) error { a.log.Log().Tracef("Waiting for the channel closing event") + fmt.Println("Waiting for the channel closing event") + loop: for { select { case event := <-evsub.Events(): - + fmt.Println("reflect.TypeOf(event): ", reflect.TypeOf(event)) _, ok := event.(*CloseEvent) if !ok { continue loop } + fmt.Println("closeevent received: ", event) evsub.Close() return nil @@ -122,7 +130,7 @@ loop: } } -func (a Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVec, error) { +func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVec, error) { // build withdrawalargs chanIDStellar := req.Tx.ID[:] @@ -140,7 +148,7 @@ func (a Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVec } -func (a Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) error { +func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) error { contractAddress := a.stellarClient.GetContractIDAddress() kp := a.kpFull @@ -153,35 +161,10 @@ func (a Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) return errors.New("error while building fund tx") } txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "withdraw", withdrawTxArgs, contractAddress, kp) + if err != nil { + return errors.New("error while invoking and processing host function: withdraw") + } - // build withdrawalargs - - // env := a.integrEnv - // contractAddress := env.GetContractIDAddress() - // kp := a.kpFull - // acc := env.AccountDetails(kp) - // generate tx to open the channel - - // call fct. - // contractAddr := a.integrEnv.GetContractIDAddress() - // //caller := a.integrEnv.Client() - // kp := a.kpFull - // acc := a.integrEnv.AccountDetails(kp) - - // invokeHostFunctionOp := env.BuildContractCallOp(acc, "withdraw", withdrawTxArgs, contractAddr) - - // preFlightOp, minFee := a.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) - - // tx, err := a.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) - // if err != nil { - // return errors.New("error while submitting operations with fee") - // } - - // // read out decoded Events and interpret them - // txMeta, err := env.DecodeTxMeta(tx) - // if err != nil { - // return errors.New("error while decoding tx meta") - // } _, err = DecodeEvents(txMeta) if err != nil { return errors.New("error while decoding events") @@ -190,9 +173,8 @@ func (a Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) return nil } -func (a Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { - //env := a.integrEnv - +func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { + fmt.Println("Close called") contractAddress := a.stellarClient.GetContractIDAddress() kp := a.kpFull hzAcc := a.stellarClient.GetHorizonAcc() @@ -201,29 +183,9 @@ func (a Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel. return errors.New("error while building fund tx") } txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "close", closeTxArgs, contractAddress, kp) - - // contractAddress := a.integrEnv.GetContractIDAddress() - // kp := a.kpFull - // acc := a.integrEnv.AccountDetails(kp) - // // generate tx to open the channel - // fundTxArgs, err := BuildCloseTxArgs(*state, sigs) - // if err != nil { - // return errors.New("error while building fund tx") - // } - // invokeHostFunctionOp := env.BuildContractCallOp(acc, "close", fundTxArgs, contractAddress) - - // preFlightOp, minFee := a.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) - - // tx, err := a.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) - // if err != nil { - // return errors.New("error while submitting operations with fee") - // } - - // // read out decoded Events and interpret them - // txMeta, err := env.DecodeTxMeta(tx) - // if err != nil { - // return errors.New("error while decoding tx meta") - // } + if err != nil { + return errors.New("error while invoking and processing host function: close") + } _, err = DecodeEvents(txMeta) if err != nil { return errors.New("error while decoding events") @@ -232,12 +194,12 @@ func (a Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel. } // Register registers and disputes a channel. -func (a Adjudicator) Register(ctx context.Context, req pchannel.AdjudicatorReq, states []pchannel.SignedState) error { +func (a *Adjudicator) Register(ctx context.Context, req pchannel.AdjudicatorReq, states []pchannel.SignedState) error { panic("implement me") } -func (a Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { - +func (a *Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { + fmt.Println("ForceClose called") contractAddress := a.stellarClient.GetContractIDAddress() kp := a.kpFull hzAcc := a.stellarClient.GetHorizonAcc() @@ -246,30 +208,9 @@ func (a Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pcha return errors.New("error while building fund tx") } txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "force_close", forceCloseTxArgs, contractAddress, kp) - - // //env := a.integrEnv - // contractAddress := a.integrEnv.GetContractIDAddress() - // kp := a.kpFull - // acc := a.integrEnv.AccountDetails(kp) - // // generate tx to open the channel - // fundTxArgs, err := BuildCloseTxArgs(*state, sigs) - // if err != nil { - // return errors.New("error while building fund tx") - // } - // invokeHostFunctionOp := env.BuildContractCallOp(acc, "force_close", fundTxArgs, contractAddress) - - // preFlightOp, minFee := a.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) - - // tx, err := a.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) - // if err != nil { - // return errors.New("error while submitting operations with fee") - // } - - // // read out decoded Events and interpret them - // txMeta, err := env.DecodeTxMeta(tx) - // if err != nil { - // return errors.New("error while decoding tx meta") - // } + if err != nil { + return errors.New("error while invoking and processing host function") + } _, err = DecodeEvents(txMeta) if err != nil { return errors.New("error while decoding events") diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index 1fbcd6e..a517d6c 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -3,6 +3,7 @@ package channel import ( "context" "errors" + "fmt" "github.com/stellar/go/xdr" pchannel "perun.network/go-perun/channel" log "perun.network/go-perun/log" @@ -28,25 +29,24 @@ type AdjEvent interface { // AdjudicatorSub implements the AdjudicatorSubscription interface. type AdjEventSub struct { - env *env.IntegrationTestEnv + //env *env.IntegrationTestEnv queryChanArgs xdr.ScVec - // current state of the channel - //chanState wire.Channel - chanControl wire.Control - cid pchannel.ID - events chan AdjEvent - Ev []AdjEvent - err error - panicErr chan error - cancel context.CancelFunc - closer *pkgsync.Closer - pollInterval time.Duration - log log.Embedding + stellarClient *env.StellarClient + chanControl wire.Control + cid pchannel.ID + events chan AdjEvent + Ev []AdjEvent + err error + panicErr chan error + cancel context.CancelFunc + closer *pkgsync.Closer + pollInterval time.Duration + log log.Embedding } -func (e *AdjEventSub) GetState() <-chan AdjEvent { - return e.events -} +// func (e *AdjEventSub) GetState() <-chan AdjEvent { +// return e.events +// } func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env.StellarClient) *AdjEventSub { getChanArgs, err := env.BuildGetChannelTxArgs(cid) @@ -56,14 +56,15 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. sub := &AdjEventSub{ queryChanArgs: getChanArgs, - //agent: conn.PerunAgent, - chanControl: wire.Control{}, - events: make(chan AdjEvent, DefaultBufferSize), - Ev: make([]AdjEvent, 0), - panicErr: make(chan error, 1), - pollInterval: DefaultSubscriptionPollingInterval, - closer: new(pkgsync.Closer), - log: log.MakeEmbedding(log.Default()), + stellarClient: stellarClient, + chanControl: wire.Control{}, + cid: cid, + events: make(chan AdjEvent, DefaultBufferSize), + Ev: make([]AdjEvent, 0), + panicErr: make(chan error, 1), + pollInterval: DefaultSubscriptionPollingInterval, + closer: new(pkgsync.Closer), + log: log.MakeEmbedding(log.Default()), } ctx, sub.cancel = context.WithCancel(ctx) @@ -74,10 +75,14 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. func (s *AdjEventSub) run(ctx context.Context) { s.log.Log().Info("Listening for channel state changes") + chanControl, err := s.getChanControl() + if err != nil { + s.panicErr <- err + } + s.chanControl = chanControl finish := func(err error) { s.err = err close(s.events) - } polling: for { @@ -92,18 +97,22 @@ polling: case <-time.After(s.pollInterval): newChanControl, err := s.getChanControl() + fmt.Println("s.chanControl: ", s.chanControl) + fmt.Println("newChanControl: ", newChanControl) if err != nil { // if query was not successful, simply repeat - continue polling + //continue polling + s.panicErr <- err } // decode channel state difference to events - adjEvent, err := DifferencesInControls(newChanControl, s.chanControl) + adjEvent, err := DifferencesInControls(s.chanControl, newChanControl) if err != nil { s.panicErr <- err } if adjEvent == nil { + fmt.Println("No events yet, continuing polling...") s.log.Log().Debug("No events yet, continuing polling...") continue polling @@ -113,6 +122,7 @@ polling: // Store the event s.log.Log().Debugf("Found event: %v", adjEvent) + fmt.Println("Found event: ", adjEvent) s.events <- adjEvent return } @@ -124,68 +134,64 @@ func (s *AdjEventSub) getChanControl() (wire.Control, error) { // query channel state getChanArgs, err := env.BuildGetChannelTxArgs(s.cid) - //kpStellar := s.env.GetStellarClient().GetAccount() - - chanMeta, err := s.env.GetChannelState(getChanArgs) if err != nil { return wire.Control{}, err } - retVal := chanMeta.V3.SorobanMeta.ReturnValue - var chanState wire.Channel - - err = chanState.FromScVal(retVal) + chanParams, err := s.stellarClient.GetChannelState(getChanArgs) if err != nil { return wire.Control{}, err } - - chanControl := chanState.Control + chanControl := chanParams.Control return chanControl, nil } -func (s *AdjEventSub) chanStateToEvent(newState wire.Control) (AdjEvent, error) { - // query channel state - - currControl := s.chanControl +// func (s *AdjEventSub) chanStateToEvent(newState wire.Control) (AdjEvent, error) { +// // query channel state - newCControl, err := s.getChanControl() - if err != nil { - return nil, err - } +// currControl := s.chanControl +// //newCControl := wire.Control{} +// newCControl, err := s.getChanControl() +// if err != nil { +// return nil, err +// } - sameChannel := IdenticalControls(currControl, newCControl) +// sameChannel := IdenticalControls(currControl, newCControl) - if sameChannel { - return CloseEvent{}, nil - } +// if sameChannel { +// return CloseEvent{}, nil +// } - // state has changed: we evaluate the differences - adjEvents, err := DifferencesInControls(currControl, newCControl) - if err != nil { - return nil, err - } - return adjEvents, nil +// // state has changed: we evaluate the differences +// adjEvents, err := DifferencesInControls(currControl, newCControl) +// if err != nil { +// return nil, err +// } +// return adjEvents, nil -} +// } func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, error) { + fmt.Println("controlCurr: ", controlCurr) + fmt.Println("controlNext: ", controlNext) + if controlCurr.FundedA != controlNext.FundedA { if controlCurr.FundedA { - return nil, errors.New("channel cannot be unfunded before withdrawal") + return nil, errors.New("channel cannot be unfunded A before withdrawal") } if controlNext.WithdrawnA && controlNext.WithdrawnB { - return FundEvent{}, nil + return &FundEvent{}, nil } } if controlCurr.FundedB != controlNext.FundedB { if controlCurr.FundedB { - return nil, errors.New("channel cannot be unfunded before withdrawal") + return nil, errors.New("channel cannot be unfunded B before withdrawal") } if controlNext.WithdrawnA && controlNext.WithdrawnB { - return FundEvent{}, nil + return &FundEvent{}, nil } } @@ -193,7 +199,12 @@ func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, err if controlCurr.Closed { return nil, errors.New("channel cannot be reopened after closing") } - return CloseEvent{}, nil + if !controlCurr.Closed && controlNext.Closed { + fmt.Println("controlCurr.Closed need to insert data: ", controlCurr.Closed) + + return &CloseEvent{}, nil + } + return &CloseEvent{}, nil } if controlCurr.WithdrawnA != controlNext.WithdrawnA { @@ -201,7 +212,7 @@ func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, err return nil, errors.New("channel cannot be unwithdrawn") } if controlNext.WithdrawnA && controlNext.WithdrawnB { - return WithdrawnEvent{}, nil + return &WithdrawnEvent{}, nil } } @@ -210,7 +221,7 @@ func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, err return nil, errors.New("channel cannot be unwithdrawn") } if controlNext.WithdrawnA && controlNext.WithdrawnB { - return WithdrawnEvent{}, nil + return &WithdrawnEvent{}, nil } } @@ -218,7 +229,7 @@ func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, err if controlCurr.Disputed { return nil, errors.New("channel cannot be undisputed") } - return DisputedEvent{}, nil + return &DisputedEvent{}, nil } return nil, nil diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go index b26fb4e..1dd8935 100644 --- a/channel/adjudicator_test.go +++ b/channel/adjudicator_test.go @@ -1,17 +1,14 @@ package channel_test import ( + "context" "fmt" - - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + pchannel "perun.network/go-perun/channel" pwallet "perun.network/go-perun/wallet" - pkgtest "polycry.pt/poly-go/test" chtest "perun.network/perun-stellar-backend/channel/test" - //client "perun.network/perun-stellar-backend/client" - "perun.network/perun-stellar-backend/wallet" + "perun.network/perun-stellar-backend/util" _ "perun.network/perun-stellar-backend/wallet/test" "perun.network/perun-stellar-backend/wire" @@ -25,163 +22,191 @@ import ( func TestCloseChannel(t *testing.T) { itest := env.NewBackendEnv() - kps, _ := itest.CreateAccounts(2, "10000000") + kps, _ := itest.CreateAccounts(3, "100000000") kpAlice := kps[0] kpBob := kps[1] + kpDeployer := kps[2] - reqAlice := itest.AccountDetails(kpAlice) - reqBob := itest.AccountDetails(kpBob) + // reqAlice := itest.AccountDetails(kpAlice) + // reqBob := itest.AccountDetails(kpBob) + reqDeployer := itest.AccountDetails(kpDeployer) - fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) + //fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) + contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) + itest.SetContractIDAddress(contractAddr) + fmt.Println("contractID: ", contractAddr) - installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), channel.PerunContractPath) - preFlightOp, minFee := itest.PreflightHostFunctions(&reqAlice, *installContractOp) - _ = itest.MustSubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + //perunFirstParams, perunFirstState := chtest.NewParamsState(t) + wAlice, accAlice, _ := util.MakeRandPerunWallet() + wBob, accBob, _ := util.MakeRandPerunWallet() + addrAlice := accAlice.Address() + addrBob := accBob.Address() + addrList := []pwallet.Address{addrAlice, addrBob} + perunFirstParams, perunFirstState := chtest.NewParamsWithAddressState(t, addrList) + fmt.Println("perunFirstParams, perunFirstState: ", perunFirstParams, perunFirstState) + + freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) + fmt.Println("freqAlice: ", freqAlice) + freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) + fmt.Println("freqBob: ", freqBob) + freqs := []*pchannel.FundingReq{freqAlice, freqBob} + fmt.Println("freqs: ", freqs) + + // Creating the client and funders as pointers + aliceClient := env.NewStellarClient(itest, kpAlice) + bobClient := env.NewStellarClient(itest, kpBob) + + aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) + bobFunder := channel.NewFunder(accBob, kpBob, bobClient) + funders := []*channel.Funder{aliceFunder, bobFunder} + fmt.Println("funders: ", funders) + chanID := perunFirstParams.ID() + //aliceIdx := false + //fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) + //require.NoError(t, err) - // Create the contract + // Calling the function + err := chtest.FundAll(context.TODO(), funders, freqs) + require.NoError(t, err) + fmt.Println("Funded all") + fmt.Println("aliceFunder: ", aliceFunder) + fmt.Println("aliceFunder: ", wBob, accBob, wAlice) - createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), channel.PerunContractPath, "a1", itest.GetPassPhrase()) - preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *createContractOp) - _, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + hzAliceGetCh := aliceClient.GetHorizonAcc() + getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) require.NoError(t, err) - // contract has been deployed, now invoke 'open' fn on the contract - contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId - require.NotNil(t, contractID) - contractIDAddress := xdr.ScAddress{ - Type: xdr.ScAddressTypeScAddressTypeContract, - ContractId: contractID, - } + txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice) + require.NoError(t, err) - // make L2 Accounts for signing states: + retVal := txMetaGetChAfterFunding.V3.SorobanMeta.ReturnValue + fmt.Println("retVal txMetaGetChAfterFunding: ", retVal) - rng := pkgtest.Prng(t) + var getChanAfterFunding wire.Channel - wAlice := wallet.NewEphemeralWallet() - accAlice, _, err := wAlice.AddNewAccount(rng) + err = getChanAfterFunding.FromScVal(retVal) require.NoError(t, err) - addrAlice := accAlice.Address() + fmt.Println("getChanAfterFunding: ", getChanAfterFunding) - wBob := wallet.NewEphemeralWallet() - accBob, _, err := wBob.AddNewAccount(rng) - require.NoError(t, err) - addrBob := accBob.Address() - - addrList := []pwallet.Address{addrAlice, addrBob} - - // invoke open-channel function + fmt.Println("getChan.Control.FundedA, getChan.Control.FundedA: ", getChanAfterFunding.Control.FundedA, getChanAfterFunding.Control.FundedA) - perunFirstParams, perunFirstState := chtest.NewParamsWithAddressState(t, addrList) + // close the channel - openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) + currStellarState := getChanAfterFunding.State - invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress) + currStellarState.Finalized = true - preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + currPerunState, err := wire.ToState(currStellarState) - tx, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) require.NoError(t, err) + fmt.Println("currStellarState: ", currStellarState) - clientTx, err := itest.Client().TransactionDetail(tx.Hash) - fmt.Println("clientTx: ", clientTx) + sigA, err := channel.Backend.Sign(accAlice, &currPerunState) require.NoError(t, err) - assert.Equal(t, tx.Hash, clientTx.Hash) - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(clientTx.ResultXdr, &txResult) + sigB, err := channel.Backend.Sign(accBob, &currPerunState) require.NoError(t, err) - opResults, ok := txResult.OperationResults() + sigs := []pwallet.Sig{sigA, sigB} - assert.True(t, ok) - assert.Equal(t, len(opResults), 1) - invokeHostFunctionResult, ok := opResults[0].MustTr().GetInvokeHostFunctionResult() - assert.True(t, ok) - assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) + // addrAlice := kpAlice.FromAddress() + // channel.Backend.Sign(addrAlice, currStellarState) - txMeta, err := env.DecodeTxMeta(tx) + closeArgs, err := channel.BuildCloseTxArgs(currPerunState, sigs) require.NoError(t, err) + hzAliceGClose := aliceClient.GetHorizonAcc() - perunEvents, err := channel.DecodeEvents(txMeta) - require.NoError(t, err) + invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr) - fmt.Println("perunEvents: ", perunEvents) + preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&hzAliceGClose, *invokeHostFunctionOpClose) - // fund the channel after it is open + fmt.Println("minFeeClose: ", minFeeClose) - chanID := perunFirstParams.ID() - aliceIdx := false + txClose, err := itest.SubmitOperationsWithFee(&hzAliceGClose, kpAlice, minFeeClose, &preFlightOpClose) - fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) require.NoError(t, err) - invokeHostFunctionOpFund := env.BuildContractCallOp(reqAlice, "fund", fundArgsAlice, contractIDAddress) - - preFlightOpFund, minFeeFund := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpFund) + txMetaClose, err := env.DecodeTxMeta(txClose) - txFund, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeFund, &preFlightOpFund) - fmt.Println("minFeeFund: ", minFeeFund) require.NoError(t, err) - txMetaFund, err := env.DecodeTxMeta(txFund) - require.NoError(t, err) + perunEventsClose, err := channel.DecodeEvents(txMetaClose) - perunEventsFund, err := channel.DecodeEvents(txMetaFund) require.NoError(t, err) - fmt.Println("perunEventsFund: ", perunEventsFund) + fmt.Println("perunEventsClose: ", perunEventsClose) - bobIdx := true - fundArgsBob, err := env.BuildFundTxArgs(chanID, bobIdx) - require.NoError(t, err) - invokeHostFunctionOpFund2 := env.BuildContractCallOp(reqBob, "fund", fundArgsBob, contractIDAddress) + // now both users can withdraw their funds - preFlightOpFund2, minFeeFund2 := itest.PreflightHostFunctions(&reqBob, *invokeHostFunctionOpFund2) - fmt.Println("minFeeFund2: ", minFeeFund2) +} - txFund2, err := itest.SubmitOperationsWithFee(&reqBob, kpBob, minFeeFund2, &preFlightOpFund2) +func TestWithdrawChannel(t *testing.T) { + itest := env.NewBackendEnv() + kps, _ := itest.CreateAccounts(3, "100000000") + + kpAlice := kps[0] + kpBob := kps[1] + kpDeployer := kps[2] + + reqDeployer := itest.AccountDetails(kpDeployer) + + //fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) + contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) + itest.SetContractIDAddress(contractAddr) + + //perunFirstParams, perunFirstState := chtest.NewParamsState(t) + _, accAlice, _ := util.MakeRandPerunWallet() + _, accBob, _ := util.MakeRandPerunWallet() + + addrAlice := accAlice.Address() + addrBob := accBob.Address() + addrList := []pwallet.Address{addrAlice, addrBob} + perunFirstParams, perunFirstState := chtest.NewParamsWithAddressState(t, addrList) + + freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) + freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) + freqs := []*pchannel.FundingReq{freqAlice, freqBob} + + // Creating the client and funders as pointers + aliceClient := env.NewStellarClient(itest, kpAlice) + bobClient := env.NewStellarClient(itest, kpBob) + + aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) + bobFunder := channel.NewFunder(accBob, kpBob, bobClient) + funders := []*channel.Funder{aliceFunder, bobFunder} + chanID := perunFirstParams.ID() + + // Calling the function + err := chtest.FundAll(context.TODO(), funders, freqs) require.NoError(t, err) - txMetaFund2, err := env.DecodeTxMeta(txFund2) - require.NoError(t, err) - fmt.Println("txMetaFund2: ", txMetaFund2) - perunEventsFund2, err := channel.DecodeEvents(txMetaFund2) - require.NoError(t, err) - fmt.Println("perunEventsFund2: ", perunEventsFund2) + fmt.Println("Channel fully funded") - // qurey channel state + hzAliceGetCh := aliceClient.GetHorizonAcc() getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) require.NoError(t, err) - invokeHostFunctionOpGetCh := env.BuildContractCallOp(reqAlice, "get_channel", getChannelArgs, contractIDAddress) - preFlightOpGetCh, minFeeGetCh := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpGetCh) - txGetCh, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeGetCh, &preFlightOpGetCh) + txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice) require.NoError(t, err) - txMetaGetCh, err := env.DecodeTxMeta(txGetCh) - require.NoError(t, err) - fmt.Println("txMetaGetCh: ", txMetaGetCh) - retVal := txMetaGetCh.V3.SorobanMeta.ReturnValue - fmt.Println("retVal: ", retVal) + retVal := txMetaGetChAfterFunding.V3.SorobanMeta.ReturnValue - var getChan wire.Channel + var getChanAfterFunding wire.Channel - err = getChan.FromScVal(retVal) + err = getChanAfterFunding.FromScVal(retVal) require.NoError(t, err) - fmt.Println("getChan: ", getChan) // close the channel - currStellarState := getChan.State + currStellarState := getChanAfterFunding.State currStellarState.Finalized = true currPerunState, err := wire.ToState(currStellarState) require.NoError(t, err) - fmt.Println("currStellarState: ", currStellarState) sigA, err := channel.Backend.Sign(accAlice, &currPerunState) require.NoError(t, err) @@ -191,17 +216,15 @@ func TestCloseChannel(t *testing.T) { sigs := []pwallet.Sig{sigA, sigB} - // addrAlice := kpAlice.FromAddress() - // channel.Backend.Sign(addrAlice, currStellarState) - closeArgs, err := channel.BuildCloseTxArgs(currPerunState, sigs) require.NoError(t, err) + hzAliceGClose := aliceClient.GetHorizonAcc() - invokeHostFunctionOpClose := env.BuildContractCallOp(reqAlice, "close", closeArgs, contractIDAddress) + invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr) - preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpClose) + preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&hzAliceGClose, *invokeHostFunctionOpClose) - txClose, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeClose, &preFlightOpClose) + txClose, err := itest.SubmitOperationsWithFee(&hzAliceGClose, kpAlice, minFeeClose, &preFlightOpClose) require.NoError(t, err) @@ -209,11 +232,37 @@ func TestCloseChannel(t *testing.T) { require.NoError(t, err) - perunEventsClose, err := channel.DecodeEvents(txMetaClose) + _, err = channel.DecodeEvents(txMetaClose) + require.NoError(t, err) + hzAliceWithdraw := aliceClient.GetHorizonAcc() + waArgs, err := env.BuildFundTxArgs(chanID, false) + require.NoError(t, err) + invokeHostFunctionOpWA := env.BuildContractCallOp(hzAliceWithdraw, "withdraw", waArgs, contractAddr) + preFlightOpWA, minFeeWA := itest.PreflightHostFunctions(&hzAliceWithdraw, *invokeHostFunctionOpWA) + txWithdrawAlice, err := itest.SubmitOperationsWithFee(&hzAliceWithdraw, kpAlice, minFeeWA, &preFlightOpWA) require.NoError(t, err) - fmt.Println("perunEventsClose: ", perunEventsClose) - // now both users can withdraw their funds + txMetaWA, err := env.DecodeTxMeta(txWithdrawAlice) + + require.NoError(t, err) + _, err = channel.DecodeEvents(txMetaWA) + require.NoError(t, err) + + hzBobWithdraw := bobClient.GetHorizonAcc() + wbArgs, err := env.BuildFundTxArgs(chanID, true) + require.NoError(t, err) + invokeHostFunctionOpWB := env.BuildContractCallOp(hzBobWithdraw, "withdraw", wbArgs, contractAddr) + preFlightOpWB, minFeeWB := itest.PreflightHostFunctions(&hzBobWithdraw, *invokeHostFunctionOpWB) + txWithdrawBob, err := itest.SubmitOperationsWithFee(&hzBobWithdraw, kpBob, minFeeWB, &preFlightOpWB) + require.NoError(t, err) + + txMetaWB, err := env.DecodeTxMeta(txWithdrawBob) + + require.NoError(t, err) + perunEventsWB, err := channel.DecodeEvents(txMetaWB) + require.NoError(t, err) + + fmt.Println("perunEventsWB: ", perunEventsWB) } diff --git a/channel/deploy.go b/channel/deploy.go index bf5dea4..1cf6dcf 100644 --- a/channel/deploy.go +++ b/channel/deploy.go @@ -7,29 +7,10 @@ import ( "github.com/stellar/go/xdr" "log" "os" - //"perun.network/perun-stellar-backend/channel/env" ) const PerunContractPath = "../testdata/perun_soroban_contract.wasm" -// func InstallAndCreateContract(reqAlice *YourReqType, kpAlice YourKeyType) error { // Assuming "YourReqType" and "YourKeyType" are the types of `reqAlice` and `kpAlice` respectively. -// // Install the contract -// installContractOp := AssembleInstallContractCodeOp(kpAlice.Address(), PerunContractPath) -// preFlightOp, minFee := env.PreflightHostFunctions(reqAlice, *installContractOp) -// if err := itest.MustSubmitOperationsWithFee(reqAlice, kpAlice, 5*minFee, &preFlightOp); err != nil { -// return err -// } - -// // Create the contract -// createContractOp := client.AssembleCreateContractOp(kpAlice.Address(), client.PerunContractPath, "a1", itest.GetPassPhrase()) -// preFlightOp, minFee = itest.PreflightHostFunctions(reqAlice, *createContractOp) -// if _, err := itest.SubmitOperationsWithFee(reqAlice, kpAlice, minFee, &preFlightOp); err != nil { -// return err -// } - -// return nil -// } - func AssembleInstallContractCodeOp(sourceAccount string, wasmFileName string) *txnbuild.InvokeHostFunction { // Assemble the InvokeHostFunction UploadContractWasm operation: // CAP-0047 - https://github.com/stellar/stellar-protocol/blob/master/core/cap-0047.md#creating-a-contract-using-invokehostfunctionop diff --git a/channel/env/client.go b/channel/env/client.go index dfbdd70..785a5d5 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -1,66 +1,76 @@ package env import ( + //"context" "errors" "fmt" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/xdr" + //pchannel "perun.network/go-perun/channel" + "perun.network/perun-stellar-backend/wire" ) type StellarClient struct { stellarEnv *IntegrationTestEnv - account *keypair.Full - hzAccount horizon.Account + kp *keypair.Full passphrase string contractIDAddress xdr.ScAddress } -func NewStellarClient(stellarEnv *IntegrationTestEnv, account *keypair.Full) *StellarClient { +func NewStellarClient(stellarEnv *IntegrationTestEnv, kp *keypair.Full) *StellarClient { passphrase := stellarEnv.GetPassPhrase() return &StellarClient{ - stellarEnv: stellarEnv, - account: account, - passphrase: passphrase, + stellarEnv: stellarEnv, + kp: kp, + passphrase: passphrase, + contractIDAddress: stellarEnv.contractIDAddress, } + } -func (s StellarClient) GetAccount() *keypair.Full { - return s.account +func (s *StellarClient) GetAccount() *keypair.Full { + return s.kp } -func (s StellarClient) GetHorizonAcc() horizon.Account { - return s.hzAccount +func (s *StellarClient) GetHorizonAcc() horizon.Account { + return s.stellarEnv.AccountDetails(s.kp) } -func (s StellarClient) GetPassPhrase() string { +func (s *StellarClient) GetPassPhrase() string { return s.passphrase } -func (s StellarClient) GetContractIDAddress() xdr.ScAddress { +func (s *StellarClient) GetContractIDAddress() xdr.ScAddress { return s.contractIDAddress } -func (s StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { +func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { + // Build contract call operation fnameXdr := xdr.ScSymbol(fname) + fmt.Println("contractAddr in InvokeAndProcessHostFunction: ", contractAddr) invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr) // Preflight host functions + fmt.Println("horizonAcc in InvokeAndProcessHostFunction: ", horizonAcc) preFlightOp, minFee := s.stellarEnv.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) - + fmt.Println("minfee for", fname, ": ", minFee) // Submit operations with fee tx, err := s.stellarEnv.SubmitOperationsWithFee(&horizonAcc, kp, minFee, &preFlightOp) if err != nil { - return xdr.TransactionMeta{}, errors.New("error while submitting operations with fee") + panic(err) + //return xdr.TransactionMeta{}, errors.New("error while submitting operations with fee") } + fmt.Println("tx from ", fname, ": ", tx) + // Decode transaction metadata txMeta, err := DecodeTxMeta(tx) if err != nil { return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") } - fmt.Println("txMeta: ", txMeta) + fmt.Println("txMeta from ", fname, ": ", txMeta) // // Decode events // _, err = DecodeSorEvents(txMeta) @@ -70,3 +80,25 @@ func (s StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, return txMeta, nil } + +func (s *StellarClient) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) { + fmt.Println("in GetChannelState") + contractAddress := s.stellarEnv.GetContractIDAddress() + kp := s.kp + hz := s.GetHorizonAcc() + + txMeta, err := s.InvokeAndProcessHostFunction(hz, "get_channel", chanArgs, contractAddress, kp) + if err != nil { + return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") + } + + retVal := txMeta.V3.SorobanMeta.ReturnValue + var getChan wire.Channel + + err = getChan.FromScVal(retVal) + if err != nil { + return wire.Channel{}, errors.New("error while decoding return value") + } + return getChan, nil + +} diff --git a/channel/env/integration.go b/channel/env/integration.go index 0f3a9ff..3e014e4 100644 --- a/channel/env/integration.go +++ b/channel/env/integration.go @@ -2,7 +2,6 @@ package env import ( "errors" - "fmt" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" @@ -20,7 +19,6 @@ const SorobanRPCPort = 8080 type IntegrationTestEnv struct { testEnv *testenv.Test contractIDAddress xdr.ScAddress - stellarClient *StellarClient } func NewBackendEnv() *IntegrationTestEnv { @@ -44,9 +42,9 @@ func NewIntegrationEnv(t *testing.T) *IntegrationTestEnv { return &itest } -// func (it *IntegrationTestEnv) GetStellarClient() *StellarClient { -// return it.stellarClient -// } +func (it *IntegrationTestEnv) SetContractIDAddress(contractIDAddress xdr.ScAddress) { + it.contractIDAddress = contractIDAddress +} func (it *IntegrationTestEnv) CreateAccounts(numAccounts int, initBalance string) ([]*keypair.Full, []txnbuild.Account) { kps, accs := it.testEnv.CreateAccounts(numAccounts, initBalance) @@ -117,25 +115,5 @@ func (it *IntegrationTestEnv) InvokeAndProcessHostFunction(horizonAcc horizon.Ac return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") } - fmt.Println("txMeta: ", txMeta) - - // // Decode events - // _, err = DecodeSorEvents(txMeta) - // if err != nil { - // return errors.New("error while decoding events") - // } - return txMeta, nil } - -func (it *IntegrationTestEnv) GetChannelState(getChanArgs xdr.ScVec) (xdr.TransactionMeta, error) { - // query channel state - kp := it.stellarClient.account - - acc := it.AccountDetails(kp) - chanState, err := it.InvokeAndProcessHostFunction(acc, "get_channel", getChanArgs, xdr.ScAddress{}, nil) - if err != nil { - return xdr.TransactionMeta{}, err - } - return chanState, nil -} diff --git a/channel/env/transaction.go b/channel/env/transaction.go index 1690e30..d416bd1 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -1,10 +1,10 @@ package env import ( + "fmt" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" - pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/wire" "perun.network/perun-stellar-backend/wire/scval" @@ -77,9 +77,11 @@ func BuildGetChannelTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { copy(chanid[:], chanIDStellar) channelID, err := scval.WrapScBytes(chanIDStellar) if err != nil { - return xdr.ScVec{}, err + panic(err) } + fmt.Println("channelID in Wire version", channelID.Bytes) + getChannelArgs := xdr.ScVec{ channelID, } @@ -102,8 +104,6 @@ func BuildForceCloseTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { return getChannelArgs, nil } -//InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) - func DecodeTxMeta(tx horizon.Transaction) (xdr.TransactionMeta, error) { var transactionMeta xdr.TransactionMeta err := xdr.SafeUnmarshalBase64(tx.ResultMetaXdr, &transactionMeta) diff --git a/channel/event.go b/channel/event.go index 29ce53e..29a3734 100644 --- a/channel/event.go +++ b/channel/event.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/stellar/go/xdr" + "log" pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/wire" "time" @@ -104,99 +105,99 @@ type StellarEvent struct { ChannelState wire.Channel } -func (e StellarEvent) GetType() EventType { +func (e *StellarEvent) GetType() EventType { return e.Type } -func (e OpenEvent) GetChannel() wire.Channel { +func (e *OpenEvent) GetChannel() wire.Channel { return e.Channel } -func (e OpenEvent) ID() pchannel.ID { +func (e *OpenEvent) ID() pchannel.ID { return e.IDV } -func (e OpenEvent) Version() Version { +func (e *OpenEvent) Version() Version { return e.VersionV } -func (e OpenEvent) Tstamp() uint64 { +func (e *OpenEvent) Tstamp() uint64 { return e.Timestamp } -func (e OpenEvent) Timeout() pchannel.Timeout { +func (e *OpenEvent) Timeout() pchannel.Timeout { when := time.Now().Add(10 * time.Second) pollInterval := 1 * time.Second return NewTimeout(when, pollInterval) } -func (e WithdrawnEvent) GetChannel() wire.Channel { +func (e *WithdrawnEvent) GetChannel() wire.Channel { return e.Channel } -func (e WithdrawnEvent) ID() pchannel.ID { +func (e *WithdrawnEvent) ID() pchannel.ID { return e.IDV } -func (e WithdrawnEvent) Version() Version { +func (e *WithdrawnEvent) Version() Version { return e.VersionV } func (e WithdrawnEvent) Tstamp() uint64 { return e.Timestamp } -func (e WithdrawnEvent) Timeout() pchannel.Timeout { +func (e *WithdrawnEvent) Timeout() pchannel.Timeout { when := time.Now().Add(10 * time.Second) pollInterval := 1 * time.Second return NewTimeout(when, pollInterval) } -func (e CloseEvent) GetChannel() wire.Channel { +func (e *CloseEvent) GetChannel() wire.Channel { return e.Channel } -func (e CloseEvent) ID() pchannel.ID { +func (e *CloseEvent) ID() pchannel.ID { return e.IDV } -func (e CloseEvent) Version() Version { +func (e *CloseEvent) Version() Version { return e.VersionV } -func (e CloseEvent) Tstamp() uint64 { +func (e *CloseEvent) Tstamp() uint64 { return e.Timestamp } -func (e CloseEvent) Timeout() pchannel.Timeout { +func (e *CloseEvent) Timeout() pchannel.Timeout { when := time.Now().Add(10 * time.Second) pollInterval := 1 * time.Second return NewTimeout(when, pollInterval) } -func (e FundEvent) Timeout() pchannel.Timeout { +func (e *FundEvent) Timeout() pchannel.Timeout { when := time.Now().Add(10 * time.Second) pollInterval := 1 * time.Second return NewTimeout(when, pollInterval) } -func (e FundEvent) ID() pchannel.ID { +func (e *FundEvent) ID() pchannel.ID { return e.IDV } -func (e FundEvent) Version() Version { +func (e *FundEvent) Version() Version { return e.VersionV } -func (e FundEvent) Tstamp() uint64 { +func (e *FundEvent) Tstamp() uint64 { return e.Timestamp } -func (e DisputedEvent) Timeout() pchannel.Timeout { +func (e *DisputedEvent) Timeout() pchannel.Timeout { when := time.Now().Add(10 * time.Second) pollInterval := 1 * time.Second return NewTimeout(when, pollInterval) } -func (e DisputedEvent) ID() pchannel.ID { +func (e *DisputedEvent) ID() pchannel.ID { return e.IDV } -func (e DisputedEvent) Version() Version { +func (e *DisputedEvent) Version() Version { return e.VersionV } -func (e DisputedEvent) Tstamp() uint64 { +func (e *DisputedEvent) Tstamp() uint64 { return e.Timestamp } @@ -252,7 +253,11 @@ func DecodeEvents(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { Channel: openEventchanStellar, } - evs = append(evs, openEvent) + evs = append(evs, &openEvent) + log.Println("OpenEvent: ", openEvent) + log.Println("OpenEvent-Params: ", openEvent.Channel.Params) + log.Println("OpenEvent-State: ", openEvent.Channel.State) + log.Println("OpenEvent-Control: ", openEvent.Channel.Control) case EventTypeFundChannel: fundEventchanStellar, _, err := GetChannelBoolFromEvents(ev.Body.V0.Data) @@ -263,7 +268,7 @@ func DecodeEvents(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { fundEvent := FundEvent{ Channel: fundEventchanStellar, } - evs = append(evs, fundEvent) + evs = append(evs, &fundEvent) case EventTypeClosed: closedEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) @@ -274,7 +279,16 @@ func DecodeEvents(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { closeEvent := CloseEvent{ Channel: closedEventchanStellar, } - evs = append(evs, closeEvent) + evs = append(evs, &closeEvent) + case EventTypeWithdrawn: + withdrawnEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) + if err != nil { + return []PerunEvent{}, err + } + withdrawnEvent := WithdrawnEvent{ + Channel: withdrawnEventchanStellar, + } + evs = append(evs, &withdrawnEvent) } } @@ -341,7 +355,7 @@ func checkOpen(cState controlsState) error { return nil } -func (e CloseEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { +func (e *CloseEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { chanID := chanState.State.ChannelID var cid [32]byte @@ -354,7 +368,7 @@ func (e CloseEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint6 return nil } -func (e FundEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { +func (e *FundEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { chanID := chanState.State.ChannelID var cid [32]byte @@ -367,7 +381,7 @@ func (e FundEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64 return nil } -func (e WithdrawnEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { +func (e *WithdrawnEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { chanID := chanState.State.ChannelID var cid [32]byte @@ -380,7 +394,7 @@ func (e WithdrawnEvent) EventDataFromChannel(chanState wire.Channel, timestamp u return nil } -func (e DisputedEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { +func (e *DisputedEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint64) error { chanID := chanState.State.ChannelID var cid [32]byte diff --git a/channel/funder.go b/channel/funder.go index f7c2912..69b14e3 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -3,12 +3,11 @@ package channel import ( "context" "errors" - //"fmt" + "fmt" "github.com/stellar/go/keypair" "log" pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel/env" - //"perun.network/perun-stellar-backend/client" "perun.network/perun-stellar-backend/wallet" "perun.network/perun-stellar-backend/wire" @@ -27,19 +26,18 @@ type Funder struct { pollingInterval time.Duration } -func NewFunder(acc *wallet.Account, stellarClient *env.StellarClient) *Funder { +func NewFunder(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient) *Funder { return &Funder{ - //integrEnv: *env.NewBackendEnv(), stellarClient: stellarClient, acc: acc, + kpFull: kp, maxIters: MaxIterationsUntilAbort, pollingInterval: DefaultPollingInterval, } } -func (f Funder) Fund(ctx context.Context, req pchannel.FundingReq) error { +func (f *Funder) Fund(ctx context.Context, req pchannel.FundingReq) error { log.Println("Fund called") - switch req.Idx { case 0: return f.fundPartyA(ctx, req) @@ -50,22 +48,30 @@ func (f Funder) Fund(ctx context.Context, req pchannel.FundingReq) error { } } -func (f Funder) fundPartyA(ctx context.Context, req pchannel.FundingReq) error { +func (f *Funder) fundPartyA(ctx context.Context, req pchannel.FundingReq) error { + fmt.Println("req: polling for party A: ", req) + err := f.OpenChannel(ctx, req.Params, req.State) if err != nil { - return err - } + return errors.New("error while opening channel in party A") + } + fmt.Println("opened channel in party A, checking state") + chanState, err := f.GetChannelState(ctx, req.Params, req.State) + fmt.Println("chanState after opening channel: ", chanState) + if err != nil { + return errors.New("error while polling for opened channel A") + } + fmt.Println("polled chanState for PartyA: ", chanState.Control.FundedA, chanState.Control.FundedB) // fund the channel: + fmt.Println("funding channel in party A: ", req.Params, "state: ", req.State) + err = f.FundChannel(ctx, req.Params, req.State, false) if err != nil { return err } - // look for channel ID in events - //chanID := req.State.ID - // await response from party B polling: @@ -74,10 +80,12 @@ polling: case <-ctx.Done(): return f.AbortChannel(ctx, req.Params, req.State) case <-time.After(f.pollingInterval): + fmt.Println("Party A: Polling for opened channel...") chanState, err := f.GetChannelState(ctx, req.Params, req.State) if err != nil { continue polling } + fmt.Println("Party A: chanState.Control.FundedA && chanState.Control.FundedB: ", chanState.Control.FundedA, chanState.Control.FundedB) if chanState.Control.FundedA && chanState.Control.FundedB { return nil @@ -88,9 +96,14 @@ polling: return f.AbortChannel(ctx, req.Params, req.State) } -func (f Funder) fundPartyB(ctx context.Context, req pchannel.FundingReq) error { -polling: +func (f *Funder) fundPartyB(ctx context.Context, req pchannel.FundingReq) error { + fmt.Println("req: polling for party B: ", req) + // err := f.OpenChannel(ctx, req.Params, req.State) + // if err != nil { + // return errors.New("error while opening channel in party A") + // } +polling: for { select { case <-ctx.Done(): @@ -98,6 +111,7 @@ polling: case <-time.After(f.pollingInterval): log.Println("Party B: Polling for opened channel...") chanState, err := f.GetChannelState(ctx, req.Params, req.State) + fmt.Println("polled chanState for PartyB: ", chanState.Control.FundedA, chanState.Control.FundedB) if err != nil { log.Println("Party B: Error while polling for opened channel:", err) continue polling @@ -112,17 +126,22 @@ polling: } } -func (f Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { +func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { //env := f.integrEnv contractAddress := f.stellarClient.GetContractIDAddress() kp := f.kpFull - reqAlice := f.stellarClient.GetHorizonAcc() + hz := f.stellarClient.GetHorizonAcc() // generate tx to open the channel openTxArgs := env.BuildOpenTxArgs(params, state) - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "open", openTxArgs, contractAddress, kp) + fmt.Println("openTxArgs: ", openTxArgs) + + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "open", openTxArgs, contractAddress, kp) + if err != nil { + return errors.New("error while invoking and processing host function: open") + } // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) @@ -146,33 +165,29 @@ func (f Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state return nil } -func (f Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { +func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { //env := f.integrEnv contractAddress := f.stellarClient.GetContractIDAddress() kp := f.kpFull - reqAlice := f.stellarClient.GetHorizonAcc() + hzAcc := f.stellarClient.GetHorizonAcc() chanId := state.ID // generate tx to open the channel - openTxArgs, err := env.BuildFundTxArgs(chanId, funderIdx) - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "fund", openTxArgs, contractAddress, kp) - - // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) + fundTxArgs, err := env.BuildFundTxArgs(chanId, funderIdx) + if err != nil { + return errors.New("error while building fund tx") + } + fmt.Println("funderchan args: ", contractAddress, kp, hzAcc, chanId, fundTxArgs, funderIdx) - // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hzAcc, "fund", fundTxArgs, contractAddress, kp) + if err != nil { + return errors.New("error while invoking and processing host function: fund") + } - // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) - // if err != nil { - // return errors.New("error while submitting operations with fee") - // } + fmt.Println("txMeta in fund: ", txMeta) - // read out decoded Events and interpret them - // txMeta, err := env.DecodeTxMeta(tx) - // if err != nil { - // return errors.New("error while decoding tx meta") - // } _, err = DecodeEvents(txMeta) if err != nil { return errors.New("error while decoding events") @@ -215,7 +230,7 @@ func (f Funder) FundChannel(ctx context.Context, params *pchannel.Params, state // return nil // } -func (f Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { +func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { contractAddress := f.stellarClient.GetContractIDAddress() kp := f.kpFull @@ -226,20 +241,6 @@ func (f Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state openTxArgs, err := env.BuildGetChannelTxArgs(chanId) txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp) - // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) - - // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) - - // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) - // if err != nil { - // return errors.New("error while submitting operations with fee") - // } - - // read out decoded Events and interpret them - // txMeta, err := env.DecodeTxMeta(tx) - // if err != nil { - // return errors.New("error while decoding tx meta") - // } _, err = DecodeEvents(txMeta) if err != nil { return errors.New("error while decoding events") @@ -248,59 +249,23 @@ func (f Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state return nil } -// func (f Funder) Abort(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { - -// //env := f.integrEnv -// contractAddress := f.integrEnv.GetContractIDAddress() -// kp := f.kpFull -// reqAlice := f.integrEnv.AccountDetails(kp) -// // generate tx to open the channel -// openTxArgs := env.BuildGetChannelTxArgs(chanId) -// invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "abort_channel", openTxArgs, contractAddress) - -// preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) - -// tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) -// if err != nil { -// return errors.New("error while submitting operations with fee") -// } - -// // read out decoded Events and interpret them -// txMeta, err := env.DecodeTxMeta(tx) -// if err != nil { -// return errors.New("error while decoding tx meta") -// } -// _, err = DecodeEvents(txMeta) -// if err != nil { -// return errors.New("error while decoding events") -// } - -// return nil -// } - -func (f Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { - - //env := f.integrEnv +func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { contractAddress := f.stellarClient.GetContractIDAddress() kp := f.kpFull - reqAlice := f.stellarClient.GetHorizonAcc() + hz := f.stellarClient.GetHorizonAcc() chanId := state.ID // generate tx to open the channel - openTxArgs, err := env.BuildGetChannelTxArgs(chanId) - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp) - - // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) - - // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) - - // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) - // if err != nil { - // return errors.New("error while submitting operations with fee") - // } + getchTxArgs, err := env.BuildGetChannelTxArgs(chanId) + if err != nil { + return wire.Channel{}, errors.New("error while building get_channel tx") + } - // read out decoded Events and interpret them + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "get_channel", getchTxArgs, contractAddress, kp) + if err != nil { + return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") + } retVal := txMeta.V3.SorobanMeta.ReturnValue var getChan wire.Channel @@ -312,39 +277,3 @@ func (f Funder) GetChannelState(ctx context.Context, params *pchannel.Params, st return getChan, nil } - -// func (f Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { - -// //env := f.integrEnv -// contractAddress := f.integrEnv.GetContractIDAddress() -// kp := f.kpFull -// acc := f.integrEnv.AccountDetails(kp) -// chanID := state.ID -// // generate tx to open the channel -// getChTxArgs, err := env.BuildGetChannelTxArgs(chanID) -// if err != nil { -// return wire.Channel{}, errors.New("error while building fund tx") -// } -// invokeHostFunctionOp := env.BuildContractCallOp(acc, "get_channel", getChTxArgs, contractAddress) - -// preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) - -// tx, err := f.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) -// if err != nil { -// return wire.Channel{}, errors.New("error while submitting operations with fee") -// } - -// txMeta, err := env.DecodeTxMeta(tx) -// if err != nil { -// return wire.Channel{}, errors.New("error while decoding tx meta") -// } - -// retVal := txMeta.V3.SorobanMeta.ReturnValue -// var getChan wire.Channel - -// err = getChan.FromScVal(retVal) -// if err != nil { -// return wire.Channel{}, errors.New("error while decoding return value") -// } -// return wire.Channel{}, nil -// } diff --git a/channel/funder_test.go b/channel/funder_test.go index 7f86d30..05d0ae8 100644 --- a/channel/funder_test.go +++ b/channel/funder_test.go @@ -1,22 +1,27 @@ package channel_test import ( + "context" "fmt" + "perun.network/perun-stellar-backend/util" + "github.com/stellar/go/xdr" - "github.com/stretchr/testify/assert" + //"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" chtest "perun.network/perun-stellar-backend/channel/test" - //client "perun.network/perun-stellar-backend/client" _ "perun.network/perun-stellar-backend/wallet/test" - "perun.network/perun-stellar-backend/wire" + //"perun.network/perun-stellar-backend/wire" + pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel" "perun.network/perun-stellar-backend/channel/env" "testing" ) +const PerunContractPath = "../testdata/perun_soroban_contract.wasm" + func TestOpenChannel(t *testing.T) { itest := env.NewIntegrationEnv(t) @@ -59,23 +64,6 @@ func TestOpenChannel(t *testing.T) { tx, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) require.NoError(t, err) - //clientTx, err := itest.Client().TransactionDetail(tx.Hash) - //fmt.Println("clientTx: ", clientTx) - //require.NoError(t, err) - - //assert.Equal(t, tx.Hash, clientTx.Hash) - //var txResult xdr.TransactionResult - //err = xdr.SafeUnmarshalBase64(clientTx.ResultXdr, &txResult) - //require.NoError(t, err) - - //opResults, ok := txResult.OperationResults() - - // assert.True(t, ok) - // assert.Equal(t, len(opResults), 1) - // invokeHostFunctionResult, ok := opResults[0].MustTr().GetInvokeHostFunctionResult() - // assert.True(t, ok) - //assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) - txMeta, err := env.DecodeTxMeta(tx) require.NoError(t, err) @@ -89,134 +77,154 @@ func TestOpenChannel(t *testing.T) { func TestFundChannel(t *testing.T) { itest := env.NewBackendEnv() - kps, _ := itest.CreateAccounts(2, "10000000") + kps, _ := itest.CreateAccounts(3, "1000000000") kpAlice := kps[0] kpBob := kps[1] + kpDeployer := kps[2] reqAlice := itest.AccountDetails(kpAlice) reqBob := itest.AccountDetails(kpBob) + reqDeployer := itest.AccountDetails(kpDeployer) fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) + contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) + itest.SetContractIDAddress(contractAddr) + fmt.Println("contractID: ", contractAddr) - installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), channel.PerunContractPath) - preFlightOp, minFee := itest.PreflightHostFunctions(&reqAlice, *installContractOp) - _ = itest.MustSubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) - - // Create the contract - - createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), channel.PerunContractPath, "a1", itest.GetPassPhrase()) - preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *createContractOp) - _, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + perunFirstParams, perunFirstState := chtest.NewParamsState(t) - require.NoError(t, err) + fmt.Println("perunFirstParams, perunFirstState: ", perunFirstParams, perunFirstState) - // contract has been deployed, now invoke 'open' fn on the contract - contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId - require.NotNil(t, contractID) - contractIDAddress := xdr.ScAddress{ - Type: xdr.ScAddressTypeScAddressTypeContract, - ContractId: contractID, - } + wAlice, accAlice, _ := util.MakeRandPerunWallet() + wBob, accBob, _ := util.MakeRandPerunWallet() - // invoke open-channel function + freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) + fmt.Println("freqAlice: ", freqAlice) + freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) + fmt.Println("freqBob: ", freqBob) + freqs := []*pchannel.FundingReq{freqAlice, freqBob} + fmt.Println("freqs: ", freqs) - perunFirstParams, perunFirstState := chtest.NewParamsState(t) + // Creating the client and funders as pointers + aliceClient := env.NewStellarClient(itest, kpAlice) + bobClient := env.NewStellarClient(itest, kpBob) - openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) + aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) + bobFunder := channel.NewFunder(accBob, kpBob, bobClient) + funders := []*channel.Funder{aliceFunder, bobFunder} + fmt.Println("funders: ", funders) + chanID := perunFirstParams.ID() + aliceIdx := false + fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) + require.NoError(t, err) + fmt.Println("chanID: ", chanID) - invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress) + fmt.Println("fundArgs outside: ", contractAddr, kpAlice, reqAlice, chanID, fundArgsAlice, aliceIdx) + //fmt.Println("funderchan args: ", contractAddress, kp, hzAcc, chanId, fundTxArgs, funderIdx) - preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) + //fmt.Println("funderchan args: ", contractAddress, kp, hzAcc, chanId, funderIdx) - tx, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) + // Calling the function + err = chtest.FundAll(context.TODO(), funders, freqs) require.NoError(t, err) + fmt.Println("Funded all") + fmt.Println("aliceFunder: ", aliceFunder) + fmt.Println("aliceFunder: ", wBob, accBob, wAlice) - clientTx, err := itest.Client().TransactionDetail(tx.Hash) - fmt.Println("clientTx: ", clientTx) - require.NoError(t, err) + // err = aliceFunder.OpenChannel(context.TODO(), perunFirstParams, perunFirstState) + // require.NoError(t, err) + //chanOpened, err := aliceFunder.GetChannelState(context.TODO(), perunFirstParams, perunFirstState) + //require.NoError(t, err) - assert.Equal(t, tx.Hash, clientTx.Hash) - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(clientTx.ResultXdr, &txResult) - require.NoError(t, err) + //fmt.Println("chanOpened state: ", chanOpened) - opResults, ok := txResult.OperationResults() + // openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) + // fnameXdr := xdr.ScSymbol("open") + // hzAlice := aliceClient.GetHorizonAcc() + // hzBob := bobClient.GetHorizonAcc() - assert.True(t, ok) - assert.Equal(t, len(opResults), 1) - invokeHostFunctionResult, ok := opResults[0].MustTr().GetInvokeHostFunctionResult() - assert.True(t, ok) - assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) + // //invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, fnameXdr, openArgs, contractAddr) + // invokeHostFunctionOp := env.BuildContractCallOp(hzAlice, fnameXdr, openArgs, contractAddr) + // preFlightOp, minFeeOpen := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOp) - txMeta, err := env.DecodeTxMeta(tx) - require.NoError(t, err) + // _, err = itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeOpen, &preFlightOp) + // require.NoError(t, err) - perunEvents, err := channel.DecodeEvents(txMeta) - require.NoError(t, err) + // // check for Channel State - fmt.Println("perunEvents: ", perunEvents) + // getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) + // require.NoError(t, err) + // invokeHostFunctionOpGetChOpen := env.BuildContractCallOp(hzAlice, "get_channel", getChannelArgs, contractAddr) + // preFlightOpGetChOpen, minFeeGetChOpen := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOpGetChOpen) + // txGetChOpen, err := itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeGetChOpen, &preFlightOpGetChOpen) + // require.NoError(t, err) - // fund the channel after it is open + // txMetaGetChOpen, err := env.DecodeTxMeta(txGetChOpen) + // require.NoError(t, err) + // fmt.Println("txMetaGetChOpen: ", txMetaGetChOpen) + // retValOpen := txMetaGetChOpen.V3.SorobanMeta.ReturnValue + // fmt.Println("retVal: ", retValOpen) - chanID := perunFirstParams.ID() - aliceIdx := false + // var getChanOpen wire.Channel - fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) - require.NoError(t, err) + // err = getChanOpen.FromScVal(retValOpen) + // require.NoError(t, err) + // fmt.Println("getChanOpen: ", getChanOpen) + // // check get state - invokeHostFunctionOpFund := env.BuildContractCallOp(reqAlice, "fund", fundArgsAlice, contractIDAddress) + // invokeHostFunctionOpFund := env.BuildContractCallOp(hzAlice, "fund", fundArgsAlice, contractAddr) - preFlightOpFund, minFeeFund := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpFund) + // preFlightOpFund, minFeeFund := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOpFund) - txFund, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeFund, &preFlightOpFund) - fmt.Println("minFeeFund: ", minFeeFund) - require.NoError(t, err) + // txFund, err := itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeFund, &preFlightOpFund) + // fmt.Println("minFeeFund: ", minFeeFund) + // require.NoError(t, err) - txMetaFund, err := env.DecodeTxMeta(txFund) - require.NoError(t, err) + // txMetaFund, err := env.DecodeTxMeta(txFund) + // require.NoError(t, err) - perunEventsFund, err := channel.DecodeEvents(txMetaFund) - require.NoError(t, err) - fmt.Println("perunEventsFund: ", perunEventsFund) + // perunEventsFund, err := channel.DecodeEvents(txMetaFund) + // require.NoError(t, err) + // fmt.Println("perunEventsFund: ", perunEventsFund) - bobIdx := true - fundArgsBob, err := env.BuildFundTxArgs(chanID, bobIdx) - require.NoError(t, err) - invokeHostFunctionOpFund2 := env.BuildContractCallOp(reqBob, "fund", fundArgsBob, contractIDAddress) + // bobIdx := true + // fundArgsBob, err := env.BuildFundTxArgs(chanID, bobIdx) + // require.NoError(t, err) + // invokeHostFunctionOpFund2 := env.BuildContractCallOp(hzBob, "fund", fundArgsBob, contractAddr) - preFlightOpFund2, minFeeFund2 := itest.PreflightHostFunctions(&reqBob, *invokeHostFunctionOpFund2) - fmt.Println("minFeeFund2: ", minFeeFund2) + // preFlightOpFund2, minFeeFund2 := itest.PreflightHostFunctions(&hzBob, *invokeHostFunctionOpFund2) + // fmt.Println("minFeeFund2: ", minFeeFund2) - txFund2, err := itest.SubmitOperationsWithFee(&reqBob, kpBob, minFeeFund2, &preFlightOpFund2) + // txFund2, err := itest.SubmitOperationsWithFee(&reqBob, kpBob, minFeeFund2, &preFlightOpFund2) - require.NoError(t, err) - txMetaFund2, err := env.DecodeTxMeta(txFund2) - require.NoError(t, err) - fmt.Println("txMetaFund2: ", txMetaFund2) - perunEventsFund2, err := channel.DecodeEvents(txMetaFund2) - require.NoError(t, err) - fmt.Println("perunEventsFund2: ", perunEventsFund2) + // require.NoError(t, err) + // txMetaFund2, err := env.DecodeTxMeta(txFund2) + // require.NoError(t, err) + // fmt.Println("txMetaFund2: ", txMetaFund2) + // perunEventsFund2, err := channel.DecodeEvents(txMetaFund2) + // require.NoError(t, err) + // fmt.Println("perunEventsFund2: ", perunEventsFund2) - // qurey channel state + // // qurey channel state - getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) - require.NoError(t, err) + // // getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) + // // require.NoError(t, err) - invokeHostFunctionOpGetCh := env.BuildContractCallOp(reqAlice, "get_channel", getChannelArgs, contractIDAddress) - preFlightOpGetCh, minFeeGetCh := itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOpGetCh) - txGetCh, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFeeGetCh, &preFlightOpGetCh) - require.NoError(t, err) + // invokeHostFunctionOpGetCh := env.BuildContractCallOp(hzAlice, "get_channel", getChannelArgs, contractAddr) + // preFlightOpGetCh, minFeeGetCh := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOpGetCh) + // txGetCh, err := itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeGetCh, &preFlightOpGetCh) + // require.NoError(t, err) - txMetaGetCh, err := env.DecodeTxMeta(txGetCh) - require.NoError(t, err) - fmt.Println("txMetaGetCh: ", txMetaGetCh) - retVal := txMetaGetCh.V3.SorobanMeta.ReturnValue - fmt.Println("retVal: ", retVal) + // txMetaGetCh, err := env.DecodeTxMeta(txGetCh) + // require.NoError(t, err) + // fmt.Println("txMetaGetCh: ", txMetaGetCh) + // retVal := txMetaGetCh.V3.SorobanMeta.ReturnValue + // fmt.Println("retVal: ", retVal) - var getChan wire.Channel + // var getChan wire.Channel - err = getChan.FromScVal(retVal) - require.NoError(t, err) - fmt.Println("getChan: ", getChan) + // err = getChan.FromScVal(retVal) + // require.NoError(t, err) + // fmt.Println("getChan: ", getChan) } diff --git a/channel/subscribe.go b/channel/subscribe.go index c8422d1..eab3476 100644 --- a/channel/subscribe.go +++ b/channel/subscribe.go @@ -1,6 +1,9 @@ package channel import ( + "fmt" + "reflect" + pchannel "perun.network/go-perun/channel" ) @@ -13,9 +16,12 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { if s.Events() == nil { return nil } + select { case event := <-s.Events(): + fmt.Println("Event received: ", event) if event == nil { + fmt.Println("Event nil received: ", event) return nil } @@ -23,30 +29,30 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { switch e := event.(type) { case *DisputedEvent: - + fmt.Println("DisputedEvent received") dispEvent := pchannel.AdjudicatorEventBase{ VersionV: e.Version(), IDV: e.ID(), TimeoutV: MakeTimeout(timestamp), } - - ddn := &pchannel.RegisteredEvent{AdjudicatorEventBase: dispEvent, - State: nil, - Sigs: nil} + ddn := &pchannel.RegisteredEvent{AdjudicatorEventBase: dispEvent, State: nil, Sigs: nil} s.closer.Close() return ddn + case *CloseEvent: + + fmt.Println("CloseEvent received") conclEvent := pchannel.AdjudicatorEventBase{ VersionV: e.Version(), IDV: e.ID(), TimeoutV: MakeTimeout(timestamp), } - ccn := &pchannel.ConcludedEvent{ - AdjudicatorEventBase: conclEvent, - } + ccn := &pchannel.ConcludedEvent{AdjudicatorEventBase: conclEvent} s.closer.Close() return ccn + default: + fmt.Printf("Received an unknown event type: %v\n", reflect.TypeOf(e)) return nil } diff --git a/channel/test/fund.go b/channel/test/fund.go new file mode 100644 index 0000000..f6ab831 --- /dev/null +++ b/channel/test/fund.go @@ -0,0 +1,23 @@ +package test + +import ( + "context" + pchannel "perun.network/go-perun/channel" + "perun.network/perun-stellar-backend/channel" + pkgerrors "polycry.pt/poly-go/errors" +) + +func FundAll(ctx context.Context, funders []*channel.Funder, reqs []*pchannel.FundingReq) error { + g := pkgerrors.NewGatherer() + for i := range funders { + i := i + g.Go(func() error { + return funders[i].Fund(ctx, *reqs[i]) + }) + } + + if g.WaitDoneOrFailedCtx(ctx) { + return ctx.Err() + } + return g.Err() +} diff --git a/client/channel.go b/client/channel.go index ee52a3e..11bcd8b 100644 --- a/client/channel.go +++ b/client/channel.go @@ -9,6 +9,7 @@ import ( "perun.network/go-perun/channel" "perun.network/go-perun/client" + "strconv" ) @@ -52,14 +53,6 @@ func (c PaymentChannel) SendPayment(amount int64) { } } -// func (p *PaymentClient) SendPaymentToPeer(amount float64) { -// if !p.HasOpenChannel() { -// return -// } -// amountInt64 := int64(amount) -// p.Channel.SendPayment(amountInt64) -// } - // Settle settles the payment channel and withdraws the funds. func (c PaymentChannel) Settle() { // If the channel is not finalized: Finalize the channel to enable fast settlement. diff --git a/client/client.go b/client/client.go index 53ed96f..b4b5e66 100644 --- a/client/client.go +++ b/client/client.go @@ -1,21 +1,21 @@ package client import ( + "context" + "errors" "fmt" "github.com/stellar/go/keypair" + //"github.com/stellar/go/protocols/horizon" "math/big" - "perun.network/go-perun/wire/net/simple" - - "errors" pchannel "perun.network/go-perun/channel" "perun.network/go-perun/client" "perun.network/go-perun/watcher/local" "perun.network/go-perun/wire" + "perun.network/go-perun/wire/net/simple" "perun.network/perun-stellar-backend/channel" - "perun.network/perun-stellar-backend/channel/types" - "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/channel/types" "perun.network/perun-stellar-backend/wallet" ) @@ -25,9 +25,8 @@ type PaymentClient struct { currency pchannel.Asset channels chan *PaymentChannel Channel *PaymentChannel - //ICConn *chanconn.Connector - wAddr wire.Address - balance *big.Int + wAddr wire.Address + balance *big.Int } func SetupPaymentClient( @@ -36,17 +35,16 @@ func SetupPaymentClient( acc *wallet.Account, stellarKp *keypair.Full, stellarTokenID types.StellarAsset, - //acc *wallet.Account, bus *wire.LocalBus, + //hzAcc *horizon.Account, ) (*PaymentClient, error) { // Connect to Perun pallet and get funder + adjudicator from it. perunConn := env.NewStellarClient(stellarEnv, stellarKp) - - funder := channel.NewFunder(acc, perunConn) - adj := channel.NewAdjudicator(acc, perunConn) + funder := channel.NewFunder(acc, stellarKp, perunConn) + adj := channel.NewAdjudicator(acc, stellarKp, perunConn) // Setup dispute watcher. watcher, err := local.NewWatcher(adj) @@ -60,7 +58,6 @@ func SetupPaymentClient( if err != nil { return nil, errors.New("creating client") } - //asset := types.NewStellarAsset(big.NewInt(int64(chainID)), common.Address(assetaddr)) // Create client and start request handler. c := &PaymentClient{ @@ -68,9 +65,8 @@ func SetupPaymentClient( account: acc, currency: &stellarTokenID, channels: make(chan *PaymentChannel, 1), - //ICConn: perunConn, - wAddr: wireAddr, - balance: big.NewInt(0), + wAddr: wireAddr, + balance: big.NewInt(0), } //go c.PollBalances() @@ -88,115 +84,59 @@ func (c *PaymentClient) startWatching(ch *client.Channel) { }() } -// func SetupPaymentClient( -// name string, -// w *wallet.FsWallet, // w is the wallet used to resolve addresses to accounts for channels. -// bus *wire.LocalBus, -// perunID string, -// ledgerID string, -// host string, -// port int, -// accountPath string, -// ) (*PaymentClient, error) { - -// acc := w.NewAccount() - -// // Connect to Perun pallet and get funder + adjudicator from it. - -// perunConn := chanconn.NewICConnector(perunID, ledgerID, accountPath, host, port) - -// funder := channel.NewFunder(acc, perunConn) -// adj := channel.NewAdjudicator(acc, perunConn) - -// // Setup dispute watcher. -// watcher, err := local.NewWatcher(adj) -// if err != nil { -// return nil, fmt.Errorf("intializing watcher: %w", err) -// } - -// // Setup Perun client. -// wireAddr := simple.NewAddress(acc.Address().String()) -// perunClient, err := client.New(wireAddr, bus, funder, adj, w, watcher) -// if err != nil { -// return nil, errors.WithMessage(err, "creating client") -// } - -// // Create client and start request handler. -// c := &PaymentClient{ -// Name: name, -// perunClient: perunClient, -// account: &acc, -// currency: channel.Asset, -// channels: make(chan *PaymentChannel, 1), -// ICConn: perunConn, -// wAddr: wireAddr, -// balance: big.NewInt(0), -// } - -// go c.PollBalances() -// go perunClient.Handle(c, c) -// return c, nil -// } - -// // SetupPaymentClient creates a new payment client. -// func SetupPaymentClient( -// bus wire.Bus, // bus is used of off-chain communication. -// w *swallet.Wallet, // w is the wallet used for signing transactions. -// acc common.Address, // acc is the address of the account to be used for signing transactions. -// eaddress *ethwallet.Address, // eaddress is the address of the Ethereum account to be used for signing transactions. -// nodeURL string, // nodeURL is the URL of the blockchain node. -// chainID uint64, // chainID is the identifier of the blockchain. -// adjudicator common.Address, // adjudicator is the address of the adjudicator. -// assetaddr ethwallet.Address, // asset is the address of the asset holder for our payment channels. -// ) (*PaymentClient, error) { -// // Create Ethereum client and contract backend. -// cb, err := CreateContractBackend(nodeURL, chainID, w) -// if err != nil { -// return nil, fmt.Errorf("creating contract backend: %w", err) -// } - -// // Validate contracts. -// err = ethchannel.ValidateAdjudicator(context.TODO(), cb, adjudicator) -// if err != nil { -// return nil, fmt.Errorf("validating adjudicator: %w", err) -// } -// err = ethchannel.ValidateAssetHolderETH(context.TODO(), cb, common.Address(assetaddr), adjudicator) -// if err != nil { -// return nil, fmt.Errorf("validating adjudicator: %w", err) -// } - -// // Setup funder. -// funder := ethchannel.NewFunder(cb) -// dep := ethchannel.NewETHDepositor() -// ethAcc := accounts.Account{Address: acc} -// asset := ethchannel.NewAsset(big.NewInt(int64(chainID)), common.Address(assetaddr)) -// funder.RegisterAsset(*asset, dep, ethAcc) - -// // Setup adjudicator. -// adj := ethchannel.NewAdjudicator(cb, adjudicator, acc, ethAcc) - -// // Setup dispute watcher. -// watcher, err := local.NewWatcher(adj) -// if err != nil { -// return nil, fmt.Errorf("intializing watcher: %w", err) -// } - -// // Setup Perun client. -// waddr := ðwire.Address{Address: eaddress} -// perunClient, err := client.New(waddr, bus, funder, adj, w, watcher) -// if err != nil { -// return nil, errors.WithMessage(err, "creating client") -// } - -// // Create client and start request handler. -// c := &PaymentClient{ -// perunClient: perunClient, -// account: eaddress, -// waddress: waddr, -// currency: asset, -// channels: make(chan *PaymentChannel, 1), -// } -// go perunClient.Handle(c, c) - -// return c, nil -// } +// OpenChannel opens a new channel with the specified peer and funding. +func (c *PaymentClient) OpenChannel(peer wire.Address, 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.WireAddress(), peer} + + // We create an initial allocation which defines the starting balances. + initBal := big.NewInt(int64(amount)) + + initAlloc := pchannel.NewAllocation(2, c.currency) + initAlloc.SetAssetBalances(c.currency, []pchannel.Bal{ + initBal, // Our initial balance. + initBal, // Peer's initial balance. + }) + + // 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.Address(), + 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) + c.Channel = newPaymentChannel(ch, c.currency) + //c.Channel.ch.OnUpdate(c.NotifyAllState) + //c.NotifyAllState(nil, ch.State()) + +} + +// AcceptedChannel returns the next accepted channel. +func (c *PaymentClient) AcceptedChannel() *PaymentChannel { + return <-c.channels +} + +func (p *PaymentClient) WireAddress() wire.Address { + return p.wAddr +} + +// Shutdown gracefully shuts down the client. +func (c *PaymentClient) Shutdown() { + c.perunClient.Close() +} diff --git a/main.go b/main.go index 3aa60af..44ceb82 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,8 @@ import ( "perun.network/perun-stellar-backend/util" ) +const PerunContractPath = "./testdata/perun_soroban_contract.wasm" + func main() { // Create a Backend to interact with the Stellar network: integration test environment from the stellar/go sdk @@ -28,9 +30,9 @@ func main() { // Deploy the contract - contractIDAddress := util.Deploy(stellarEnv, kpDeployer, hzDeployer) - - fmt.Println("Deployed contractIDAddress: ", contractIDAddress) + contractIDAddress := util.Deploy(stellarEnv, kpDeployer, hzDeployer, PerunContractPath) + stellarEnv.SetContractIDAddress(contractIDAddress) + //fmt.Println("Deployed contractIDAddress: ", contractIDAddress) // Generate L2 accounts for the payment channel wAlice, accAlice, _ := util.MakeRandPerunWallet() @@ -47,6 +49,15 @@ func main() { panic(err) } - fmt.Println("alicePerun, bobPerun: ", alicePerun, bobPerun) + alicePerun.OpenChannel(bobPerun.WireAddress(), 1000) + aliceChannel := alicePerun.Channel + bobChannel := bobPerun.AcceptedChannel() + fmt.Println("All funded", alicePerun, bobPerun) + + aliceChannel.Settle() + bobChannel.Settle() + + alicePerun.Shutdown() + bobPerun.Shutdown() } diff --git a/util/util.go b/util/util.go index a67c088..0d970cc 100644 --- a/util/util.go +++ b/util/util.go @@ -9,24 +9,21 @@ import ( "perun.network/perun-stellar-backend/channel/types" "perun.network/perun-stellar-backend/wallet" - //pkgtest "polycry.pt/poly-go/test" "crypto/rand" "encoding/binary" mathrand "math/rand" ) -const PerunContractPath = "./testdata/perun_soroban_contract.wasm" - -func Deploy(stellarEnv *env.IntegrationTestEnv, kpAlice *keypair.Full, hzDeployer horizon.Account) xdr.ScAddress { +func Deploy(stellarEnv *env.IntegrationTestEnv, kp *keypair.Full, hz horizon.Account, contractPath string) xdr.ScAddress { // Install contract - installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), PerunContractPath) - preFlightOp, minFee := stellarEnv.PreflightHostFunctions(&hzDeployer, *installContractOp) - _ = stellarEnv.MustSubmitOperationsWithFee(&hzDeployer, kpAlice, minFee, &preFlightOp) + installContractOp := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) + preFlightOp, minFee := stellarEnv.PreflightHostFunctions(&hz, *installContractOp) + _ = stellarEnv.MustSubmitOperationsWithFee(&hz, kp, minFee, &preFlightOp) // Create the contract - createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), PerunContractPath, "a1", stellarEnv.GetPassPhrase()) - preFlightOp, minFee = stellarEnv.PreflightHostFunctions(&hzDeployer, *createContractOp) - _, err := stellarEnv.SubmitOperationsWithFee(&hzDeployer, kpAlice, minFee, &preFlightOp) + createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", stellarEnv.GetPassPhrase()) + preFlightOp, minFee = stellarEnv.PreflightHostFunctions(&hz, *createContractOp) + _, err := stellarEnv.SubmitOperationsWithFee(&hz, kp, minFee, &preFlightOp) if err != nil { panic(err) } From 376a86d4f0ec9e79e9cedf740b79cb797c3a7ec2 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 7 Nov 2023 08:22:16 +0100 Subject: [PATCH 04/51] LICENSE, README: Add license and additional information channel: small formatting changes .assets: Add Perun image for README --- .assets/go-perun.png | Bin 0 -> 150688 bytes LICENSE | 202 +++++++++++++++++++++++++++++++++++++ README.md | 74 ++++++++++++++ channel/adjudicator.go | 3 +- channel/adjudicator_sub.go | 1 + channel/subscribe.go | 2 - 6 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 .assets/go-perun.png create mode 100644 LICENSE create mode 100644 README.md diff --git a/.assets/go-perun.png b/.assets/go-perun.png new file mode 100644 index 0000000000000000000000000000000000000000..6d8787bc738a1a711e97f8603987542675d6e3ee GIT binary patch literal 150688 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

8nZB^GeR(LCMaAb{OIE4WU)d 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

j?gU|`V_x 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 literal 0 HcmV?d00001 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..487790a --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +

+ Perun +

+ + +# [Perun](https://perun.network/) Stellar backend + +This repository contains the [Stellar](https://dfinity.org/) backend for the [go-perun](https://github.com/perun-network/go-perun) channel library. It provides the necessary components to run our secure peer-to-peer Perun Payment Channels on the Stellar blockchain, using a [Soroban](https://soroban.stellar.org/) smart contract implementation. The payment channel implementation connects our Perun state machine with the [Perun contract](https://github.com/perun-network/perun-soroban-contract), which implements the Perun Payment Channel logic on the Stellar blockchain. This project is financed through the Stellar Community Fund grants program. + +In the following sections, we will describe how to run our Payment Channels on a local instance of the Stellar blockchain. + +## [Setup](#setup) + +1. We use a fork of the Stellar Go SDK, which is located [here](https://github.com/perun-network/go). Our payment channels use the integration test environment of the Stellar Go SDK, which supports Soroban functionality. You can find more information on the test environment [here](https://github.com/perun-network/go/tree/master/services/horizon/internal/docs). + +However, we will summarize the necessary steps to run the Stellar Go client with Soroban support here: + +Clone the forked Stellar Go SDK: + +```sh +git clone https://github.com/perun-network/go +``` + +Build the horizon binary: + +```sh +cd go/services/horizon/ +go build -o stellar-horizon && go install +``` +Add the stellar-horizon executable in you PATH in your ~/.bashrc file. You can find the exact description of the process [here](https://github.com/perun-network/go/blob/master/services/horizon/internal/docs/GUIDE_FOR_DEVELOPERS.md#building-horizon). + +To run the Postgres database server, you start a docker container in the docker folder: + +```sh +docker-compose -f ./docker/docker-compose.yml up horizon-postgres +``` + +After using the Stellar backend, you can stop the Postgres database server with: + +```sh +docker-compose -f ./docker/docker-compose.yml down +``` + +2. To use the integration test environment of the Stellar Go SDK, you need to set the correct environment variables: + +```sh +export HORIZON_INTEGRATION_TESTS_ENABLED="true" +export HORIZON_INTEGRATION_TESTS_CORE_MAX_SUPPORTED_PROTOCOL="20" +export HORIZON_INTEGRATION_TESTS_ENABLE_SOROBAN_RPC="true" +export HORIZON_INTEGRATION_TESTS_DOCKER_IMG="stellar/stellar-core:19.13.1-1481.3acf6dd26.focal" +export HORIZON_INTEGRATION_TESTS_SOROBAN_RPC_DOCKER_IMG="stellar/soroban-rpc:20.0.0-rc3-39" +``` + +Note that these settings are required to support the Soroban smart contract functionality using the Stellar Go SDK. You can find the exact specifications on the test environment [here](https://github.com/stellar/go/blob/master/.github/workflows/horizon.yml). + +3. Running the payment channels: + +Having set up the working environment in the previous steps, you can clone the repository and run the payment channel tests: + +```sh +git clone https://github.com/perun-network/perun-stellar-backend +cd perun-stellar-backend +go test ./... +``` + +To run the simple payment channel demo, you can run the following command: + +```sh +go run main.go +``` + +## Copyright + +Copyright 2023 PolyCrypt GmbH. Use of the source code is governed by the Apache 2.0 license that can be found in the [LICENSE file](LICENSE). \ No newline at end of file diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 057c3f7..c8d2495 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -73,7 +73,7 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, return err } } - // close has been called, now we wait for the event + //close has been called, now we wait for the event err = a.waitForClosed(ctx, evSub, cid) if err != nil { return err @@ -153,7 +153,6 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) contractAddress := a.stellarClient.GetContractIDAddress() kp := a.kpFull hzAcc := a.stellarClient.GetHorizonAcc() - //chanId := req.Tx.State.ID // generate tx to open the channel withdrawTxArgs, err := a.BuildWithdrawTxArgs(req) diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index a517d6c..488ad5b 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -112,6 +112,7 @@ polling: } if adjEvent == nil { + s.chanControl = newChanControl fmt.Println("No events yet, continuing polling...") s.log.Log().Debug("No events yet, continuing polling...") continue polling diff --git a/channel/subscribe.go b/channel/subscribe.go index eab3476..18edf21 100644 --- a/channel/subscribe.go +++ b/channel/subscribe.go @@ -7,7 +7,6 @@ import ( pchannel "perun.network/go-perun/channel" ) -// Next implements the AdjudicatorSub.Next function. func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { if s.closer.IsClosed() { return nil @@ -19,7 +18,6 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { select { case event := <-s.Events(): - fmt.Println("Event received: ", event) if event == nil { fmt.Println("Event nil received: ", event) return nil From f112d41178b9f5286a3f6d19ac4476caaeb5c6c3 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 7 Nov 2023 15:43:41 +0100 Subject: [PATCH 05/51] channel: Remove additional Eventsubscription from Adjudicator.Withdraw. --- channel/adjudicator.go | 40 ++++++++++++++++++++++++++++++-------- channel/adjudicator_sub.go | 8 +++++--- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index c8d2495..10a8bf6 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -52,9 +52,9 @@ func (a *Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel. func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { - cid := req.Tx.ID + //cid := req.Tx.ID - txSigner := a.stellarClient + //txSigner := a.stellarClient if req.Tx.State.IsFinal { log.Println("Withdraw called") @@ -62,19 +62,32 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, // a.isConcluded - evSub := NewAdjudicatorSub(ctx, req.Tx.ID, txSigner) - defer evSub.Close() + //evSub := NewAdjudicatorSub(ctx, req.Tx.ID, txSigner) + //defer evSub.Close() err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) if err != nil { - if err == ErrChannelAlreadyClosed { - return a.withdraw(ctx, req) - } else { + getChanArgs, err := env.BuildGetChannelTxArgs(req.Tx.ID) + if err != nil { + panic(err) + } + chanControl, err := a.stellarClient.GetChannelState(getChanArgs) + if err != nil { return err } + + if chanControl.Control.Closed { + return a.withdraw(ctx, req) + } + + // if err == ErrChannelAlreadyClosed { + // return a.withdraw(ctx, req) + // } else { + // return err + // } } //close has been called, now we wait for the event - err = a.waitForClosed(ctx, evSub, cid) + //err = a.waitForClosed(ctx, evSub, cid) if err != nil { return err } @@ -83,6 +96,7 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, } else { err := a.ForceClose(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) + fmt.Println("ForceClose called") if err != nil { if err == ErrChannelAlreadyClosed { return a.withdraw(ctx, req) @@ -134,6 +148,15 @@ func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVe // build withdrawalargs chanIDStellar := req.Tx.ID[:] + partyIdx := req.Idx + var withdrawIdx xdr.ScVal + if partyIdx == 0 { + withdrawIdx = scval.MustWrapBool(false) + } else if partyIdx == 1 { + withdrawIdx = scval.MustWrapBool(true) + } else { + panic("partyIdx must be 0 or 1") + } var chanid xdr.ScBytes copy(chanid, chanIDStellar) channelID, err := scval.WrapScBytes(chanIDStellar) @@ -143,6 +166,7 @@ func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVe withdrawArgs := xdr.ScVec{ channelID, + withdrawIdx, } return withdrawArgs, nil diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index 488ad5b..61e3a78 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -74,6 +74,7 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. } func (s *AdjEventSub) run(ctx context.Context) { + fmt.Println("run() called") s.log.Log().Info("Listening for channel state changes") chanControl, err := s.getChanControl() if err != nil { @@ -91,9 +92,10 @@ polling: case <-ctx.Done(): finish(nil) return - case <-s.events: - finish(nil) - return + // could trigger race condition + // case <-s.events: + // finish(nil) + // return case <-time.After(s.pollInterval): newChanControl, err := s.getChanControl() From 40d1f521dbec5721904519444687956b8a6898e2 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 7 Nov 2023 15:53:21 +0100 Subject: [PATCH 06/51] main: Remove printline statements channel: Remove comments --- channel/adjudicator.go | 23 ----------------------- channel/adjudicator_sub.go | 11 +---------- main.go | 8 ++------ 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 10a8bf6..cfe673b 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -5,15 +5,12 @@ import ( "errors" "fmt" "github.com/stellar/go/keypair" - //"github.com/stellar/go/protocols/horizon" "github.com/stellar/go/xdr" "perun.network/go-perun/log" - "reflect" pchannel "perun.network/go-perun/channel" pwallet "perun.network/go-perun/wallet" "perun.network/perun-stellar-backend/channel/env" - //"perun.network/perun-stellar-backend/client" "perun.network/perun-stellar-backend/wallet" "perun.network/perun-stellar-backend/wire" @@ -52,18 +49,8 @@ func (a *Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel. func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { - //cid := req.Tx.ID - - //txSigner := a.stellarClient - if req.Tx.State.IsFinal { log.Println("Withdraw called") - // we listen for the close event - - // a.isConcluded - - //evSub := NewAdjudicatorSub(ctx, req.Tx.ID, txSigner) - //defer evSub.Close() err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) if err != nil { @@ -80,14 +67,7 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, return a.withdraw(ctx, req) } - // if err == ErrChannelAlreadyClosed { - // return a.withdraw(ctx, req) - // } else { - // return err - // } } - //close has been called, now we wait for the event - //err = a.waitForClosed(ctx, evSub, cid) if err != nil { return err } @@ -115,20 +95,17 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, func (a *Adjudicator) waitForClosed(ctx context.Context, evsub *AdjEventSub, cid pchannel.ID) error { a.log.Log().Tracef("Waiting for the channel closing event") - fmt.Println("Waiting for the channel closing event") loop: for { select { case event := <-evsub.Events(): - fmt.Println("reflect.TypeOf(event): ", reflect.TypeOf(event)) _, ok := event.(*CloseEvent) if !ok { continue loop } - fmt.Println("closeevent received: ", event) evsub.Close() return nil diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index 61e3a78..9436825 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -44,10 +44,6 @@ type AdjEventSub struct { log log.Embedding } -// func (e *AdjEventSub) GetState() <-chan AdjEvent { -// return e.events -// } - func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env.StellarClient) *AdjEventSub { getChanArgs, err := env.BuildGetChannelTxArgs(cid) if err != nil { @@ -99,12 +95,9 @@ polling: case <-time.After(s.pollInterval): newChanControl, err := s.getChanControl() - fmt.Println("s.chanControl: ", s.chanControl) - fmt.Println("newChanControl: ", newChanControl) if err != nil { - // if query was not successful, simply repeat - //continue polling + s.panicErr <- err } // decode channel state difference to events @@ -115,7 +108,6 @@ polling: if adjEvent == nil { s.chanControl = newChanControl - fmt.Println("No events yet, continuing polling...") s.log.Log().Debug("No events yet, continuing polling...") continue polling @@ -125,7 +117,6 @@ polling: // Store the event s.log.Log().Debugf("Found event: %v", adjEvent) - fmt.Println("Found event: ", adjEvent) s.events <- adjEvent return } diff --git a/main.go b/main.go index 44ceb82..447c8a6 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "perun.network/go-perun/wire" "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/client" @@ -22,12 +21,10 @@ func main() { kpBob := kps[1] kpDeployer := kps[2] - hzAlice := stellarEnv.AccountDetails(kpAlice) - hzBob := stellarEnv.AccountDetails(kpBob) + _ = stellarEnv.AccountDetails(kpAlice) + _ = stellarEnv.AccountDetails(kpBob) hzDeployer := stellarEnv.AccountDetails(kpDeployer) - fmt.Println("hzAlice, hzBob, hzDeployer: ", hzAlice, hzBob, hzDeployer) - // Deploy the contract contractIDAddress := util.Deploy(stellarEnv, kpDeployer, hzDeployer, PerunContractPath) @@ -52,7 +49,6 @@ func main() { alicePerun.OpenChannel(bobPerun.WireAddress(), 1000) aliceChannel := alicePerun.Channel bobChannel := bobPerun.AcceptedChannel() - fmt.Println("All funded", alicePerun, bobPerun) aliceChannel.Settle() bobChannel.Settle() From f7be18ba5ee9a4fb6380c2cd972b5bbc1849e0e7 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 28 Nov 2023 15:51:47 +0100 Subject: [PATCH 07/51] main.go: Include Stellar Token Asset Contract channel: Increase DefaultSubscriptionPollingInterval, DefaultPollingInterval constants to allow slow query responses during listening of adjudicator events. Remove comments and Println statements. testdata: Include perun_soroban_token.wasm, implementing generic Stellar asset tokens --- channel/adjudicator_sub.go | 36 +------ channel/adjudicator_test.go | 45 +-------- channel/env/client.go | 15 --- channel/env/integration.go | 2 - channel/env/transaction.go | 3 - channel/funder.go | 61 ++---------- channel/funder_test.go | 139 ++------------------------- channel/types/asset_test.go | 1 - main.go | 18 ++-- testdata/perun_soroban_contract.wasm | Bin 24044 -> 23908 bytes testdata/perun_soroban_token.wasm | Bin 0 -> 7620 bytes 11 files changed, 30 insertions(+), 290 deletions(-) create mode 100755 testdata/perun_soroban_token.wasm diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index 9436825..5eb37e7 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -15,7 +15,7 @@ import ( const ( DefaultBufferSize = 3 - DefaultSubscriptionPollingInterval = time.Duration(4) * time.Second + DefaultSubscriptionPollingInterval = time.Duration(10) * time.Second ) type AdjEvent interface { @@ -29,7 +29,6 @@ type AdjEvent interface { // AdjudicatorSub implements the AdjudicatorSubscription interface. type AdjEventSub struct { - //env *env.IntegrationTestEnv queryChanArgs xdr.ScVec stellarClient *env.StellarClient chanControl wire.Control @@ -70,7 +69,6 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. } func (s *AdjEventSub) run(ctx context.Context) { - fmt.Println("run() called") s.log.Log().Info("Listening for channel state changes") chanControl, err := s.getChanControl() if err != nil { @@ -88,10 +86,6 @@ polling: case <-ctx.Done(): finish(nil) return - // could trigger race condition - // case <-s.events: - // finish(nil) - // return case <-time.After(s.pollInterval): newChanControl, err := s.getChanControl() @@ -141,36 +135,8 @@ func (s *AdjEventSub) getChanControl() (wire.Control, error) { return chanControl, nil } -// func (s *AdjEventSub) chanStateToEvent(newState wire.Control) (AdjEvent, error) { -// // query channel state - -// currControl := s.chanControl -// //newCControl := wire.Control{} -// newCControl, err := s.getChanControl() -// if err != nil { -// return nil, err -// } - -// sameChannel := IdenticalControls(currControl, newCControl) - -// if sameChannel { -// return CloseEvent{}, nil -// } - -// // state has changed: we evaluate the differences -// adjEvents, err := DifferencesInControls(currControl, newCControl) -// if err != nil { -// return nil, err -// } -// return adjEvents, nil - -// } - func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, error) { - fmt.Println("controlCurr: ", controlCurr) - fmt.Println("controlNext: ", controlNext) - if controlCurr.FundedA != controlNext.FundedA { if controlCurr.FundedA { return nil, errors.New("channel cannot be unfunded A before withdrawal") diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go index 1dd8935..2be928a 100644 --- a/channel/adjudicator_test.go +++ b/channel/adjudicator_test.go @@ -2,7 +2,6 @@ package channel_test import ( "context" - "fmt" "github.com/stretchr/testify/require" pchannel "perun.network/go-perun/channel" pwallet "perun.network/go-perun/wallet" @@ -27,31 +26,21 @@ func TestCloseChannel(t *testing.T) { kpAlice := kps[0] kpBob := kps[1] kpDeployer := kps[2] - - // reqAlice := itest.AccountDetails(kpAlice) - // reqBob := itest.AccountDetails(kpBob) reqDeployer := itest.AccountDetails(kpDeployer) - //fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) itest.SetContractIDAddress(contractAddr) - fmt.Println("contractID: ", contractAddr) - //perunFirstParams, perunFirstState := chtest.NewParamsState(t) - wAlice, accAlice, _ := util.MakeRandPerunWallet() - wBob, accBob, _ := util.MakeRandPerunWallet() + _, accAlice, _ := util.MakeRandPerunWallet() + _, accBob, _ := util.MakeRandPerunWallet() addrAlice := accAlice.Address() addrBob := accBob.Address() addrList := []pwallet.Address{addrAlice, addrBob} perunFirstParams, perunFirstState := chtest.NewParamsWithAddressState(t, addrList) - fmt.Println("perunFirstParams, perunFirstState: ", perunFirstParams, perunFirstState) freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) - fmt.Println("freqAlice: ", freqAlice) freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) - fmt.Println("freqBob: ", freqBob) freqs := []*pchannel.FundingReq{freqAlice, freqBob} - fmt.Println("freqs: ", freqs) // Creating the client and funders as pointers aliceClient := env.NewStellarClient(itest, kpAlice) @@ -60,18 +49,10 @@ func TestCloseChannel(t *testing.T) { aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) bobFunder := channel.NewFunder(accBob, kpBob, bobClient) funders := []*channel.Funder{aliceFunder, bobFunder} - fmt.Println("funders: ", funders) chanID := perunFirstParams.ID() - //aliceIdx := false - //fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) - //require.NoError(t, err) - // Calling the function err := chtest.FundAll(context.TODO(), funders, freqs) require.NoError(t, err) - fmt.Println("Funded all") - fmt.Println("aliceFunder: ", aliceFunder) - fmt.Println("aliceFunder: ", wBob, accBob, wAlice) hzAliceGetCh := aliceClient.GetHorizonAcc() @@ -82,15 +63,11 @@ func TestCloseChannel(t *testing.T) { require.NoError(t, err) retVal := txMetaGetChAfterFunding.V3.SorobanMeta.ReturnValue - fmt.Println("retVal txMetaGetChAfterFunding: ", retVal) var getChanAfterFunding wire.Channel err = getChanAfterFunding.FromScVal(retVal) require.NoError(t, err) - fmt.Println("getChanAfterFunding: ", getChanAfterFunding) - - fmt.Println("getChan.Control.FundedA, getChan.Control.FundedA: ", getChanAfterFunding.Control.FundedA, getChanAfterFunding.Control.FundedA) // close the channel @@ -101,7 +78,6 @@ func TestCloseChannel(t *testing.T) { currPerunState, err := wire.ToState(currStellarState) require.NoError(t, err) - fmt.Println("currStellarState: ", currStellarState) sigA, err := channel.Backend.Sign(accAlice, &currPerunState) require.NoError(t, err) @@ -111,9 +87,6 @@ func TestCloseChannel(t *testing.T) { sigs := []pwallet.Sig{sigA, sigB} - // addrAlice := kpAlice.FromAddress() - // channel.Backend.Sign(addrAlice, currStellarState) - closeArgs, err := channel.BuildCloseTxArgs(currPerunState, sigs) require.NoError(t, err) hzAliceGClose := aliceClient.GetHorizonAcc() @@ -122,8 +95,6 @@ func TestCloseChannel(t *testing.T) { preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&hzAliceGClose, *invokeHostFunctionOpClose) - fmt.Println("minFeeClose: ", minFeeClose) - txClose, err := itest.SubmitOperationsWithFee(&hzAliceGClose, kpAlice, minFeeClose, &preFlightOpClose) require.NoError(t, err) @@ -132,12 +103,9 @@ func TestCloseChannel(t *testing.T) { require.NoError(t, err) - perunEventsClose, err := channel.DecodeEvents(txMetaClose) + _, err = channel.DecodeEvents(txMetaClose) require.NoError(t, err) - fmt.Println("perunEventsClose: ", perunEventsClose) - - // now both users can withdraw their funds } @@ -152,11 +120,9 @@ func TestWithdrawChannel(t *testing.T) { reqDeployer := itest.AccountDetails(kpDeployer) - //fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) itest.SetContractIDAddress(contractAddr) - //perunFirstParams, perunFirstState := chtest.NewParamsState(t) _, accAlice, _ := util.MakeRandPerunWallet() _, accBob, _ := util.MakeRandPerunWallet() @@ -181,7 +147,6 @@ func TestWithdrawChannel(t *testing.T) { // Calling the function err := chtest.FundAll(context.TODO(), funders, freqs) require.NoError(t, err) - fmt.Println("Channel fully funded") hzAliceGetCh := aliceClient.GetHorizonAcc() @@ -260,9 +225,7 @@ func TestWithdrawChannel(t *testing.T) { txMetaWB, err := env.DecodeTxMeta(txWithdrawBob) require.NoError(t, err) - perunEventsWB, err := channel.DecodeEvents(txMetaWB) + _, err = channel.DecodeEvents(txMetaWB) require.NoError(t, err) - fmt.Println("perunEventsWB: ", perunEventsWB) - } diff --git a/channel/env/client.go b/channel/env/client.go index 785a5d5..c6bd402 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -1,13 +1,11 @@ package env import ( - //"context" "errors" "fmt" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/xdr" - //pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/wire" ) @@ -48,18 +46,14 @@ func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, // Build contract call operation fnameXdr := xdr.ScSymbol(fname) - fmt.Println("contractAddr in InvokeAndProcessHostFunction: ", contractAddr) invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr) // Preflight host functions - fmt.Println("horizonAcc in InvokeAndProcessHostFunction: ", horizonAcc) preFlightOp, minFee := s.stellarEnv.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) - fmt.Println("minfee for", fname, ": ", minFee) // Submit operations with fee tx, err := s.stellarEnv.SubmitOperationsWithFee(&horizonAcc, kp, minFee, &preFlightOp) if err != nil { panic(err) - //return xdr.TransactionMeta{}, errors.New("error while submitting operations with fee") } fmt.Println("tx from ", fname, ": ", tx) @@ -70,19 +64,10 @@ func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") } - fmt.Println("txMeta from ", fname, ": ", txMeta) - - // // Decode events - // _, err = DecodeSorEvents(txMeta) - // if err != nil { - // return errors.New("error while decoding events") - // } - return txMeta, nil } func (s *StellarClient) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) { - fmt.Println("in GetChannelState") contractAddress := s.stellarEnv.GetContractIDAddress() kp := s.kp hz := s.GetHorizonAcc() diff --git a/channel/env/integration.go b/channel/env/integration.go index 3e014e4..b693104 100644 --- a/channel/env/integration.go +++ b/channel/env/integration.go @@ -22,8 +22,6 @@ type IntegrationTestEnv struct { } func NewBackendEnv() *IntegrationTestEnv { - // Create a dummy testing.T object. - // This might not be appropriate depending on your use case. t := &testing.T{} cfg := testenv.Config{ diff --git a/channel/env/transaction.go b/channel/env/transaction.go index d416bd1..847146e 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -1,7 +1,6 @@ package env import ( - "fmt" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" @@ -80,8 +79,6 @@ func BuildGetChannelTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { panic(err) } - fmt.Println("channelID in Wire version", channelID.Bytes) - getChannelArgs := xdr.ScVec{ channelID, } diff --git a/channel/funder.go b/channel/funder.go index 69b14e3..0a5cf67 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -15,10 +15,9 @@ import ( ) const MaxIterationsUntilAbort = 10 -const DefaultPollingInterval = time.Duration(5) * time.Second +const DefaultPollingInterval = time.Duration(7) * time.Second type Funder struct { - //integrEnv env.IntegrationTestEnv stellarClient *env.StellarClient acc *wallet.Account kpFull *keypair.Full @@ -143,20 +142,6 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state return errors.New("error while invoking and processing host function: open") } - // invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openTxArgs, contractAddress) - - // preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) - - // tx, err := f.integrEnv.SubmitOperationsWithFee(&reqAlice, kp, minFee, &preFlightOp) - // if err != nil { - // return errors.New("error while submitting operations with fee") - // } - - // read out decoded Events and interpret them - // txMeta, err := env.DecodeTxMeta(tx) - // if err != nil { - // return errors.New("error while decoding tx meta") - // } _, err = DecodeEvents(txMeta) if err != nil { return errors.New("error while decoding events") @@ -167,8 +152,6 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { - //env := f.integrEnv - contractAddress := f.stellarClient.GetContractIDAddress() kp := f.kpFull hzAcc := f.stellarClient.GetHorizonAcc() @@ -186,8 +169,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state return errors.New("error while invoking and processing host function: fund") } - fmt.Println("txMeta in fund: ", txMeta) - _, err = DecodeEvents(txMeta) if err != nil { return errors.New("error while decoding events") @@ -196,40 +177,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state return nil } -// func (f Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { - -// //env := f.integrEnv -// contractAddress := f.integrEnv.GetContractIDAddress() -// kp := f.kpFull -// acc := f.integrEnv.AccountDetails(kp) -// chanID := state.ID -// // generate tx to open the channel -// fundTxArgs, err := env.BuildFundTxArgs(chanID, funderIdx) -// if err != nil { -// return errors.New("error while building fund tx") -// } -// invokeHostFunctionOp := env.BuildContractCallOp(acc, "fund", fundTxArgs, contractAddress) - -// preFlightOp, minFee := f.integrEnv.PreflightHostFunctions(&acc, *invokeHostFunctionOp) - -// tx, err := f.integrEnv.SubmitOperationsWithFee(&acc, kp, minFee, &preFlightOp) -// if err != nil { -// return errors.New("error while submitting operations with fee") -// } - -// // read out decoded Events and interpret them -// txMeta, err := env.DecodeTxMeta(tx) -// if err != nil { -// return errors.New("error while decoding tx meta") -// } -// _, err = DecodeEvents(txMeta) -// if err != nil { -// return errors.New("error while decoding events") -// } - -// return nil -// } - func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { contractAddress := f.stellarClient.GetContractIDAddress() @@ -239,7 +186,13 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat // generate tx to open the channel openTxArgs, err := env.BuildGetChannelTxArgs(chanId) + if err != nil { + return errors.New("error while building get_channel tx") + } txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp) + if err != nil { + return errors.New("error while invoking and processing host function: abort_funding") + } _, err = DecodeEvents(txMeta) if err != nil { diff --git a/channel/funder_test.go b/channel/funder_test.go index 05d0ae8..3ebe511 100644 --- a/channel/funder_test.go +++ b/channel/funder_test.go @@ -2,21 +2,14 @@ package channel_test import ( "context" - "fmt" - "perun.network/perun-stellar-backend/util" - "github.com/stellar/go/xdr" - //"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - chtest "perun.network/perun-stellar-backend/channel/test" - - _ "perun.network/perun-stellar-backend/wallet/test" - //"perun.network/perun-stellar-backend/wire" - pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel" "perun.network/perun-stellar-backend/channel/env" - + chtest "perun.network/perun-stellar-backend/channel/test" + "perun.network/perun-stellar-backend/util" + _ "perun.network/perun-stellar-backend/wallet/test" "testing" ) @@ -67,11 +60,9 @@ func TestOpenChannel(t *testing.T) { txMeta, err := env.DecodeTxMeta(tx) require.NoError(t, err) - perunEvents, err := channel.DecodeEvents(txMeta) + _, err = channel.DecodeEvents(txMeta) require.NoError(t, err) - fmt.Println("perunEvents: ", perunEvents) - } func TestFundChannel(t *testing.T) { @@ -83,28 +74,21 @@ func TestFundChannel(t *testing.T) { kpBob := kps[1] kpDeployer := kps[2] - reqAlice := itest.AccountDetails(kpAlice) - reqBob := itest.AccountDetails(kpBob) + _ = itest.AccountDetails(kpAlice) + _ = itest.AccountDetails(kpBob) reqDeployer := itest.AccountDetails(kpDeployer) - fmt.Println("reqAlice, reqBob: ", reqAlice.Balances, reqBob.Balances) contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) itest.SetContractIDAddress(contractAddr) - fmt.Println("contractID: ", contractAddr) perunFirstParams, perunFirstState := chtest.NewParamsState(t) - fmt.Println("perunFirstParams, perunFirstState: ", perunFirstParams, perunFirstState) - - wAlice, accAlice, _ := util.MakeRandPerunWallet() - wBob, accBob, _ := util.MakeRandPerunWallet() + _, accAlice, _ := util.MakeRandPerunWallet() + _, accBob, _ := util.MakeRandPerunWallet() freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) - fmt.Println("freqAlice: ", freqAlice) freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) - fmt.Println("freqBob: ", freqBob) freqs := []*pchannel.FundingReq{freqAlice, freqBob} - fmt.Println("freqs: ", freqs) // Creating the client and funders as pointers aliceClient := env.NewStellarClient(itest, kpAlice) @@ -113,118 +97,13 @@ func TestFundChannel(t *testing.T) { aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) bobFunder := channel.NewFunder(accBob, kpBob, bobClient) funders := []*channel.Funder{aliceFunder, bobFunder} - fmt.Println("funders: ", funders) chanID := perunFirstParams.ID() aliceIdx := false - fundArgsAlice, err := env.BuildFundTxArgs(chanID, aliceIdx) + _, err := env.BuildFundTxArgs(chanID, aliceIdx) require.NoError(t, err) - fmt.Println("chanID: ", chanID) - - fmt.Println("fundArgs outside: ", contractAddr, kpAlice, reqAlice, chanID, fundArgsAlice, aliceIdx) - //fmt.Println("funderchan args: ", contractAddress, kp, hzAcc, chanId, fundTxArgs, funderIdx) - - //fmt.Println("funderchan args: ", contractAddress, kp, hzAcc, chanId, funderIdx) // Calling the function err = chtest.FundAll(context.TODO(), funders, freqs) require.NoError(t, err) - fmt.Println("Funded all") - fmt.Println("aliceFunder: ", aliceFunder) - fmt.Println("aliceFunder: ", wBob, accBob, wAlice) - - // err = aliceFunder.OpenChannel(context.TODO(), perunFirstParams, perunFirstState) - // require.NoError(t, err) - //chanOpened, err := aliceFunder.GetChannelState(context.TODO(), perunFirstParams, perunFirstState) - //require.NoError(t, err) - - //fmt.Println("chanOpened state: ", chanOpened) - - // openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) - // fnameXdr := xdr.ScSymbol("open") - // hzAlice := aliceClient.GetHorizonAcc() - // hzBob := bobClient.GetHorizonAcc() - - // //invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, fnameXdr, openArgs, contractAddr) - // invokeHostFunctionOp := env.BuildContractCallOp(hzAlice, fnameXdr, openArgs, contractAddr) - // preFlightOp, minFeeOpen := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOp) - - // _, err = itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeOpen, &preFlightOp) - // require.NoError(t, err) - - // // check for Channel State - - // getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) - // require.NoError(t, err) - // invokeHostFunctionOpGetChOpen := env.BuildContractCallOp(hzAlice, "get_channel", getChannelArgs, contractAddr) - // preFlightOpGetChOpen, minFeeGetChOpen := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOpGetChOpen) - // txGetChOpen, err := itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeGetChOpen, &preFlightOpGetChOpen) - // require.NoError(t, err) - - // txMetaGetChOpen, err := env.DecodeTxMeta(txGetChOpen) - // require.NoError(t, err) - // fmt.Println("txMetaGetChOpen: ", txMetaGetChOpen) - // retValOpen := txMetaGetChOpen.V3.SorobanMeta.ReturnValue - // fmt.Println("retVal: ", retValOpen) - - // var getChanOpen wire.Channel - - // err = getChanOpen.FromScVal(retValOpen) - // require.NoError(t, err) - // fmt.Println("getChanOpen: ", getChanOpen) - // // check get state - - // invokeHostFunctionOpFund := env.BuildContractCallOp(hzAlice, "fund", fundArgsAlice, contractAddr) - - // preFlightOpFund, minFeeFund := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOpFund) - - // txFund, err := itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeFund, &preFlightOpFund) - // fmt.Println("minFeeFund: ", minFeeFund) - // require.NoError(t, err) - - // txMetaFund, err := env.DecodeTxMeta(txFund) - // require.NoError(t, err) - - // perunEventsFund, err := channel.DecodeEvents(txMetaFund) - // require.NoError(t, err) - // fmt.Println("perunEventsFund: ", perunEventsFund) - - // bobIdx := true - // fundArgsBob, err := env.BuildFundTxArgs(chanID, bobIdx) - // require.NoError(t, err) - // invokeHostFunctionOpFund2 := env.BuildContractCallOp(hzBob, "fund", fundArgsBob, contractAddr) - - // preFlightOpFund2, minFeeFund2 := itest.PreflightHostFunctions(&hzBob, *invokeHostFunctionOpFund2) - // fmt.Println("minFeeFund2: ", minFeeFund2) - - // txFund2, err := itest.SubmitOperationsWithFee(&reqBob, kpBob, minFeeFund2, &preFlightOpFund2) - - // require.NoError(t, err) - // txMetaFund2, err := env.DecodeTxMeta(txFund2) - // require.NoError(t, err) - // fmt.Println("txMetaFund2: ", txMetaFund2) - // perunEventsFund2, err := channel.DecodeEvents(txMetaFund2) - // require.NoError(t, err) - // fmt.Println("perunEventsFund2: ", perunEventsFund2) - - // // qurey channel state - - // // getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) - // // require.NoError(t, err) - - // invokeHostFunctionOpGetCh := env.BuildContractCallOp(hzAlice, "get_channel", getChannelArgs, contractAddr) - // preFlightOpGetCh, minFeeGetCh := itest.PreflightHostFunctions(&hzAlice, *invokeHostFunctionOpGetCh) - // txGetCh, err := itest.SubmitOperationsWithFee(&hzAlice, kpAlice, minFeeGetCh, &preFlightOpGetCh) - // require.NoError(t, err) - - // txMetaGetCh, err := env.DecodeTxMeta(txGetCh) - // require.NoError(t, err) - // fmt.Println("txMetaGetCh: ", txMetaGetCh) - // retVal := txMetaGetCh.V3.SorobanMeta.ReturnValue - // fmt.Println("retVal: ", retVal) - - // var getChan wire.Channel - // err = getChan.FromScVal(retVal) - // require.NoError(t, err) - // fmt.Println("getChan: ", getChan) } diff --git a/channel/types/asset_test.go b/channel/types/asset_test.go index 0285035..97c552f 100644 --- a/channel/types/asset_test.go +++ b/channel/types/asset_test.go @@ -5,7 +5,6 @@ import ( "github.com/stellar/go/xdr" "github.com/stretchr/testify/require" "perun.network/perun-stellar-backend/channel/types" - "testing" ) diff --git a/main.go b/main.go index 447c8a6..ac35e25 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "perun.network/go-perun/wire" "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/client" @@ -8,6 +9,7 @@ import ( ) const PerunContractPath = "./testdata/perun_soroban_contract.wasm" +const StellarAssetContractPath = "./testdata/perun_soroban_token.wasm" func main() { @@ -15,21 +17,19 @@ func main() { stellarEnv := env.NewBackendEnv() // Create two Stellar L1 accounts - kps, _ := stellarEnv.CreateAccounts(3, "10000000") + kps, _ := stellarEnv.CreateAccounts(4, "100000000") kpAlice := kps[0] kpBob := kps[1] - kpDeployer := kps[2] + kpDeployerPerun := kps[2] + kpDeployerToken := kps[3] - _ = stellarEnv.AccountDetails(kpAlice) - _ = stellarEnv.AccountDetails(kpBob) - hzDeployer := stellarEnv.AccountDetails(kpDeployer) + realAssetContractID := util.Deploy(stellarEnv, kpDeployerToken, stellarEnv.AccountDetails(kpDeployerToken), StellarAssetContractPath) - // Deploy the contract - - contractIDAddress := util.Deploy(stellarEnv, kpDeployer, hzDeployer, PerunContractPath) + fmt.Println("Deployed Real Asset Contract ID: ", realAssetContractID) + // Deploy the Perun contract + contractIDAddress := util.Deploy(stellarEnv, kpDeployerPerun, stellarEnv.AccountDetails(kpDeployerPerun), PerunContractPath) stellarEnv.SetContractIDAddress(contractIDAddress) - //fmt.Println("Deployed contractIDAddress: ", contractIDAddress) // Generate L2 accounts for the payment channel wAlice, accAlice, _ := util.MakeRandPerunWallet() diff --git a/testdata/perun_soroban_contract.wasm b/testdata/perun_soroban_contract.wasm index 3696a62ec79671197dad033b57e6756e3bebdd1d..cbc3d1c0eb2161dfffe6cbd2e1faa1e46574dde8 100755 GIT binary patch delta 1556 zcmZ8gU1%It6u#%)*`Ms}Zf<@yS+^m*GY#fnQfWFirs>W?(yG*-zDp8nP^Ptriuhu7 ziyC8V<8E#uK|u>56|@?hmlBmOR6z`A=!;Pwng@|stVACqNTHrHvy0%v+Lyqd}(3Kc>ADB2+NX)%7{27lJ9gC$> ziJ3X3*yVznD;TdRs0ngGThX(i{r96U04({r_&A4kJ(W0s=W=4Gd7VOYiYe~)c;4ak@rd~*;g(P!%eq_6K*hfxuL}xoJps$wZ|#D&^k(b5u%^w-Ubw0IGaAE| zdEWo6Jpo*v^KWIdfI-pR>Rso(E~$@gFi%A8%_f#vDQ9tbpfMnTJv~5UBrnU{aA0 zw#*e*QA~n2C0it&_lkM28yqoT(~j8*M|^{I$dtEDM^tOdeRk3jS87U+opQv7HKnNR zv?DIow5;9eh?PKbw^%@;bi`H&Y=l6QBQ`>y5=>(^kFZM6fm;rNmJwDCfl>&xj<8Y) z?1niqj@S)z?AZyCHb?A)$WTzW-4Uz$O7A`R69aPON5O;1;&8@gY@gC)v2m_I$NTm{ zR1frZ*flb@z*E>a@w$m($fLV~Vw3jz28!4**~;8g*ei)86tPPZchL>|BXI{sYz#$J zr@78>o!mOF)Gzg4r?yUNz-M9M*MENgDOyaM%v` zrA>1;L(!m!DJw$Pqxd{w{~%igD@KtOu;PS2V>Qs#V9!;@2KETVlCytdlUVP*r9UxwbN0Q6%6 zd;L!botVFKpeY8|G#}j$U+L1=-8wnetWS?V0#*IRXc^DrhhJ~41}P!QO?+=mUL#dM he%MCm>6W??$cVE?Bqe$cs($>)-vHnGck;J7{slJ}R6GCx delta 1678 zcmZ8hU1%It6u#%)nVs30oy|?MX||gh?o5Ntrj4v6W^_}-PNYp0M96~*f+4oSu&sSi zQSl+W{?sP1)!p7yN(Jp(i`G&Y!9RoqT2Txde2{=3L41h#LlDt`h@Lw$8_|Wm=jWbt z&pF?{-+cce{PYpnWa^2>fDi(YcQ)C|&6#`Kk3flNFoi3HXW$N^H&=$!a1$Fu$#hkajr?_0F>W==775M`or@oR~gNMlk-c{0#c6Lu2=h-+h;L za&|cU19T0V@-MvrXJkJ09Gnd=q@DthVY_*d!is#r8p8FoRq9x&*XyJph@e4#HP|40 zCH%rVsN1h7zsm&hS7k4shDP`z|B9*9+ypGjw{m@OPJWr&?=Go8Vq@U*Q3}|C1iadZ zMx6+-*QM7bG4J9Y5q{le(eS%&3&P*>9e_@waBb&;3M>c~h-#3p78&tCWCi65;c8(P zD~9*=zOJG8(fbAd4KB5Uc@q3vz-WS&cS5%eB#1i;axysDQke zbs@v-z(mkmxeg0JAgKaxGMAGOmP)c#w<3CvA8}R4H!HOU0G?y);&$ zcA&cqMU4H+F^DwSfT~FOOx@=is+mX?hR@cqzOa0@s-#{f=O{`O`=X*2HF#DzXwZy$ z@!HCy89N%WgO^DoaunGdc z_Gw~NywM##TS~0ky>6eqA19Wg*O2$wHD#xLb~yW+W_u2Kbzmqba{RRFUx2WM!#{}L><w*c5UsaaRIGWUFCJMj?1L7LA!YZ^P zj37VmY&(>B$N`}v1BFq=a6@6QW-QzHn9Z^k~84ZfT}5=#bW$?wRonf_y=9Y0iPejgb54a8cNP9j-q=8IgUag zC*d}lL+YX6ZblUzBrqr6+dDpy5RLFFQETbs!YqZ1aC(G>@H;*6)gj#3n4!qktSz$q zf=Y7L0LZFjy3iSX$k5V+e;htC)NR0Z`RvH8a6?`hIR(pd=s*M48wck3a+Ir>k57*9 n>+<0{Pj$AO_|>KA?sTpaZ%`e3i^>vsLK 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 Date: Fri, 8 Dec 2023 09:22:49 +0100 Subject: [PATCH 08/51] main: Include Token Asset contract channel: Adapt channel to Token Asset contract. Include transactions that realize the asset token contract functions. wire: Include string encoding for ScVal --- channel/adjudicator.go | 21 +++-- channel/adjudicator_test.go | 31 ++++--- channel/env/client.go | 40 +++++---- channel/env/integration.go | 24 +++-- channel/env/transaction.go | 4 +- channel/event.go | 2 +- channel/funder.go | 170 ++++++++++++++++++++++++++++++++---- channel/funder_test.go | 11 ++- channel/token.go | 157 +++++++++++++++++++++++++++++++++ main.go | 77 +++++++++++++--- wire/scval/scval.go | 12 +++ 11 files changed, 471 insertions(+), 78 deletions(-) create mode 100644 channel/token.go diff --git a/channel/adjudicator.go b/channel/adjudicator.go index cfe673b..1b34a68 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -151,7 +151,7 @@ func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVe func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) error { - contractAddress := a.stellarClient.GetContractIDAddress() + contractAddress := a.stellarClient.GetPerunAddress() kp := a.kpFull hzAcc := a.stellarClient.GetHorizonAcc() @@ -160,12 +160,13 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) if err != nil { return errors.New("error while building fund tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "withdraw", withdrawTxArgs, contractAddress, kp) + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "withdraw", withdrawTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: withdraw") } - _, err = DecodeEvents(txMeta) + _, err = DecodeEventsPerun(txMeta) if err != nil { return errors.New("error while decoding events") } @@ -175,18 +176,19 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { fmt.Println("Close called") - contractAddress := a.stellarClient.GetContractIDAddress() + contractAddress := a.stellarClient.GetPerunAddress() kp := a.kpFull hzAcc := a.stellarClient.GetHorizonAcc() closeTxArgs, err := BuildCloseTxArgs(*state, sigs) if err != nil { return errors.New("error while building fund tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "close", closeTxArgs, contractAddress, kp) + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "close", closeTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: close") } - _, err = DecodeEvents(txMeta) + _, err = DecodeEventsPerun(txMeta) if err != nil { return errors.New("error while decoding events") } @@ -200,18 +202,19 @@ func (a *Adjudicator) Register(ctx context.Context, req pchannel.AdjudicatorReq, func (a *Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { fmt.Println("ForceClose called") - contractAddress := a.stellarClient.GetContractIDAddress() + contractAddress := a.stellarClient.GetPerunAddress() kp := a.kpFull hzAcc := a.stellarClient.GetHorizonAcc() forceCloseTxArgs, err := env.BuildForceCloseTxArgs(id) if err != nil { return errors.New("error while building fund tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "force_close", forceCloseTxArgs, contractAddress, kp) + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "force_close", forceCloseTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function") } - _, err = DecodeEvents(txMeta) + _, err = DecodeEventsPerun(txMeta) if err != nil { return errors.New("error while decoding events") } diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go index 2be928a..1765c24 100644 --- a/channel/adjudicator_test.go +++ b/channel/adjudicator_test.go @@ -2,6 +2,7 @@ package channel_test import ( "context" + "github.com/stellar/go/xdr" "github.com/stretchr/testify/require" pchannel "perun.network/go-perun/channel" pwallet "perun.network/go-perun/wallet" @@ -29,7 +30,7 @@ func TestCloseChannel(t *testing.T) { reqDeployer := itest.AccountDetails(kpDeployer) contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) - itest.SetContractIDAddress(contractAddr) + itest.SetPerunAddress(contractAddr) _, accAlice, _ := util.MakeRandPerunWallet() _, accBob, _ := util.MakeRandPerunWallet() @@ -59,7 +60,8 @@ func TestCloseChannel(t *testing.T) { getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) require.NoError(t, err) - txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice) + auth := []xdr.SorobanAuthorizationEntry{} + txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice, auth) require.NoError(t, err) retVal := txMetaGetChAfterFunding.V3.SorobanMeta.ReturnValue @@ -91,7 +93,8 @@ func TestCloseChannel(t *testing.T) { require.NoError(t, err) hzAliceGClose := aliceClient.GetHorizonAcc() - invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr) + auth = []xdr.SorobanAuthorizationEntry{} + invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr, auth) preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&hzAliceGClose, *invokeHostFunctionOpClose) @@ -103,7 +106,7 @@ func TestCloseChannel(t *testing.T) { require.NoError(t, err) - _, err = channel.DecodeEvents(txMetaClose) + _, err = channel.DecodeEventsPerun(txMetaClose) require.NoError(t, err) @@ -121,7 +124,7 @@ func TestWithdrawChannel(t *testing.T) { reqDeployer := itest.AccountDetails(kpDeployer) contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) - itest.SetContractIDAddress(contractAddr) + itest.SetPerunAddress(contractAddr) _, accAlice, _ := util.MakeRandPerunWallet() _, accBob, _ := util.MakeRandPerunWallet() @@ -153,7 +156,8 @@ func TestWithdrawChannel(t *testing.T) { getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) require.NoError(t, err) - txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice) + auth := []xdr.SorobanAuthorizationEntry{} + txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice, auth) require.NoError(t, err) retVal := txMetaGetChAfterFunding.V3.SorobanMeta.ReturnValue @@ -185,7 +189,8 @@ func TestWithdrawChannel(t *testing.T) { require.NoError(t, err) hzAliceGClose := aliceClient.GetHorizonAcc() - invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr) + auth = []xdr.SorobanAuthorizationEntry{} + invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr, auth) preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&hzAliceGClose, *invokeHostFunctionOpClose) @@ -197,13 +202,14 @@ func TestWithdrawChannel(t *testing.T) { require.NoError(t, err) - _, err = channel.DecodeEvents(txMetaClose) + _, err = channel.DecodeEventsPerun(txMetaClose) require.NoError(t, err) hzAliceWithdraw := aliceClient.GetHorizonAcc() waArgs, err := env.BuildFundTxArgs(chanID, false) require.NoError(t, err) - invokeHostFunctionOpWA := env.BuildContractCallOp(hzAliceWithdraw, "withdraw", waArgs, contractAddr) + auth = []xdr.SorobanAuthorizationEntry{} + invokeHostFunctionOpWA := env.BuildContractCallOp(hzAliceWithdraw, "withdraw", waArgs, contractAddr, auth) preFlightOpWA, minFeeWA := itest.PreflightHostFunctions(&hzAliceWithdraw, *invokeHostFunctionOpWA) txWithdrawAlice, err := itest.SubmitOperationsWithFee(&hzAliceWithdraw, kpAlice, minFeeWA, &preFlightOpWA) require.NoError(t, err) @@ -211,13 +217,14 @@ func TestWithdrawChannel(t *testing.T) { txMetaWA, err := env.DecodeTxMeta(txWithdrawAlice) require.NoError(t, err) - _, err = channel.DecodeEvents(txMetaWA) + _, err = channel.DecodeEventsPerun(txMetaWA) require.NoError(t, err) hzBobWithdraw := bobClient.GetHorizonAcc() wbArgs, err := env.BuildFundTxArgs(chanID, true) require.NoError(t, err) - invokeHostFunctionOpWB := env.BuildContractCallOp(hzBobWithdraw, "withdraw", wbArgs, contractAddr) + auth = []xdr.SorobanAuthorizationEntry{} + invokeHostFunctionOpWB := env.BuildContractCallOp(hzBobWithdraw, "withdraw", wbArgs, contractAddr, auth) preFlightOpWB, minFeeWB := itest.PreflightHostFunctions(&hzBobWithdraw, *invokeHostFunctionOpWB) txWithdrawBob, err := itest.SubmitOperationsWithFee(&hzBobWithdraw, kpBob, minFeeWB, &preFlightOpWB) require.NoError(t, err) @@ -225,7 +232,7 @@ func TestWithdrawChannel(t *testing.T) { txMetaWB, err := env.DecodeTxMeta(txWithdrawBob) require.NoError(t, err) - _, err = channel.DecodeEvents(txMetaWB) + _, err = channel.DecodeEventsPerun(txMetaWB) require.NoError(t, err) } diff --git a/channel/env/client.go b/channel/env/client.go index c6bd402..547359b 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -10,19 +10,21 @@ import ( ) type StellarClient struct { - stellarEnv *IntegrationTestEnv - kp *keypair.Full - passphrase string - contractIDAddress xdr.ScAddress + stellarEnv *IntegrationTestEnv + kp *keypair.Full + passphrase string + perunAddress xdr.ScAddress + tokenAddress xdr.ScAddress } func NewStellarClient(stellarEnv *IntegrationTestEnv, kp *keypair.Full) *StellarClient { passphrase := stellarEnv.GetPassPhrase() return &StellarClient{ - stellarEnv: stellarEnv, - kp: kp, - passphrase: passphrase, - contractIDAddress: stellarEnv.contractIDAddress, + stellarEnv: stellarEnv, + kp: kp, + passphrase: passphrase, + perunAddress: stellarEnv.perunAddress, + tokenAddress: stellarEnv.tokenAddress, } } @@ -38,19 +40,23 @@ func (s *StellarClient) GetPassPhrase() string { return s.passphrase } -func (s *StellarClient) GetContractIDAddress() xdr.ScAddress { - return s.contractIDAddress +func (s *StellarClient) GetPerunAddress() xdr.ScAddress { + return s.perunAddress } -func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { +func (s *StellarClient) GetTokenAddress() xdr.ScAddress { + return s.tokenAddress +} + +func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full, auth []xdr.SorobanAuthorizationEntry) (xdr.TransactionMeta, error) { // Build contract call operation fnameXdr := xdr.ScSymbol(fname) - invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr) - // Preflight host functions + invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr, auth) + preFlightOp, minFee := s.stellarEnv.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) - // Submit operations with fee + tx, err := s.stellarEnv.SubmitOperationsWithFee(&horizonAcc, kp, minFee, &preFlightOp) if err != nil { panic(err) @@ -68,11 +74,11 @@ func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, } func (s *StellarClient) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) { - contractAddress := s.stellarEnv.GetContractIDAddress() + contractAddress := s.stellarEnv.GetPerunAddress() kp := s.kp hz := s.GetHorizonAcc() - - txMeta, err := s.InvokeAndProcessHostFunction(hz, "get_channel", chanArgs, contractAddress, kp) + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := s.InvokeAndProcessHostFunction(hz, "get_channel", chanArgs, contractAddress, kp, auth) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } diff --git a/channel/env/integration.go b/channel/env/integration.go index b693104..9559533 100644 --- a/channel/env/integration.go +++ b/channel/env/integration.go @@ -17,8 +17,9 @@ const StandaloneAuth = "Standalone Network ; February 2017" const SorobanRPCPort = 8080 type IntegrationTestEnv struct { - testEnv *testenv.Test - contractIDAddress xdr.ScAddress + testEnv *testenv.Test + perunAddress xdr.ScAddress + tokenAddress xdr.ScAddress } func NewBackendEnv() *IntegrationTestEnv { @@ -40,8 +41,12 @@ func NewIntegrationEnv(t *testing.T) *IntegrationTestEnv { return &itest } -func (it *IntegrationTestEnv) SetContractIDAddress(contractIDAddress xdr.ScAddress) { - it.contractIDAddress = contractIDAddress +func (it *IntegrationTestEnv) SetPerunAddress(perunAddress xdr.ScAddress) { + it.perunAddress = perunAddress +} + +func (it *IntegrationTestEnv) SetTokenAddress(tokenAddress xdr.ScAddress) { + it.tokenAddress = tokenAddress } func (it *IntegrationTestEnv) CreateAccounts(numAccounts int, initBalance string) ([]*keypair.Full, []txnbuild.Account) { @@ -89,14 +94,15 @@ func (it *IntegrationTestEnv) GetPassPhrase() string { return it.testEnv.GetPassPhrase() } -func (it *IntegrationTestEnv) GetContractIDAddress() xdr.ScAddress { - return it.contractIDAddress +func (it *IntegrationTestEnv) GetPerunAddress() xdr.ScAddress { + return it.perunAddress } func (it *IntegrationTestEnv) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { // Build contract call operation fnameXdr := xdr.ScSymbol(fname) - invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr) + auth := []xdr.SorobanAuthorizationEntry{} + invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr, auth) // Preflight host functions preFlightOp, minFee := it.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) @@ -115,3 +121,7 @@ func (it *IntegrationTestEnv) InvokeAndProcessHostFunction(horizonAcc horizon.Ac return txMeta, nil } + +func (it *IntegrationTestEnv) SubmitOperations(source txnbuild.Account, signer *keypair.Full, ops ...txnbuild.Operation) (horizon.Transaction, error) { + return it.testEnv.SubmitOperations(source, signer, ops...) +} diff --git a/channel/env/transaction.go b/channel/env/transaction.go index 847146e..6a1a398 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -9,7 +9,8 @@ import ( "perun.network/perun-stellar-backend/wire/scval" ) -func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xdr.ScVec, contractIdAddress xdr.ScAddress) *txnbuild.InvokeHostFunction { +func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xdr.ScVec, contractIdAddress xdr.ScAddress, auth []xdr.SorobanAuthorizationEntry) *txnbuild.InvokeHostFunction { + return &txnbuild.InvokeHostFunction{ HostFunction: xdr.HostFunction{ Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, @@ -19,6 +20,7 @@ func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xd Args: callArgs, }, }, + Auth: auth, SourceAccount: caller.AccountID, } } diff --git a/channel/event.go b/channel/event.go index 29a3734..474feb1 100644 --- a/channel/event.go +++ b/channel/event.go @@ -201,7 +201,7 @@ func (e *DisputedEvent) Tstamp() uint64 { return e.Timestamp } -func DecodeEvents(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { +func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { evs := make([]PerunEvent, 0) txEvents := txMeta.V3.SorobanMeta.Events diff --git a/channel/funder.go b/channel/funder.go index 0a5cf67..a4246a8 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -2,20 +2,28 @@ package channel import ( "context" + "crypto/rand" + "crypto/sha256" "errors" "fmt" + // "github.com/stellar/go/gxdr" "github.com/stellar/go/keypair" + "github.com/stellar/go/xdr" "log" + "math/big" + pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/channel/types" "perun.network/perun-stellar-backend/wallet" "perun.network/perun-stellar-backend/wire" + "perun.network/perun-stellar-backend/wire/scval" "time" ) -const MaxIterationsUntilAbort = 10 -const DefaultPollingInterval = time.Duration(7) * time.Second +const MaxIterationsUntilAbort = 20 +const DefaultPollingInterval = time.Duration(6) * time.Second type Funder struct { stellarClient *env.StellarClient @@ -129,20 +137,20 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state //env := f.integrEnv - contractAddress := f.stellarClient.GetContractIDAddress() + contractAddress := f.stellarClient.GetPerunAddress() kp := f.kpFull hz := f.stellarClient.GetHorizonAcc() // generate tx to open the channel openTxArgs := env.BuildOpenTxArgs(params, state) fmt.Println("openTxArgs: ", openTxArgs) - - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "open", openTxArgs, contractAddress, kp) + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "open", openTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: open") } - _, err = DecodeEvents(txMeta) + _, err = DecodeEventsPerun(txMeta) if err != nil { return errors.New("error while decoding events") } @@ -152,7 +160,10 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { - contractAddress := f.stellarClient.GetContractIDAddress() + perunContractAddress := f.stellarClient.GetPerunAddress() + tokenContractAddress := f.stellarClient.GetTokenAddress() + fmt.Println("perunContractAddress: ", perunContractAddress, "tokenContractAddress: ", tokenContractAddress) + kp := f.kpFull hzAcc := f.stellarClient.GetHorizonAcc() chanId := state.ID @@ -162,14 +173,114 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state if err != nil { return errors.New("error while building fund tx") } - fmt.Println("funderchan args: ", contractAddress, kp, hzAcc, chanId, fundTxArgs, funderIdx) + // fmt.Println("funderchan args: ", perunContractAddress, kp, hzAcc, chanId, fundTxArgs, funderIdx) + + balsStellar, err := wire.MakeBalances(state.Allocation) + if err != nil { + return errors.New("error while making balances") + } + // tokenIDAsset := balsStellar.Token + + // fmt.Println("tokenIDAsset, perunID: ", tokenIDAsset.ContractId, perunContractAddress.ContractId) + + var amountInt128 xdr.Int128Parts + + if funderIdx { + + amountInt128 = balsStellar.BalB + if err != nil { + return errors.New("error while making int128 parts on index 1") + } + + } else { + amountInt128 = balsStellar.BalA + if err != nil { + return errors.New("error while making int128 parts on index 0") + } + } + + amountBalsScv, err := scval.WrapInt128Parts(amountInt128) + if err != nil { + return errors.New("error while wrapping int128 parts") + } + + stellarAddr, err := types.MakeAccountAddress(kp) + if err != nil { + return errors.New("error while making account address") + } + scClientAddr := scval.MustWrapScAddress(stellarAddr) + scPerunAddr := scval.MustWrapScAddress(perunContractAddress) + transferArgs := xdr.ScVec{scClientAddr, scPerunAddr, amountBalsScv} + + authTransfer := xdr.SorobanAuthorizedInvocation{ + Function: xdr.SorobanAuthorizedFunction{ + Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, + ContractFn: &xdr.InvokeContractArgs{ + ContractAddress: tokenContractAddress, + FunctionName: "transfer", + Args: transferArgs, + }, + }, + SubInvocations: nil, + } + fmt.Println("authTransfer: ", authTransfer) + + fundRootInv := xdr.SorobanAuthorizedInvocation{ + Function: xdr.SorobanAuthorizedFunction{ + Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, + ContractFn: &xdr.InvokeContractArgs{ + ContractAddress: perunContractAddress, + FunctionName: "fund", + Args: fundTxArgs, + }, + }, + SubInvocations: []xdr.SorobanAuthorizedInvocation{}, //authTransfer + } + pphrase := f.stellarClient.GetPassPhrase() - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hzAcc, "fund", fundTxArgs, contractAddress, kp) + preimg, err := makePreImgAuth(pphrase, fundRootInv) + if err != nil { + panic(err) + } + + preimgMarshaled, err := preimg.MarshalBinary() + if err != nil { + panic(err) + } + + hashSign, err := kp.Sign(preimgMarshaled) + if err != nil { + panic(err) + } + + hashScVal, err := scval.WrapScBytes(hashSign) + if err != nil { + panic(err) + } + + srbAddrCreds := xdr.SorobanAddressCredentials{ + Address: stellarAddr, + Nonce: preimg.SorobanAuthorization.Nonce, + SignatureExpirationLedger: preimg.SorobanAuthorization.SignatureExpirationLedger, + Signature: hashScVal, + } + + srbCreds := xdr.SorobanCredentials{Address: &srbAddrCreds, + Type: xdr.SorobanCredentialsTypeSorobanCredentialsAddress} + + authFundClx := []xdr.SorobanAuthorizationEntry{ + { + Credentials: srbCreds, + RootInvocation: fundRootInv, + }, + } + + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hzAcc, "fund", fundTxArgs, perunContractAddress, kp, authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx if err != nil { return errors.New("error while invoking and processing host function: fund") } - _, err = DecodeEvents(txMeta) + _, err = DecodeEventsPerun(txMeta) if err != nil { return errors.New("error while decoding events") } @@ -177,9 +288,35 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state return nil } +func makePreImgAuth(passphrase string, rootInv xdr.SorobanAuthorizedInvocation) (xdr.HashIdPreimage, error) { + max := big.NewInt(0).SetInt64(int64(^uint64(0) >> 1)) + randomPart, err := rand.Int(rand.Reader, max) + if err != nil { + panic(err) + } + networkId := xdr.Hash(sha256.Sum256([]byte(passphrase))) + ledgerEntry := uint32(100) + lEntryXdr := xdr.Uint32(ledgerEntry) + nonce := randomPart.Int64() + ncXdr := xdr.Int64(nonce) + + srbPreImgAuth := xdr.HashIdPreimageSorobanAuthorization{ + NetworkId: networkId, + Nonce: ncXdr, + SignatureExpirationLedger: lEntryXdr, + Invocation: rootInv, + } + + srbAuth := xdr.HashIdPreimage{ + Type: xdr.EnvelopeTypeEnvelopeTypeSorobanAuthorization, + SorobanAuthorization: &srbPreImgAuth} + + return srbAuth, nil +} + func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { - contractAddress := f.stellarClient.GetContractIDAddress() + contractAddress := f.stellarClient.GetPerunAddress() kp := f.kpFull reqAlice := f.stellarClient.GetHorizonAcc() chanId := state.ID @@ -189,12 +326,13 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat if err != nil { return errors.New("error while building get_channel tx") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp) + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: abort_funding") } - _, err = DecodeEvents(txMeta) + _, err = DecodeEventsPerun(txMeta) if err != nil { return errors.New("error while decoding events") } @@ -204,7 +342,7 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { - contractAddress := f.stellarClient.GetContractIDAddress() + contractAddress := f.stellarClient.GetPerunAddress() kp := f.kpFull hz := f.stellarClient.GetHorizonAcc() chanId := state.ID @@ -214,8 +352,8 @@ func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, s if err != nil { return wire.Channel{}, errors.New("error while building get_channel tx") } - - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "get_channel", getchTxArgs, contractAddress, kp) + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "get_channel", getchTxArgs, contractAddress, kp, auth) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } diff --git a/channel/funder_test.go b/channel/funder_test.go index 3ebe511..5cad677 100644 --- a/channel/funder_test.go +++ b/channel/funder_test.go @@ -2,6 +2,7 @@ package channel_test import ( "context" + "fmt" "github.com/stellar/go/xdr" "github.com/stretchr/testify/require" pchannel "perun.network/go-perun/channel" @@ -32,6 +33,8 @@ func TestOpenChannel(t *testing.T) { // Create the contract + fmt.Println("itest.GetPassPhrase(): ", itest.GetPassPhrase()) + createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), channel.PerunContractPath, "a1", itest.GetPassPhrase()) preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *createContractOp) _, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) @@ -49,8 +52,8 @@ func TestOpenChannel(t *testing.T) { perunFirstParams, perunFirstState := chtest.NewParamsState(t) openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) - - invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress) + auth := []xdr.SorobanAuthorizationEntry{} + invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress, auth) preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) @@ -60,7 +63,7 @@ func TestOpenChannel(t *testing.T) { txMeta, err := env.DecodeTxMeta(tx) require.NoError(t, err) - _, err = channel.DecodeEvents(txMeta) + _, err = channel.DecodeEventsPerun(txMeta) require.NoError(t, err) } @@ -79,7 +82,7 @@ func TestFundChannel(t *testing.T) { reqDeployer := itest.AccountDetails(kpDeployer) contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) - itest.SetContractIDAddress(contractAddr) + itest.SetPerunAddress(contractAddr) perunFirstParams, perunFirstState := chtest.NewParamsState(t) diff --git a/channel/token.go b/channel/token.go new file mode 100644 index 0000000..3a21077 --- /dev/null +++ b/channel/token.go @@ -0,0 +1,157 @@ +package channel + +import ( + "context" + "errors" + "github.com/stellar/go/xdr" + "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/wire/scval" +) + +func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName string, tokenSymbol string) (xdr.ScVec, error) { + + adminScAddr, err := scval.WrapScAddress(adminAddr) + if err != nil { + panic(err) + } + + decim := xdr.Uint32(decimals) + scvaltype := xdr.ScValTypeScvU32 + decimSc, err := xdr.NewScVal(scvaltype, decim) + if err != nil { + panic(err) + } + + tokenNameScString := xdr.ScString(tokenName) + tokenNameXdr := scval.MustWrapScString(tokenNameScString) + + tokenSymbolString := xdr.ScString(tokenSymbol) + tokenSymbolXdr := scval.MustWrapScString(tokenSymbolString) + + initTokenArgs := xdr.ScVec{ + adminScAddr, + decimSc, + tokenNameXdr, + tokenSymbolXdr, + } + + return initTokenArgs, nil +} + +func BuildMintTokenArgs(mintTo xdr.ScAddress, amount int64) (xdr.ScVec, error) { + + recScAddr, err := scval.WrapScAddress(mintTo) + if err != nil { + panic(err) + } + + amounti64 := xdr.Int64(amount) + scvaltype := xdr.ScValTypeScvI64 + + amountSc, err := xdr.NewScVal(scvaltype, amounti64) + if err != nil { + panic(err) + } + + MintTokenArgs := xdr.ScVec{ + recScAddr, + amountSc, + } + + return MintTokenArgs, nil +} + +func BuildTokenNameArgs() (xdr.ScVec, error) { + + return xdr.ScVec{}, nil +} + +func BuildGetTokenBalanceArgs(balanceOf xdr.ScAddress) (xdr.ScVec, error) { + + recScAddr, err := scval.WrapScAddress(balanceOf) + if err != nil { + panic(err) + } + + GetTokenBalanceArgs := xdr.ScVec{ + recScAddr, + } + + return GetTokenBalanceArgs, nil +} + +func InitTokenContract(ctx context.Context, stellarClient *env.StellarClient, adminAddr xdr.ScAddress, decimals uint32, tokenName, tokenSymbol string, contractAddress xdr.ScAddress) error { + + hzAcc := stellarClient.GetHorizonAcc() + kp := stellarClient.GetAccount() + + initTokenArgs, err := BuildInitTokenArgs(adminAddr, decimals, tokenName, tokenSymbol) + if err != nil { + return errors.New("error while building fund tx") + } + auth := []xdr.SorobanAuthorizationEntry{} + _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "initialize", initTokenArgs, contractAddress, kp, auth) + if err != nil { + return errors.New("error while invoking and processing host function for InitTokenContract") + } + + return nil +} + +func GetTokenName(ctx context.Context, stellarClient *env.StellarClient, contractAddress xdr.ScAddress) error { + + hzAcc := stellarClient.GetHorizonAcc() + kp := stellarClient.GetAccount() + // generate tx to open the channel + TokenNameArgs, err := BuildTokenNameArgs() + if err != nil { + return errors.New("error while building fund tx") + } + auth := []xdr.SorobanAuthorizationEntry{} + + _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "name", TokenNameArgs, contractAddress, kp, auth) + if err != nil { + return errors.New("error while invoking and processing host function for GetTokenName") + } + + return nil +} + +func MintToken(ctx context.Context, stellarClient *env.StellarClient, mintTo xdr.ScAddress, mintAmount int64, contractAddress xdr.ScAddress) error { + + hzAcc := stellarClient.GetHorizonAcc() + kp := stellarClient.GetAccount() + TokenNameArgs, err := BuildMintTokenArgs(mintTo, mintAmount) + if err != nil { + return errors.New("error while building fund tx") + } + auth := []xdr.SorobanAuthorizationEntry{} + _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "mint", TokenNameArgs, contractAddress, kp, auth) + if err != nil { + return errors.New("error while invoking and processing host function for GetTokenName") + } + + return nil +} + +func GetTokenBalance(ctx context.Context, stellarClient *env.StellarClient, balanceOf xdr.ScAddress, contractAddress xdr.ScAddress) error { + + hzAcc := stellarClient.GetHorizonAcc() + kp := stellarClient.GetAccount() + // generate tx to open the channel + getBalanceArgs, err := BuildGetTokenBalanceArgs(balanceOf) + if err != nil { + return errors.New("error while building fund tx") + } + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "balance", getBalanceArgs, contractAddress, kp, auth) + if err != nil { + return errors.New("error while invoking and processing host function for GetTokenName") + } + + _ = txMeta.V3.SorobanMeta.ReturnValue + + // rVal := reVal.MustI128() + + return nil +} diff --git a/main.go b/main.go index ac35e25..8bfe7b3 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( - "fmt" + "context" + _ "github.com/stellar/go/txnbuild" "perun.network/go-perun/wire" + "perun.network/perun-stellar-backend/channel" "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/channel/types" "perun.network/perun-stellar-backend/client" "perun.network/perun-stellar-backend/util" ) @@ -13,28 +16,80 @@ const StellarAssetContractPath = "./testdata/perun_soroban_token.wasm" func main() { - // Create a Backend to interact with the Stellar network: integration test environment from the stellar/go sdk stellarEnv := env.NewBackendEnv() - // Create two Stellar L1 accounts - kps, _ := stellarEnv.CreateAccounts(4, "100000000") - + kps, _ := stellarEnv.CreateAccounts(5, "100000000") kpAlice := kps[0] kpBob := kps[1] kpDeployerPerun := kps[2] kpDeployerToken := kps[3] + _ = kps[4] + + tokenContractIDAddress := util.Deploy(stellarEnv, kpDeployerToken, stellarEnv.AccountDetails(kpDeployerToken), StellarAssetContractPath) + + adminScAddr, err := types.MakeAccountAddress(kpDeployerToken) + if err != nil { + panic(err) + } + decims := uint32(7) + tokenName := "PerunToken" + tokenSymbol := "PRN" + + deployerStellarClient := env.NewStellarClient(stellarEnv, kpDeployerToken) + // aliceStellarClient := env.NewStellarClient(stellarEnv, kpAlice) + + err = channel.InitTokenContract(context.TODO(), deployerStellarClient, adminScAddr, decims, tokenName, tokenSymbol, tokenContractIDAddress) //tokenContractIDAddress) + if err != nil { + panic(err) + } + + err = channel.GetTokenName(context.TODO(), deployerStellarClient, tokenContractIDAddress) + if err != nil { + panic(err) + } + + aliceAddrXdr, err := types.MakeAccountAddress(kpAlice) + if err != nil { + panic(err) + } + bobAddrXdr, err := types.MakeAccountAddress(kpBob) + if err != nil { + panic(err) + } + + mintAmount := int64(100000000) + + err = channel.MintToken(context.TODO(), deployerStellarClient, aliceAddrXdr, mintAmount, tokenContractIDAddress) + if err != nil { + panic(err) + } + err = channel.MintToken(context.TODO(), deployerStellarClient, bobAddrXdr, mintAmount, tokenContractIDAddress) + if err != nil { + panic(err) + } - realAssetContractID := util.Deploy(stellarEnv, kpDeployerToken, stellarEnv.AccountDetails(kpDeployerToken), StellarAssetContractPath) + err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, aliceAddrXdr, tokenContractIDAddress) + if err != nil { + panic(err) + } + err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, bobAddrXdr, tokenContractIDAddress) + if err != nil { + panic(err) + } - fmt.Println("Deployed Real Asset Contract ID: ", realAssetContractID) - // Deploy the Perun contract - contractIDAddress := util.Deploy(stellarEnv, kpDeployerPerun, stellarEnv.AccountDetails(kpDeployerPerun), PerunContractPath) - stellarEnv.SetContractIDAddress(contractIDAddress) + perunContractIDAddress := util.Deploy(stellarEnv, kpDeployerPerun, stellarEnv.AccountDetails(kpDeployerPerun), PerunContractPath) + stellarEnv.SetPerunAddress(perunContractIDAddress) + stellarEnv.SetTokenAddress(tokenContractIDAddress) // Generate L2 accounts for the payment channel wAlice, accAlice, _ := util.MakeRandPerunWallet() wBob, accBob, _ := util.MakeRandPerunWallet() - assetContractID := util.NewRandAsset() + assetCID, err := types.NewStellarAssetFromScAddress(tokenContractIDAddress) // tokenContractIDAddress //util.NewRandAsset() + if err != nil { + panic(err) + } + assetContractID := *assetCID + bus := wire.NewLocalBus() alicePerun, err := client.SetupPaymentClient(stellarEnv, wAlice, accAlice, kpAlice, assetContractID, bus) if err != nil { diff --git a/wire/scval/scval.go b/wire/scval/scval.go index 8f1f2cd..11a0c65 100644 --- a/wire/scval/scval.go +++ b/wire/scval/scval.go @@ -50,6 +50,18 @@ func MustWrapScSymbol(symbol xdr.ScSymbol) xdr.ScVal { return v } +func WrapScString(str xdr.ScString) (xdr.ScVal, error) { + return xdr.NewScVal(xdr.ScValTypeScvString, str) +} + +func MustWrapScString(str xdr.ScString) xdr.ScVal { + v, err := WrapScString(str) + if err != nil { + panic(err) + } + return v +} + func WrapScBytes(b xdr.ScBytes) (xdr.ScVal, error) { return xdr.NewScVal(xdr.ScValTypeScvBytes, b) } From ece511c387cd65d56aa5fffe2a26c2b4a7c562fc Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 12 Dec 2023 16:49:33 +0100 Subject: [PATCH 09/51] main: Execute Token contract calls channel: Include contract function calls and arguments to invoke stellar asset token functions. Also, extend IntegrationTestEnv functionality. util: Modify Deploy function to output contract id hash --- channel/adjudicator_test.go | 4 +- channel/env/client.go | 6 +- channel/env/integration.go | 54 ++++++++++- channel/env/transaction.go | 30 ++++++ channel/funder.go | 188 +++++++++++++++++++----------------- channel/funder_test.go | 2 +- channel/token.go | 47 +++++++++ go.mod | 5 +- main.go | 56 ++++++++--- util/util.go | 5 +- 10 files changed, 288 insertions(+), 109 deletions(-) diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go index 1765c24..5e79fb1 100644 --- a/channel/adjudicator_test.go +++ b/channel/adjudicator_test.go @@ -29,7 +29,7 @@ func TestCloseChannel(t *testing.T) { kpDeployer := kps[2] reqDeployer := itest.AccountDetails(kpDeployer) - contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) + contractAddr, _ := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) itest.SetPerunAddress(contractAddr) _, accAlice, _ := util.MakeRandPerunWallet() @@ -123,7 +123,7 @@ func TestWithdrawChannel(t *testing.T) { reqDeployer := itest.AccountDetails(kpDeployer) - contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) + contractAddr, _ := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) itest.SetPerunAddress(contractAddr) _, accAlice, _ := util.MakeRandPerunWallet() diff --git a/channel/env/client.go b/channel/env/client.go index 547359b..f3427ec 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -2,9 +2,9 @@ package env import ( "errors" - "fmt" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" "perun.network/perun-stellar-backend/wire" ) @@ -57,12 +57,12 @@ func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, preFlightOp, minFee := s.stellarEnv.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) - tx, err := s.stellarEnv.SubmitOperationsWithFee(&horizonAcc, kp, minFee, &preFlightOp) + tx, err := s.stellarEnv.SubmitOperationsWithFee(&horizonAcc, kp, minFee+txnbuild.MinBaseFee, &preFlightOp) if err != nil { panic(err) } - fmt.Println("tx from ", fname, ": ", tx) + // fmt.Println("tx from ", fname, ": ", tx) // Decode transaction metadata txMeta, err := DecodeTxMeta(tx) diff --git a/channel/env/integration.go b/channel/env/integration.go index 9559533..4f4dda6 100644 --- a/channel/env/integration.go +++ b/channel/env/integration.go @@ -2,12 +2,14 @@ package env import ( "errors" + "github.com/stellar/go/amount" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" testenv "github.com/stellar/go/services/horizon/pkg/test/integration" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" "testing" ) @@ -26,8 +28,9 @@ func NewBackendEnv() *IntegrationTestEnv { t := &testing.T{} cfg := testenv.Config{ - ProtocolVersion: PtotocolVersion, - EnableSorobanRPC: EnableSorobanRPC, + ProtocolVersion: PtotocolVersion, + EnableSorobanRPC: EnableSorobanRPC, + HorizonEnvironment: map[string]string{"INGEST_DISABLE_STATE_VERIFICATION": "true", "CONNECTION_TIMEOUT": "360000"}, } itest := IntegrationTestEnv{testEnv: testenv.NewTest(t, cfg)} return &itest @@ -55,6 +58,19 @@ func (it *IntegrationTestEnv) CreateAccounts(numAccounts int, initBalance string return kps, accs } +func (it *IntegrationTestEnv) CreateAccount(initialBalance string) (*keypair.Full, txnbuild.Account) { + kps, accts := it.CreateAccounts(1, initialBalance) + return kps[0], accts[0] +} + +func (it *IntegrationTestEnv) CurrentTest() *testing.T { + return it.testEnv.CurrentTest() +} + +func (it *IntegrationTestEnv) Master() *keypair.Full { + return it.testEnv.Master() +} + func (it *IntegrationTestEnv) AccountDetails(acc *keypair.Full) horizon.Account { accountReq := horizonclient.AccountRequest{AccountID: acc.Address()} hzAccount, err := it.testEnv.Client().AccountDetail(accountReq) @@ -98,6 +114,21 @@ func (it *IntegrationTestEnv) GetPerunAddress() xdr.ScAddress { return it.perunAddress } +func AssertContainsBalance(it *IntegrationTestEnv, acct *keypair.Full, issuer, code string, amt xdr.Int64) { + accountResponse := it.testEnv.MustGetAccount(acct) + if issuer == "" && code == "" { + xlmBalance, err := accountResponse.GetNativeBalance() + if err != nil { + panic(err) + } + assert.NoError(it.testEnv.CurrentTest(), err) + assert.Equal(it.testEnv.CurrentTest(), amt, amount.MustParse(xlmBalance)) + } else { + assetBalance := accountResponse.GetCreditBalance(code, issuer) + assert.Equal(it.testEnv.CurrentTest(), amt, amount.MustParse(assetBalance)) + } +} + func (it *IntegrationTestEnv) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { // Build contract call operation fnameXdr := xdr.ScSymbol(fname) @@ -125,3 +156,22 @@ func (it *IntegrationTestEnv) InvokeAndProcessHostFunction(horizonAcc horizon.Ac func (it *IntegrationTestEnv) SubmitOperations(source txnbuild.Account, signer *keypair.Full, ops ...txnbuild.Operation) (horizon.Transaction, error) { return it.testEnv.SubmitOperations(source, signer, ops...) } + +func (it *IntegrationTestEnv) MustGetAccount(source *keypair.Full) horizon.Account { + client := it.Client() + account, err := client.AccountDetail(horizonclient.AccountRequest{AccountID: source.Address()}) + if err != nil { + panic(err) + } + return account +} + +func (it *IntegrationTestEnv) MustEstablishTrustline( + truster *keypair.Full, account txnbuild.Account, asset txnbuild.Asset, +) (resp horizon.Transaction) { + txResp, err := it.testEnv.EstablishTrustline(truster, account, asset) + if err != nil { + panic(err) + } + return txResp +} diff --git a/channel/env/transaction.go b/channel/env/transaction.go index 6a1a398..90045f5 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -112,3 +112,33 @@ func DecodeTxMeta(tx horizon.Transaction) (xdr.TransactionMeta, error) { return transactionMeta, nil } + +func stellarAssetContractID(stellarEnv *IntegrationTestEnv, asset xdr.Asset) xdr.Hash { + contractID, err := asset.ContractID(stellarEnv.GetPassPhrase()) + if err != nil { + panic(err) + } + return contractID +} + +func i128Param(hi int64, lo uint64) xdr.ScVal { + i128 := &xdr.Int128Parts{ + Hi: xdr.Int64(hi), + Lo: xdr.Uint64(lo), + } + return xdr.ScVal{ + Type: xdr.ScValTypeScvI128, + I128: i128, + } +} + +func AccountAddressParam(accountID string) xdr.ScVal { + address := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeAccount, + AccountId: xdr.MustAddressPtr(accountID), + } + return xdr.ScVal{ + Type: xdr.ScValTypeScvAddress, + Address: &address, + } +} diff --git a/channel/funder.go b/channel/funder.go index a4246a8..a72e294 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -6,7 +6,6 @@ import ( "crypto/sha256" "errors" "fmt" - // "github.com/stellar/go/gxdr" "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" "log" @@ -14,7 +13,6 @@ import ( pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel/env" - "perun.network/perun-stellar-backend/channel/types" "perun.network/perun-stellar-backend/wallet" "perun.network/perun-stellar-backend/wire" "perun.network/perun-stellar-backend/wire/scval" @@ -162,7 +160,11 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state perunContractAddress := f.stellarClient.GetPerunAddress() tokenContractAddress := f.stellarClient.GetTokenAddress() - fmt.Println("perunContractAddress: ", perunContractAddress, "tokenContractAddress: ", tokenContractAddress) + + // tokenAddrString, err := tokenContractAddress.String() + // if err != nil { + // return errors.New("error while converting token address to string") + // } kp := f.kpFull hzAcc := f.stellarClient.GetHorizonAcc() @@ -173,109 +175,123 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state if err != nil { return errors.New("error while building fund tx") } - // fmt.Println("funderchan args: ", perunContractAddress, kp, hzAcc, chanId, fundTxArgs, funderIdx) - balsStellar, err := wire.MakeBalances(state.Allocation) if err != nil { return errors.New("error while making balances") } - // tokenIDAsset := balsStellar.Token - // fmt.Println("tokenIDAsset, perunID: ", tokenIDAsset.ContractId, perunContractAddress.ContractId) + tokenIDAddrFromBals := balsStellar.Token - var amountInt128 xdr.Int128Parts + same := tokenIDAddrFromBals.Equals(tokenContractAddress) + if !same { + panic("tokenIDAddrFromBals not equal to tokenContractAddress") + } - if funderIdx { + // var amountInt128 xdr.Int128Parts - amountInt128 = balsStellar.BalB - if err != nil { - return errors.New("error while making int128 parts on index 1") - } + // if funderIdx { - } else { - amountInt128 = balsStellar.BalA - if err != nil { - return errors.New("error while making int128 parts on index 0") - } - } + // amountInt128 = balsStellar.BalB + // if err != nil { + // return errors.New("error while making int128 parts on index 1") + // } - amountBalsScv, err := scval.WrapInt128Parts(amountInt128) - if err != nil { - return errors.New("error while wrapping int128 parts") - } + // } else { + // amountInt128 = balsStellar.BalA + // if err != nil { + // return errors.New("error while making int128 parts on index 0") + // } + // } - stellarAddr, err := types.MakeAccountAddress(kp) - if err != nil { - return errors.New("error while making account address") - } - scClientAddr := scval.MustWrapScAddress(stellarAddr) - scPerunAddr := scval.MustWrapScAddress(perunContractAddress) - transferArgs := xdr.ScVec{scClientAddr, scPerunAddr, amountBalsScv} - - authTransfer := xdr.SorobanAuthorizedInvocation{ - Function: xdr.SorobanAuthorizedFunction{ - Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, - ContractFn: &xdr.InvokeContractArgs{ - ContractAddress: tokenContractAddress, - FunctionName: "transfer", - Args: transferArgs, - }, - }, - SubInvocations: nil, - } - fmt.Println("authTransfer: ", authTransfer) - - fundRootInv := xdr.SorobanAuthorizedInvocation{ - Function: xdr.SorobanAuthorizedFunction{ - Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, - ContractFn: &xdr.InvokeContractArgs{ - ContractAddress: perunContractAddress, - FunctionName: "fund", - Args: fundTxArgs, - }, - }, - SubInvocations: []xdr.SorobanAuthorizedInvocation{}, //authTransfer - } - pphrase := f.stellarClient.GetPassPhrase() + // amountBalsScv, err := scval.WrapInt128Parts(amountInt128) + // if err != nil { + // return errors.New("error while wrapping int128 parts") + // } - preimg, err := makePreImgAuth(pphrase, fundRootInv) - if err != nil { - panic(err) - } + // stellarAddr, err := types.MakeAccountAddress(kp) + // if err != nil { + // return errors.New("error while making account address") + // } + // scClientAddr := scval.MustWrapScAddress(stellarAddr) + // scPerunAddr := scval.MustWrapScAddress(perunContractAddress) + // transferArgs := xdr.ScVec{scClientAddr, scPerunAddr, amountBalsScv} + + // transfer here to perun, try if it works + + tokenAddr := tokenContractAddress //balsStellar.Token + + // authTransfer := xdr.SorobanAuthorizedInvocation{ + // Function: xdr.SorobanAuthorizedFunction{ + // Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, + // ContractFn: &xdr.InvokeContractArgs{ + // ContractAddress: tokenContractAddress, + // FunctionName: "transfer", + // Args: transferArgs, + // }, + // }, + // SubInvocations: nil, + // } - preimgMarshaled, err := preimg.MarshalBinary() - if err != nil { - panic(err) - } + // fundRootInv := xdr.SorobanAuthorizedInvocation{ + // Function: xdr.SorobanAuthorizedFunction{ + // Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, + // ContractFn: &xdr.InvokeContractArgs{ + // ContractAddress: perunContractAddress, + // FunctionName: "fund", + // Args: fundTxArgs, + // }, + // }, + // SubInvocations: []xdr.SorobanAuthorizedInvocation{}, //authTransfer + // } + // pphrase := f.stellarClient.GetPassPhrase() - hashSign, err := kp.Sign(preimgMarshaled) - if err != nil { - panic(err) - } + // preimg, err := makePreImgAuth(pphrase, fundRootInv) + // if err != nil { + // panic(err) + // } - hashScVal, err := scval.WrapScBytes(hashSign) - if err != nil { - panic(err) - } + // preimgMarshaled, err := preimg.MarshalBinary() + // if err != nil { + // panic(err) + // } - srbAddrCreds := xdr.SorobanAddressCredentials{ - Address: stellarAddr, - Nonce: preimg.SorobanAuthorization.Nonce, - SignatureExpirationLedger: preimg.SorobanAuthorization.SignatureExpirationLedger, - Signature: hashScVal, - } + // hashSign, err := kp.Sign(preimgMarshaled) + // if err != nil { + // panic(err) + // } + + // hashScVal, err := scval.WrapScBytes(hashSign) + // if err != nil { + // panic(err) + // } - srbCreds := xdr.SorobanCredentials{Address: &srbAddrCreds, - Type: xdr.SorobanCredentialsTypeSorobanCredentialsAddress} + // srbAddrCreds := xdr.SorobanAddressCredentials{ + // Address: stellarAddr, + // Nonce: preimg.SorobanAuthorization.Nonce, + // SignatureExpirationLedger: preimg.SorobanAuthorization.SignatureExpirationLedger, + // Signature: hashScVal, + // } + + // srbCreds := xdr.SorobanCredentials{Address: &srbAddrCreds, + // Type: xdr.SorobanCredentialsTypeSorobanCredentialsAddress} + + // authFundClx := []xdr.SorobanAuthorizationEntry{ + // { + // Credentials: srbCreds, + // RootInvocation: fundRootInv, + // }, + // } + + tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) - authFundClx := []xdr.SorobanAuthorizationEntry{ - { - Credentials: srbCreds, - RootInvocation: fundRootInv, - }, + testArgs := xdr.ScVec{tokenAddrXdr} + hzAcctInt0 := f.stellarClient.GetHorizonAcc() + _, err = f.stellarClient.InvokeAndProcessHostFunction(hzAcctInt0, "testinteract", testArgs, perunContractAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx + if err != nil { + return errors.New("error while invoking and processing host function: testinteract") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hzAcc, "fund", fundTxArgs, perunContractAddress, kp, authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hzAcc, "fund", fundTxArgs, perunContractAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx if err != nil { return errors.New("error while invoking and processing host function: fund") } diff --git a/channel/funder_test.go b/channel/funder_test.go index 5cad677..5bef8d2 100644 --- a/channel/funder_test.go +++ b/channel/funder_test.go @@ -81,7 +81,7 @@ func TestFundChannel(t *testing.T) { _ = itest.AccountDetails(kpBob) reqDeployer := itest.AccountDetails(kpDeployer) - contractAddr := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) + contractAddr, _ := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) itest.SetPerunAddress(contractAddr) perunFirstParams, perunFirstState := chtest.NewParamsState(t) diff --git a/channel/token.go b/channel/token.go index 3a21077..aacd1cd 100644 --- a/channel/token.go +++ b/channel/token.go @@ -80,6 +80,32 @@ func BuildGetTokenBalanceArgs(balanceOf xdr.ScAddress) (xdr.ScVec, error) { return GetTokenBalanceArgs, nil } +func BuildTransferTokenArgs(from xdr.ScAddress, to xdr.ScAddress, amount xdr.Int128Parts) (xdr.ScVec, error) { + + fromScAddr, err := scval.WrapScAddress(from) + if err != nil { + panic(err) + } + + toScAddr, err := scval.WrapScAddress(to) + if err != nil { + panic(err) + } + + amountSc, err := scval.WrapInt128Parts(amount) + if err != nil { + panic(err) + } + + GetTokenBalanceArgs := xdr.ScVec{ + fromScAddr, + toScAddr, + amountSc, + } + + return GetTokenBalanceArgs, nil +} + func InitTokenContract(ctx context.Context, stellarClient *env.StellarClient, adminAddr xdr.ScAddress, decimals uint32, tokenName, tokenSymbol string, contractAddress xdr.ScAddress) error { hzAcc := stellarClient.GetHorizonAcc() @@ -155,3 +181,24 @@ func GetTokenBalance(ctx context.Context, stellarClient *env.StellarClient, bala return nil } + +func TransferToken(ctx context.Context, stellarClient *env.StellarClient, from xdr.ScAddress, to xdr.ScAddress, amount128 xdr.Int128Parts, contractAddress xdr.ScAddress) error { + + hzAcc := stellarClient.GetHorizonAcc() + kp := stellarClient.GetAccount() + transferArgs, err := BuildTransferTokenArgs(from, to, amount128) + if err != nil { + return errors.New("error while building fund tx") + } + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "transfer", transferArgs, contractAddress, kp, auth) + if err != nil { + return errors.New("error while invoking and processing host function for GetTokenName") + } + + _ = txMeta.V3.SorobanMeta.ReturnValue + + // rVal := reVal.MustI128() + + return nil +} diff --git a/go.mod b/go.mod index 46c97db..4b6233f 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,9 @@ require ( polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b ) -//replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => github.com/perun-network/go v0.0.0-20231003185205-facabfc2f4c4 -replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => ../stellarfork/go +replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => github.com/perun-network/go v0.0.0-20231212081247-248b5be6c4ba + +// replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => ../stellarfork/go require ( github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 // indirect diff --git a/main.go b/main.go index 8bfe7b3..f7ef742 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,8 @@ package main import ( "context" - _ "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + "log" "perun.network/go-perun/wire" "perun.network/perun-stellar-backend/channel" "perun.network/perun-stellar-backend/channel/env" @@ -17,15 +18,12 @@ const StellarAssetContractPath = "./testdata/perun_soroban_token.wasm" func main() { stellarEnv := env.NewBackendEnv() - - kps, _ := stellarEnv.CreateAccounts(5, "100000000") + kps, _ := stellarEnv.CreateAccounts(5, "1000000000") kpAlice := kps[0] kpBob := kps[1] kpDeployerPerun := kps[2] kpDeployerToken := kps[3] - _ = kps[4] - - tokenContractIDAddress := util.Deploy(stellarEnv, kpDeployerToken, stellarEnv.AccountDetails(kpDeployerToken), StellarAssetContractPath) + tokenContractIDAddress, _ := util.Deploy(stellarEnv, kpDeployerToken, stellarEnv.AccountDetails(kpDeployerToken), StellarAssetContractPath) adminScAddr, err := types.MakeAccountAddress(kpDeployerToken) if err != nil { @@ -36,9 +34,9 @@ func main() { tokenSymbol := "PRN" deployerStellarClient := env.NewStellarClient(stellarEnv, kpDeployerToken) - // aliceStellarClient := env.NewStellarClient(stellarEnv, kpAlice) + aliceStellarClient := env.NewStellarClient(stellarEnv, kpAlice) - err = channel.InitTokenContract(context.TODO(), deployerStellarClient, adminScAddr, decims, tokenName, tokenSymbol, tokenContractIDAddress) //tokenContractIDAddress) + err = channel.InitTokenContract(context.TODO(), deployerStellarClient, adminScAddr, decims, tokenName, tokenSymbol, tokenContractIDAddress) if err != nil { panic(err) } @@ -68,6 +66,21 @@ func main() { panic(err) } + err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, aliceAddrXdr, tokenContractIDAddress) + if err != nil { + panic(err) + } + err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, bobAddrXdr, tokenContractIDAddress) + if err != nil { + panic(err) + } + txAmountBob128 := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(mintAmount / 10.)} + + err = channel.TransferToken(context.TODO(), aliceStellarClient, aliceAddrXdr, bobAddrXdr, txAmountBob128, tokenContractIDAddress) + if err != nil { + panic(err) + } + err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, aliceAddrXdr, tokenContractIDAddress) if err != nil { panic(err) @@ -77,14 +90,32 @@ func main() { panic(err) } - perunContractIDAddress := util.Deploy(stellarEnv, kpDeployerPerun, stellarEnv.AccountDetails(kpDeployerPerun), PerunContractPath) + perunContractIDAddress, _ := util.Deploy(stellarEnv, kpDeployerPerun, stellarEnv.AccountDetails(kpDeployerPerun), PerunContractPath) stellarEnv.SetPerunAddress(perunContractIDAddress) stellarEnv.SetTokenAddress(tokenContractIDAddress) - // Generate L2 accounts for the payment channel + err = channel.MintToken(context.TODO(), deployerStellarClient, perunContractIDAddress, mintAmount, tokenContractIDAddress) + if err != nil { + panic(err) + } + + txAmountPerun128 := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(mintAmount / 20.)} + + err = channel.TransferToken(context.TODO(), aliceStellarClient, aliceAddrXdr, perunContractIDAddress, txAmountPerun128, tokenContractIDAddress) + if err != nil { + panic(err) + } + + err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, perunContractIDAddress, tokenContractIDAddress) + if err != nil { + panic(err) + } + + // // Generate L2 accounts for the payment channel wAlice, accAlice, _ := util.MakeRandPerunWallet() wBob, accBob, _ := util.MakeRandPerunWallet() - assetCID, err := types.NewStellarAssetFromScAddress(tokenContractIDAddress) // tokenContractIDAddress //util.NewRandAsset() + + assetCID, err := types.NewStellarAssetFromScAddress(tokenContractIDAddress) if err != nil { panic(err) } @@ -97,6 +128,7 @@ func main() { } bobPerun, err := client.SetupPaymentClient(stellarEnv, wBob, accBob, kpBob, assetContractID, bus) + if err != nil { panic(err) } @@ -111,4 +143,6 @@ func main() { alicePerun.Shutdown() bobPerun.Shutdown() + log.Println("Done") + } diff --git a/util/util.go b/util/util.go index 0d970cc..4d12ad3 100644 --- a/util/util.go +++ b/util/util.go @@ -14,7 +14,7 @@ import ( mathrand "math/rand" ) -func Deploy(stellarEnv *env.IntegrationTestEnv, kp *keypair.Full, hz horizon.Account, contractPath string) xdr.ScAddress { +func Deploy(stellarEnv *env.IntegrationTestEnv, kp *keypair.Full, hz horizon.Account, contractPath string) (xdr.ScAddress, xdr.Hash) { // Install contract installContractOp := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) preFlightOp, minFee := stellarEnv.PreflightHostFunctions(&hz, *installContractOp) @@ -28,11 +28,12 @@ func Deploy(stellarEnv *env.IntegrationTestEnv, kp *keypair.Full, hz horizon.Acc panic(err) } contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + contractHash := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash contractIDAddress := xdr.ScAddress{ Type: xdr.ScAddressTypeScAddressTypeContract, ContractId: contractID, } - return contractIDAddress + return contractIDAddress, contractHash } From 2c43939d5901a8b10f72251050c8f5f7d183d297 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 12 Dec 2023 16:52:29 +0100 Subject: [PATCH 10/51] channel: Remove commented authorization generation code --- channel/funder.go | 105 ++-------------------------------------------- 1 file changed, 3 insertions(+), 102 deletions(-) diff --git a/channel/funder.go b/channel/funder.go index a72e294..8c4f798 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -161,11 +161,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state perunContractAddress := f.stellarClient.GetPerunAddress() tokenContractAddress := f.stellarClient.GetTokenAddress() - // tokenAddrString, err := tokenContractAddress.String() - // if err != nil { - // return errors.New("error while converting token address to string") - // } - kp := f.kpFull hzAcc := f.stellarClient.GetHorizonAcc() chanId := state.ID @@ -182,106 +177,12 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state tokenIDAddrFromBals := balsStellar.Token - same := tokenIDAddrFromBals.Equals(tokenContractAddress) - if !same { + sameContractTokenID := tokenIDAddrFromBals.Equals(tokenContractAddress) + if !sameContractTokenID { panic("tokenIDAddrFromBals not equal to tokenContractAddress") } - // var amountInt128 xdr.Int128Parts - - // if funderIdx { - - // amountInt128 = balsStellar.BalB - // if err != nil { - // return errors.New("error while making int128 parts on index 1") - // } - - // } else { - // amountInt128 = balsStellar.BalA - // if err != nil { - // return errors.New("error while making int128 parts on index 0") - // } - // } - - // amountBalsScv, err := scval.WrapInt128Parts(amountInt128) - // if err != nil { - // return errors.New("error while wrapping int128 parts") - // } - - // stellarAddr, err := types.MakeAccountAddress(kp) - // if err != nil { - // return errors.New("error while making account address") - // } - // scClientAddr := scval.MustWrapScAddress(stellarAddr) - // scPerunAddr := scval.MustWrapScAddress(perunContractAddress) - // transferArgs := xdr.ScVec{scClientAddr, scPerunAddr, amountBalsScv} - - // transfer here to perun, try if it works - - tokenAddr := tokenContractAddress //balsStellar.Token - - // authTransfer := xdr.SorobanAuthorizedInvocation{ - // Function: xdr.SorobanAuthorizedFunction{ - // Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, - // ContractFn: &xdr.InvokeContractArgs{ - // ContractAddress: tokenContractAddress, - // FunctionName: "transfer", - // Args: transferArgs, - // }, - // }, - // SubInvocations: nil, - // } - - // fundRootInv := xdr.SorobanAuthorizedInvocation{ - // Function: xdr.SorobanAuthorizedFunction{ - // Type: xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, - // ContractFn: &xdr.InvokeContractArgs{ - // ContractAddress: perunContractAddress, - // FunctionName: "fund", - // Args: fundTxArgs, - // }, - // }, - // SubInvocations: []xdr.SorobanAuthorizedInvocation{}, //authTransfer - // } - // pphrase := f.stellarClient.GetPassPhrase() - - // preimg, err := makePreImgAuth(pphrase, fundRootInv) - // if err != nil { - // panic(err) - // } - - // preimgMarshaled, err := preimg.MarshalBinary() - // if err != nil { - // panic(err) - // } - - // hashSign, err := kp.Sign(preimgMarshaled) - // if err != nil { - // panic(err) - // } - - // hashScVal, err := scval.WrapScBytes(hashSign) - // if err != nil { - // panic(err) - // } - - // srbAddrCreds := xdr.SorobanAddressCredentials{ - // Address: stellarAddr, - // Nonce: preimg.SorobanAuthorization.Nonce, - // SignatureExpirationLedger: preimg.SorobanAuthorization.SignatureExpirationLedger, - // Signature: hashScVal, - // } - - // srbCreds := xdr.SorobanCredentials{Address: &srbAddrCreds, - // Type: xdr.SorobanCredentialsTypeSorobanCredentialsAddress} - - // authFundClx := []xdr.SorobanAuthorizationEntry{ - // { - // Credentials: srbCreds, - // RootInvocation: fundRootInv, - // }, - // } - + tokenAddr := tokenContractAddress tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) testArgs := xdr.ScVec{tokenAddrXdr} From 447b0b476a78d59ae841aacfb8bef1b5e2c50a01 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 12 Dec 2023 19:46:43 +0100 Subject: [PATCH 11/51] quickstart.sh: Add simple quickstart initialization of Stellar and Soroban --- quickstart.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 quickstart.sh diff --git a/quickstart.sh b/quickstart.sh new file mode 100755 index 0000000..424fb6c --- /dev/null +++ b/quickstart.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +docker run --rm -it \ + -p "8000:8000" \ + --name stellar \ + stellar/quickstart:soroban-dev \ + --testnet \ + --enable-soroban-rpc \ No newline at end of file From 297a804d759c441442dc94d5e7974136002098b2 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Sat, 16 Dec 2023 15:24:12 +0100 Subject: [PATCH 12/51] Adapt to most recent stellar/go version env: Keep simulateTransaction function from integration test environment --- channel/adjudicator.go | 142 +++++++-- channel/adjudicator_sub.go | 29 +- channel/adjudicator_test.go | 238 --------------- channel/env/client.go | 107 +++---- channel/env/integration.go | 177 ----------- channel/env/transaction.go | 281 +++++++++++++++--- channel/event.go | 10 - channel/funder.go | 114 +++---- channel/funder_test.go | 112 ------- channel/subscribe.go | 12 +- channel/test/fund.go | 34 +-- channel/token.go | 253 ++++++++-------- client/client.go | 24 +- go.mod | 82 +---- go.sum | 252 +++------------- main.go | 108 ++----- quickstart.sh | 72 ++++- services/horizon/docker/Dockerfile | 21 -- services/horizon/docker/Dockerfile.dev | 28 -- services/horizon/docker/Makefile | 32 -- ...captive-core-classic-integration-tests.cfg | 13 - .../docker/captive-core-integration-tests.cfg | 15 - ...ive-core-integration-tests.soroban-rpc.cfg | 14 - ...ingest-range-classic-integration-tests.cfg | 9 - ...-core-reingest-range-integration-tests.cfg | 10 - .../docker/captive-core-standalone.cfg | 12 - services/horizon/docker/core-start.sh | 29 -- ...-compose.integration-tests.soroban-rpc.yml | 20 -- .../docker-compose.integration-tests.yml | 32 -- .../horizon/docker/docker-compose.pubnet.yml | 6 - .../docker/docker-compose.standalone.yml | 53 ---- services/horizon/docker/docker-compose.yml | 38 --- services/horizon/docker/start.sh | 28 -- ...stellar-core-classic-integration-tests.cfg | 24 -- .../docker/stellar-core-integration-tests.cfg | 25 -- .../horizon/docker/stellar-core-pubnet.cfg | 202 ------------- .../docker/stellar-core-standalone.cfg | 25 -- .../horizon/docker/stellar-core-testnet.cfg | 41 --- testdata/perun_soroban_contract.wasm | Bin 23908 -> 24262 bytes testdata/perun_soroban_token.wasm | Bin 7620 -> 7562 bytes util/deploy.go | 59 ++++ util/util.go | 215 ++++++++++++-- wire/invocation.go | 36 +++ 43 files changed, 1099 insertions(+), 1935 deletions(-) delete mode 100644 channel/adjudicator_test.go delete mode 100644 channel/env/integration.go delete mode 100644 channel/funder_test.go delete mode 100644 services/horizon/docker/Dockerfile delete mode 100644 services/horizon/docker/Dockerfile.dev delete mode 100644 services/horizon/docker/Makefile delete mode 100644 services/horizon/docker/captive-core-classic-integration-tests.cfg delete mode 100644 services/horizon/docker/captive-core-integration-tests.cfg delete mode 100644 services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg delete mode 100644 services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg delete mode 100644 services/horizon/docker/captive-core-reingest-range-integration-tests.cfg delete mode 100644 services/horizon/docker/captive-core-standalone.cfg delete mode 100755 services/horizon/docker/core-start.sh delete mode 100644 services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml delete mode 100644 services/horizon/docker/docker-compose.integration-tests.yml delete mode 100644 services/horizon/docker/docker-compose.pubnet.yml delete mode 100644 services/horizon/docker/docker-compose.standalone.yml delete mode 100644 services/horizon/docker/docker-compose.yml delete mode 100755 services/horizon/docker/start.sh delete mode 100644 services/horizon/docker/stellar-core-classic-integration-tests.cfg delete mode 100644 services/horizon/docker/stellar-core-integration-tests.cfg delete mode 100644 services/horizon/docker/stellar-core-pubnet.cfg delete mode 100644 services/horizon/docker/stellar-core-standalone.cfg delete mode 100644 services/horizon/docker/stellar-core-testnet.cfg create mode 100644 util/deploy.go create mode 100644 wire/invocation.go diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 1b34a68..7aa31de 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" "perun.network/go-perun/log" @@ -13,52 +14,69 @@ import ( "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/wallet" + "time" + "perun.network/perun-stellar-backend/wire" "perun.network/perun-stellar-backend/wire/scval" - "time" ) -var ErrChannelAlreadyClosed = errors.New("nonce values was out of range") +var ErrChannelAlreadyClosed = errors.New("Channel is already closed") type Adjudicator struct { log log.Embedding stellarClient *env.StellarClient acc *wallet.Account kpFull *keypair.Full + assetID xdr.ScAddress + perunID xdr.ScAddress maxIters int pollingInterval time.Duration } // NewAdjudicator returns a new Adjudicator. -func NewAdjudicator(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient) *Adjudicator { +func NewAdjudicator(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *Adjudicator { return &Adjudicator{ stellarClient: stellarClient, acc: acc, kpFull: kp, + perunID: perunID, + assetID: assetID, maxIters: MaxIterationsUntilAbort, pollingInterval: DefaultPollingInterval, log: log.MakeEmbedding(log.Default()), } } +func (a *Adjudicator) GetPerunID() xdr.ScAddress { + return a.perunID +} + +func (a *Adjudicator) GetAssetID() xdr.ScAddress { + return a.assetID +} + func (a *Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel.AdjudicatorSubscription, error) { c := a.stellarClient - return NewAdjudicatorSub(ctx, cid, c), nil + perunID := a.GetPerunID() + assetID := a.GetAssetID() + return NewAdjudicatorSub(ctx, cid, c, perunID, assetID), nil } func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { + // cid := req.Tx.State.ID + if req.Tx.State.IsFinal { log.Println("Withdraw called") - err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) + err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs) if err != nil { - getChanArgs, err := env.BuildGetChannelTxArgs(req.Tx.ID) + // getChanArgs, err := env.BuildGetChannelTxArgs(req.Tx.ID) if err != nil { panic(err) } - chanControl, err := a.stellarClient.GetChannelState(getChanArgs) + chanControl, err := a.GetChannelState(ctx, req.Tx.State) if err != nil { return err } @@ -71,12 +89,11 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, if err != nil { return err } - //... after the event has arrived, we conclude return a.withdraw(ctx, req) } else { err := a.ForceClose(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) - fmt.Println("ForceClose called") + log.Println("ForceClose called") if err != nil { if err == ErrChannelAlreadyClosed { return a.withdraw(ctx, req) @@ -121,6 +138,33 @@ loop: } } +func (a *Adjudicator) GetChannelState(ctx context.Context, state *pchannel.State) (wire.Channel, error) { + + contractAddress := a.GetPerunID() + kp := a.kpFull + chanId := state.ID + + // generate tx to open the channel + getchTxArgs, err := env.BuildGetChannelTxArgs(chanId) + if err != nil { + return wire.Channel{}, errors.New("error while building get_channel tx") + } + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp, auth) + if err != nil { + return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") + } + + retVal := txMeta.V3.SorobanMeta.ReturnValue + var getChan wire.Channel + + err = getChan.FromScVal(retVal) + if err != nil { + return wire.Channel{}, errors.New("error while decoding return value") + } + return getChan, nil +} + func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVec, error) { // build withdrawalargs @@ -151,9 +195,9 @@ func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVe func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) error { - contractAddress := a.stellarClient.GetPerunAddress() + perunAddress := a.GetPerunID() kp := a.kpFull - hzAcc := a.stellarClient.GetHorizonAcc() + // hzAcc := a.stellarClient.GetHorizonAcc() // generate tx to open the channel withdrawTxArgs, err := a.BuildWithdrawTxArgs(req) @@ -161,7 +205,7 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) return errors.New("error while building fund tx") } auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "withdraw", withdrawTxArgs, contractAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("withdraw", withdrawTxArgs, perunAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: withdraw") } @@ -174,17 +218,17 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) return nil } -func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { - fmt.Println("Close called") - contractAddress := a.stellarClient.GetPerunAddress() +func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig) error { + log.Println("Close called") + contractAddress := a.GetPerunID() kp := a.kpFull - hzAcc := a.stellarClient.GetHorizonAcc() + // hzAcc := a.stellarClient.GetHorizonAcc() closeTxArgs, err := BuildCloseTxArgs(*state, sigs) if err != nil { return errors.New("error while building fund tx") } auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "close", closeTxArgs, contractAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("close", closeTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: close") } @@ -197,20 +241,46 @@ func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel // Register registers and disputes a channel. func (a *Adjudicator) Register(ctx context.Context, req pchannel.AdjudicatorReq, states []pchannel.SignedState) error { - panic("implement me") + log.Println("Register called") + sigs := req.Tx.Sigs + state := req.Tx.State + err := a.Dispute(ctx, state, sigs) + if err != nil { + return fmt.Errorf("error while disputing channel: %w", err) + } + return nil +} + +func (a *Adjudicator) Dispute(ctx context.Context, state *pchannel.State, sigs []pwallet.Sig) error { + contractAddress := a.GetPerunID() + kp := a.kpFull + // hzAcc := a.stellarClient.GetHorizonAcc() + closeTxArgs, err := BuildDisputeTxArgs(*state, sigs) + if err != nil { + return errors.New("error while building fund tx") + } + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("dispute", closeTxArgs, contractAddress, kp, auth) + if err != nil { + return errors.New("error while invoking and processing host function: dispute") + } + _, err = DecodeEventsPerun(txMeta) + if err != nil { + return errors.New("error while decoding events") + } + return nil } func (a *Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { - fmt.Println("ForceClose called") - contractAddress := a.stellarClient.GetPerunAddress() + log.Println("ForceClose called") + contractAddress := a.GetPerunID() kp := a.kpFull - hzAcc := a.stellarClient.GetHorizonAcc() forceCloseTxArgs, err := env.BuildForceCloseTxArgs(id) if err != nil { return errors.New("error while building fund tx") } auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction(hzAcc, "force_close", forceCloseTxArgs, contractAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("force_close", forceCloseTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function") } @@ -249,6 +319,34 @@ func BuildCloseTxArgs(state pchannel.State, sigs []pwallet.Sig) (xdr.ScVec, erro return fundArgs, nil } +func BuildDisputeTxArgs(state pchannel.State, sigs []pwallet.Sig) (xdr.ScVec, error) { + + wireState, err := wire.MakeState(state) + if err != nil { + return xdr.ScVec{}, err + } + + sigAXdr, err := scval.WrapScBytes(sigs[0]) + if err != nil { + return xdr.ScVec{}, err + } + sigBXdr, err := scval.WrapScBytes(sigs[1]) + if err != nil { + return xdr.ScVec{}, err + } + xdrState, err := wireState.ToScVal() + if err != nil { + return xdr.ScVec{}, err + } + + fundArgs := xdr.ScVec{ + xdrState, + sigAXdr, + sigBXdr, + } + return fundArgs, nil +} + func (a Adjudicator) Progress(ctx context.Context, req pchannel.ProgressReq) error { // only relevant for AppChannels return nil diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index 5eb37e7..d295476 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -33,6 +33,8 @@ type AdjEventSub struct { stellarClient *env.StellarClient chanControl wire.Control cid pchannel.ID + perunID xdr.ScAddress + assetID xdr.ScAddress events chan AdjEvent Ev []AdjEvent err error @@ -43,7 +45,7 @@ type AdjEventSub struct { log log.Embedding } -func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env.StellarClient) *AdjEventSub { +func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *AdjEventSub { getChanArgs, err := env.BuildGetChannelTxArgs(cid) if err != nil { panic(err) @@ -54,6 +56,8 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. stellarClient: stellarClient, chanControl: wire.Control{}, cid: cid, + perunID: perunID, + assetID: assetID, events: make(chan AdjEvent, DefaultBufferSize), Ev: make([]AdjEvent, 0), panicErr: make(chan error, 1), @@ -118,6 +122,27 @@ polling: } } +func (s *AdjEventSub) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) { + contractAddress := s.perunID + kp := s.stellarClient.GetKeyPair() + // hz := s.GetHorizonAcc() + auth := []xdr.SorobanAuthorizationEntry{} + txMeta, err := s.stellarClient.InvokeAndProcessHostFunction("get_channel", chanArgs, contractAddress, kp, auth) + if err != nil { + return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") + } + + retVal := txMeta.V3.SorobanMeta.ReturnValue + var getChan wire.Channel + + err = getChan.FromScVal(retVal) + if err != nil { + return wire.Channel{}, errors.New("error while decoding return value") + } + return getChan, nil + +} + func (s *AdjEventSub) getChanControl() (wire.Control, error) { // query channel state @@ -126,7 +151,7 @@ func (s *AdjEventSub) getChanControl() (wire.Control, error) { return wire.Control{}, err } - chanParams, err := s.stellarClient.GetChannelState(getChanArgs) + chanParams, err := s.GetChannelState(getChanArgs) if err != nil { return wire.Control{}, err } diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go deleted file mode 100644 index 5e79fb1..0000000 --- a/channel/adjudicator_test.go +++ /dev/null @@ -1,238 +0,0 @@ -package channel_test - -import ( - "context" - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/require" - pchannel "perun.network/go-perun/channel" - pwallet "perun.network/go-perun/wallet" - - chtest "perun.network/perun-stellar-backend/channel/test" - "perun.network/perun-stellar-backend/util" - - _ "perun.network/perun-stellar-backend/wallet/test" - "perun.network/perun-stellar-backend/wire" - - "perun.network/perun-stellar-backend/channel" - "perun.network/perun-stellar-backend/channel/env" - - "testing" -) - -func TestCloseChannel(t *testing.T) { - itest := env.NewBackendEnv() - - kps, _ := itest.CreateAccounts(3, "100000000") - - kpAlice := kps[0] - kpBob := kps[1] - kpDeployer := kps[2] - reqDeployer := itest.AccountDetails(kpDeployer) - - contractAddr, _ := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) - itest.SetPerunAddress(contractAddr) - - _, accAlice, _ := util.MakeRandPerunWallet() - _, accBob, _ := util.MakeRandPerunWallet() - addrAlice := accAlice.Address() - addrBob := accBob.Address() - addrList := []pwallet.Address{addrAlice, addrBob} - perunFirstParams, perunFirstState := chtest.NewParamsWithAddressState(t, addrList) - - freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) - freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) - freqs := []*pchannel.FundingReq{freqAlice, freqBob} - - // Creating the client and funders as pointers - aliceClient := env.NewStellarClient(itest, kpAlice) - bobClient := env.NewStellarClient(itest, kpBob) - - aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) - bobFunder := channel.NewFunder(accBob, kpBob, bobClient) - funders := []*channel.Funder{aliceFunder, bobFunder} - chanID := perunFirstParams.ID() - - err := chtest.FundAll(context.TODO(), funders, freqs) - require.NoError(t, err) - - hzAliceGetCh := aliceClient.GetHorizonAcc() - - getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) - require.NoError(t, err) - - auth := []xdr.SorobanAuthorizationEntry{} - txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice, auth) - require.NoError(t, err) - - retVal := txMetaGetChAfterFunding.V3.SorobanMeta.ReturnValue - - var getChanAfterFunding wire.Channel - - err = getChanAfterFunding.FromScVal(retVal) - require.NoError(t, err) - - // close the channel - - currStellarState := getChanAfterFunding.State - - currStellarState.Finalized = true - - currPerunState, err := wire.ToState(currStellarState) - - require.NoError(t, err) - - sigA, err := channel.Backend.Sign(accAlice, &currPerunState) - require.NoError(t, err) - - sigB, err := channel.Backend.Sign(accBob, &currPerunState) - require.NoError(t, err) - - sigs := []pwallet.Sig{sigA, sigB} - - closeArgs, err := channel.BuildCloseTxArgs(currPerunState, sigs) - require.NoError(t, err) - hzAliceGClose := aliceClient.GetHorizonAcc() - - auth = []xdr.SorobanAuthorizationEntry{} - invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr, auth) - - preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&hzAliceGClose, *invokeHostFunctionOpClose) - - txClose, err := itest.SubmitOperationsWithFee(&hzAliceGClose, kpAlice, minFeeClose, &preFlightOpClose) - - require.NoError(t, err) - - txMetaClose, err := env.DecodeTxMeta(txClose) - - require.NoError(t, err) - - _, err = channel.DecodeEventsPerun(txMetaClose) - - require.NoError(t, err) - -} - -func TestWithdrawChannel(t *testing.T) { - itest := env.NewBackendEnv() - - kps, _ := itest.CreateAccounts(3, "100000000") - - kpAlice := kps[0] - kpBob := kps[1] - kpDeployer := kps[2] - - reqDeployer := itest.AccountDetails(kpDeployer) - - contractAddr, _ := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) - itest.SetPerunAddress(contractAddr) - - _, accAlice, _ := util.MakeRandPerunWallet() - _, accBob, _ := util.MakeRandPerunWallet() - - addrAlice := accAlice.Address() - addrBob := accBob.Address() - addrList := []pwallet.Address{addrAlice, addrBob} - perunFirstParams, perunFirstState := chtest.NewParamsWithAddressState(t, addrList) - - freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) - freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) - freqs := []*pchannel.FundingReq{freqAlice, freqBob} - - // Creating the client and funders as pointers - aliceClient := env.NewStellarClient(itest, kpAlice) - bobClient := env.NewStellarClient(itest, kpBob) - - aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) - bobFunder := channel.NewFunder(accBob, kpBob, bobClient) - funders := []*channel.Funder{aliceFunder, bobFunder} - chanID := perunFirstParams.ID() - - // Calling the function - err := chtest.FundAll(context.TODO(), funders, freqs) - require.NoError(t, err) - - hzAliceGetCh := aliceClient.GetHorizonAcc() - - getChannelArgs, err := env.BuildGetChannelTxArgs(chanID) - require.NoError(t, err) - - auth := []xdr.SorobanAuthorizationEntry{} - txMetaGetChAfterFunding, err := aliceClient.InvokeAndProcessHostFunction(hzAliceGetCh, "get_channel", getChannelArgs, contractAddr, kpAlice, auth) - require.NoError(t, err) - - retVal := txMetaGetChAfterFunding.V3.SorobanMeta.ReturnValue - - var getChanAfterFunding wire.Channel - - err = getChanAfterFunding.FromScVal(retVal) - require.NoError(t, err) - - // close the channel - - currStellarState := getChanAfterFunding.State - - currStellarState.Finalized = true - - currPerunState, err := wire.ToState(currStellarState) - - require.NoError(t, err) - - sigA, err := channel.Backend.Sign(accAlice, &currPerunState) - require.NoError(t, err) - - sigB, err := channel.Backend.Sign(accBob, &currPerunState) - require.NoError(t, err) - - sigs := []pwallet.Sig{sigA, sigB} - - closeArgs, err := channel.BuildCloseTxArgs(currPerunState, sigs) - require.NoError(t, err) - hzAliceGClose := aliceClient.GetHorizonAcc() - - auth = []xdr.SorobanAuthorizationEntry{} - invokeHostFunctionOpClose := env.BuildContractCallOp(hzAliceGClose, "close", closeArgs, contractAddr, auth) - - preFlightOpClose, minFeeClose := itest.PreflightHostFunctions(&hzAliceGClose, *invokeHostFunctionOpClose) - - txClose, err := itest.SubmitOperationsWithFee(&hzAliceGClose, kpAlice, minFeeClose, &preFlightOpClose) - - require.NoError(t, err) - - txMetaClose, err := env.DecodeTxMeta(txClose) - - require.NoError(t, err) - - _, err = channel.DecodeEventsPerun(txMetaClose) - require.NoError(t, err) - - hzAliceWithdraw := aliceClient.GetHorizonAcc() - waArgs, err := env.BuildFundTxArgs(chanID, false) - require.NoError(t, err) - auth = []xdr.SorobanAuthorizationEntry{} - invokeHostFunctionOpWA := env.BuildContractCallOp(hzAliceWithdraw, "withdraw", waArgs, contractAddr, auth) - preFlightOpWA, minFeeWA := itest.PreflightHostFunctions(&hzAliceWithdraw, *invokeHostFunctionOpWA) - txWithdrawAlice, err := itest.SubmitOperationsWithFee(&hzAliceWithdraw, kpAlice, minFeeWA, &preFlightOpWA) - require.NoError(t, err) - - txMetaWA, err := env.DecodeTxMeta(txWithdrawAlice) - - require.NoError(t, err) - _, err = channel.DecodeEventsPerun(txMetaWA) - require.NoError(t, err) - - hzBobWithdraw := bobClient.GetHorizonAcc() - wbArgs, err := env.BuildFundTxArgs(chanID, true) - require.NoError(t, err) - auth = []xdr.SorobanAuthorizationEntry{} - invokeHostFunctionOpWB := env.BuildContractCallOp(hzBobWithdraw, "withdraw", wbArgs, contractAddr, auth) - preFlightOpWB, minFeeWB := itest.PreflightHostFunctions(&hzBobWithdraw, *invokeHostFunctionOpWB) - txWithdrawBob, err := itest.SubmitOperationsWithFee(&hzBobWithdraw, kpBob, minFeeWB, &preFlightOpWB) - require.NoError(t, err) - - txMetaWB, err := env.DecodeTxMeta(txWithdrawBob) - - require.NoError(t, err) - _, err = channel.DecodeEventsPerun(txMetaWB) - require.NoError(t, err) - -} diff --git a/channel/env/client.go b/channel/env/client.go index f3427ec..4cd8ddd 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -1,95 +1,76 @@ package env import ( - "errors" + "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/txnbuild" - "github.com/stellar/go/xdr" - "perun.network/perun-stellar-backend/wire" + // "github.com/stellar/go/txnbuild" + // "log" ) +const HorizonURL = "http://localhost:8000" +const NETWORK_PASSPHRASE = "Standalone Network ; February 2017" + +type HorizonMasterClient struct { + master *horizonclient.Client + sourceKey *keypair.Full +} + type StellarClient struct { - stellarEnv *IntegrationTestEnv - kp *keypair.Full - passphrase string - perunAddress xdr.ScAddress - tokenAddress xdr.ScAddress + hzClient *horizonclient.Client + kp *keypair.Full + // clientKey *keypair.Full } -func NewStellarClient(stellarEnv *IntegrationTestEnv, kp *keypair.Full) *StellarClient { - passphrase := stellarEnv.GetPassPhrase() +func NewHorizonClient() *horizonclient.Client { + return &horizonclient.Client{HorizonURL: HorizonURL} +} + +func NewStellarClient(kp *keypair.Full) *StellarClient { return &StellarClient{ - stellarEnv: stellarEnv, - kp: kp, - passphrase: passphrase, - perunAddress: stellarEnv.perunAddress, - tokenAddress: stellarEnv.tokenAddress, + hzClient: NewHorizonClient(), + kp: kp, } - } -func (s *StellarClient) GetAccount() *keypair.Full { +func (s *StellarClient) GetKeyPair() *keypair.Full { return s.kp } -func (s *StellarClient) GetHorizonAcc() horizon.Account { - return s.stellarEnv.AccountDetails(s.kp) -} -func (s *StellarClient) GetPassPhrase() string { - return s.passphrase +func NewHorizonMasterClient() *HorizonMasterClient { + sourceKey := keypair.Root(NETWORK_PASSPHRASE) + return &HorizonMasterClient{ + master: &horizonclient.Client{HorizonURL: HorizonURL}, + sourceKey: sourceKey, + } } -func (s *StellarClient) GetPerunAddress() xdr.ScAddress { - return s.perunAddress +func (m *HorizonMasterClient) GetMaster() *horizonclient.Client { + return m.master } -func (s *StellarClient) GetTokenAddress() xdr.ScAddress { - return s.tokenAddress +func (m *HorizonMasterClient) GetSourceKey() *keypair.Full { + return m.sourceKey } -func (s *StellarClient) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full, auth []xdr.SorobanAuthorizationEntry) (xdr.TransactionMeta, error) { - - // Build contract call operation - fnameXdr := xdr.ScSymbol(fname) - - invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr, auth) - - preFlightOp, minFee := s.stellarEnv.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) +func (c *StellarClient) GetHorizonClient() *horizonclient.Client { + return c.hzClient +} - tx, err := s.stellarEnv.SubmitOperationsWithFee(&horizonAcc, kp, minFee+txnbuild.MinBaseFee, &preFlightOp) +func (h *HorizonMasterClient) GetAccount(kp *keypair.Full) horizon.Account { + accountReq := horizonclient.AccountRequest{AccountID: kp.Address()} + hzAccount, err := h.master.AccountDetail(accountReq) if err != nil { panic(err) } - - // fmt.Println("tx from ", fname, ": ", tx) - - // Decode transaction metadata - txMeta, err := DecodeTxMeta(tx) - if err != nil { - return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") - } - - return txMeta, nil + return hzAccount } -func (s *StellarClient) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) { - contractAddress := s.stellarEnv.GetPerunAddress() - kp := s.kp - hz := s.GetHorizonAcc() - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := s.InvokeAndProcessHostFunction(hz, "get_channel", chanArgs, contractAddress, kp, auth) +func (s *StellarClient) GetHorizonAccount(kp *keypair.Full) horizon.Account { + accountReq := horizonclient.AccountRequest{AccountID: kp.Address()} + hzAccount, err := s.hzClient.AccountDetail(accountReq) if err != nil { - return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") - } - - retVal := txMeta.V3.SorobanMeta.ReturnValue - var getChan wire.Channel - - err = getChan.FromScVal(retVal) - if err != nil { - return wire.Channel{}, errors.New("error while decoding return value") + panic(err) } - return getChan, nil - + return hzAccount } diff --git a/channel/env/integration.go b/channel/env/integration.go deleted file mode 100644 index 4f4dda6..0000000 --- a/channel/env/integration.go +++ /dev/null @@ -1,177 +0,0 @@ -package env - -import ( - "errors" - "github.com/stellar/go/amount" - "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - "github.com/stellar/go/protocols/horizon" - testenv "github.com/stellar/go/services/horizon/pkg/test/integration" - "github.com/stellar/go/txnbuild" - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/assert" - "testing" -) - -const PtotocolVersion = 20 -const EnableSorobanRPC = true -const StandaloneAuth = "Standalone Network ; February 2017" -const SorobanRPCPort = 8080 - -type IntegrationTestEnv struct { - testEnv *testenv.Test - perunAddress xdr.ScAddress - tokenAddress xdr.ScAddress -} - -func NewBackendEnv() *IntegrationTestEnv { - t := &testing.T{} - - cfg := testenv.Config{ - ProtocolVersion: PtotocolVersion, - EnableSorobanRPC: EnableSorobanRPC, - HorizonEnvironment: map[string]string{"INGEST_DISABLE_STATE_VERIFICATION": "true", "CONNECTION_TIMEOUT": "360000"}, - } - itest := IntegrationTestEnv{testEnv: testenv.NewTest(t, cfg)} - return &itest -} - -func NewIntegrationEnv(t *testing.T) *IntegrationTestEnv { - cfg := testenv.Config{ProtocolVersion: PtotocolVersion, - EnableSorobanRPC: EnableSorobanRPC, - } - itest := IntegrationTestEnv{testEnv: testenv.NewTest(t, cfg)} - return &itest -} - -func (it *IntegrationTestEnv) SetPerunAddress(perunAddress xdr.ScAddress) { - it.perunAddress = perunAddress -} - -func (it *IntegrationTestEnv) SetTokenAddress(tokenAddress xdr.ScAddress) { - it.tokenAddress = tokenAddress -} - -func (it *IntegrationTestEnv) CreateAccounts(numAccounts int, initBalance string) ([]*keypair.Full, []txnbuild.Account) { - kps, accs := it.testEnv.CreateAccounts(numAccounts, initBalance) - - return kps, accs -} - -func (it *IntegrationTestEnv) CreateAccount(initialBalance string) (*keypair.Full, txnbuild.Account) { - kps, accts := it.CreateAccounts(1, initialBalance) - return kps[0], accts[0] -} - -func (it *IntegrationTestEnv) CurrentTest() *testing.T { - return it.testEnv.CurrentTest() -} - -func (it *IntegrationTestEnv) Master() *keypair.Full { - return it.testEnv.Master() -} - -func (it *IntegrationTestEnv) AccountDetails(acc *keypair.Full) horizon.Account { - accountReq := horizonclient.AccountRequest{AccountID: acc.Address()} - hzAccount, err := it.testEnv.Client().AccountDetail(accountReq) - if err != nil { - panic(err) - } - return hzAccount -} - -func (it *IntegrationTestEnv) PreflightHostFunctions(sourceAccount txnbuild.Account, function txnbuild.InvokeHostFunction) (txnbuild.InvokeHostFunction, int64) { - return it.testEnv.PreflightHostFunctions(sourceAccount, function) -} - -func (it *IntegrationTestEnv) MustSubmitOperationsWithFee( - source txnbuild.Account, - signer *keypair.Full, - fee int64, - ops ...txnbuild.Operation, -) horizon.Transaction { - return it.testEnv.MustSubmitOperationsWithFee(source, signer, fee, ops...) -} - -func (it *IntegrationTestEnv) SubmitOperationsWithFee( - source txnbuild.Account, - signer *keypair.Full, - fee int64, - ops ...txnbuild.Operation, -) (horizon.Transaction, error) { - return it.testEnv.SubmitOperationsWithFee(source, signer, fee, ops...) -} - -func (it *IntegrationTestEnv) Client() *horizonclient.Client { - return it.testEnv.Client() -} - -func (it *IntegrationTestEnv) GetPassPhrase() string { - return it.testEnv.GetPassPhrase() -} - -func (it *IntegrationTestEnv) GetPerunAddress() xdr.ScAddress { - return it.perunAddress -} - -func AssertContainsBalance(it *IntegrationTestEnv, acct *keypair.Full, issuer, code string, amt xdr.Int64) { - accountResponse := it.testEnv.MustGetAccount(acct) - if issuer == "" && code == "" { - xlmBalance, err := accountResponse.GetNativeBalance() - if err != nil { - panic(err) - } - assert.NoError(it.testEnv.CurrentTest(), err) - assert.Equal(it.testEnv.CurrentTest(), amt, amount.MustParse(xlmBalance)) - } else { - assetBalance := accountResponse.GetCreditBalance(code, issuer) - assert.Equal(it.testEnv.CurrentTest(), amt, amount.MustParse(assetBalance)) - } -} - -func (it *IntegrationTestEnv) InvokeAndProcessHostFunction(horizonAcc horizon.Account, fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { - // Build contract call operation - fnameXdr := xdr.ScSymbol(fname) - auth := []xdr.SorobanAuthorizationEntry{} - invokeHostFunctionOp := BuildContractCallOp(horizonAcc, fnameXdr, callTxArgs, contractAddr, auth) - - // Preflight host functions - preFlightOp, minFee := it.PreflightHostFunctions(&horizonAcc, *invokeHostFunctionOp) - - // Submit operations with fee - tx, err := it.SubmitOperationsWithFee(&horizonAcc, kp, minFee, &preFlightOp) - if err != nil { - return xdr.TransactionMeta{}, errors.New("error while submitting operations with fee") - } - - // Decode transaction metadata - txMeta, err := DecodeTxMeta(tx) - if err != nil { - return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") - } - - return txMeta, nil -} - -func (it *IntegrationTestEnv) SubmitOperations(source txnbuild.Account, signer *keypair.Full, ops ...txnbuild.Operation) (horizon.Transaction, error) { - return it.testEnv.SubmitOperations(source, signer, ops...) -} - -func (it *IntegrationTestEnv) MustGetAccount(source *keypair.Full) horizon.Account { - client := it.Client() - account, err := client.AccountDetail(horizonclient.AccountRequest{AccountID: source.Address()}) - if err != nil { - panic(err) - } - return account -} - -func (it *IntegrationTestEnv) MustEstablishTrustline( - truster *keypair.Full, account txnbuild.Account, asset txnbuild.Asset, -) (resp horizon.Transaction) { - txResp, err := it.testEnv.EstablishTrustline(truster, account, asset) - if err != nil { - panic(err) - } - return txResp -} diff --git a/channel/env/transaction.go b/channel/env/transaction.go index 90045f5..f34f13f 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -1,28 +1,183 @@ package env import ( + "context" + "errors" + "fmt" + "github.com/2opremio/pretty" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/jhttp" + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/wire" "perun.network/perun-stellar-backend/wire/scval" + + // "perun.network/perun-stellar-backend/util" + + "strconv" + "time" ) -func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xdr.ScVec, contractIdAddress xdr.ScAddress, auth []xdr.SorobanAuthorizationEntry) *txnbuild.InvokeHostFunction { +const sorobanRPCPort = 8000 - return &txnbuild.InvokeHostFunction{ - HostFunction: xdr.HostFunction{ - Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, - InvokeContract: &xdr.InvokeContractArgs{ - ContractAddress: contractIdAddress, - FunctionName: fName, - Args: callArgs, - }, - }, - Auth: auth, - SourceAccount: caller.AccountID, +type RPCSimulateTxResponse struct { + Error string `json:"error,omitempty"` + TransactionData string `json:"transactionData"` + Results []RPCSimulateHostFunctionResult `json:"results"` + MinResourceFee int64 `json:"minResourceFee,string"` +} + +type RPCSimulateHostFunctionResult struct { + Auth []string `json:"auth"` + XDR string `json:"xdr"` +} + +func PreflightHostFunctions(hzClient *horizonclient.Client, + sourceAccount txnbuild.Account, function txnbuild.InvokeHostFunction, +) (txnbuild.InvokeHostFunction, int64) { + fmt.Println("Preflighting function call of type:", function.HostFunction.Type) + if function.HostFunction.Type == xdr.HostFunctionTypeHostFunctionTypeInvokeContract { + fmt.Printf("Preflighting function call to: %s\n", string(function.HostFunction.InvokeContract.FunctionName)) + } + result, transactionData := simulateTransaction(hzClient, sourceAccount, &function) + + if function.HostFunction.Type == xdr.HostFunctionTypeHostFunctionTypeInvokeContract { + fmt.Println("Preflight result in function: ", string(function.HostFunction.InvokeContract.FunctionName), ": ", result) + + } + + function.Ext = xdr.TransactionExt{ + V: 1, + SorobanData: &transactionData, + } + var funAuth []xdr.SorobanAuthorizationEntry + for _, res := range result.Results { + fmt.Println("Enter range loop") + var decodedRes xdr.ScVal + err := xdr.SafeUnmarshalBase64(res.XDR, &decodedRes) + if err != nil { + panic(err) + } + fmt.Printf("Result:\n\n%# +v\n\n", pretty.Formatter(decodedRes)) + for _, authBase64 := range res.Auth { + var authEntry xdr.SorobanAuthorizationEntry + err = xdr.SafeUnmarshalBase64(authBase64, &authEntry) + if err != nil { + panic(err) + } + fmt.Printf("Auth:\n\n%# +v\n\n", pretty.Formatter(authEntry)) + funAuth = append(funAuth, authEntry) + } } + function.Auth = funAuth + + return function, result.MinResourceFee +} + +func simulateTransaction(hzClient *horizonclient.Client, + sourceAccount txnbuild.Account, op txnbuild.Operation, +) (RPCSimulateTxResponse, xdr.SorobanTransactionData) { + // Before preflighting, make sure soroban-rpc is in sync with Horizon + root, err := hzClient.Root() + if err != nil { + panic(err) + } + syncWithSorobanRPC(uint32(root.HorizonSequence)) + + // TODO: soroban-tools should be exporting a proper Go client + ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(sorobanRPCPort)+"/soroban/rpc", nil) + sorobanRPCClient := jrpc2.NewClient(ch, nil) + txParams := GetBaseTransactionParamsWithFee(sourceAccount, txnbuild.MinBaseFee, op) + txParams.IncrementSequenceNum = false + tx, err := txnbuild.NewTransaction(txParams) + if err != nil { + panic(err) + } + base64, err := tx.Base64() + if err != nil { + panic(err) + } + result := RPCSimulateTxResponse{} + fmt.Printf("Preflight TX:\n\n%v \n\n", base64) + err = sorobanRPCClient.CallResult(context.Background(), "simulateTransaction", struct { + Transaction string `json:"transaction"` + }{base64}, &result) + if err != nil { + panic(err) + } + var transactionData xdr.SorobanTransactionData + err = xdr.SafeUnmarshalBase64(result.TransactionData, &transactionData) + if err != nil { + panic(err) + } + fmt.Printf("Transaction Data:\n\n%# +v\n\n", pretty.Formatter(transactionData)) + return result, transactionData +} +func syncWithSorobanRPC(ledgerToWaitFor uint32) { + for j := 0; j < 20; j++ { + result := struct { + Sequence uint32 `json:"sequence"` + }{} + ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(sorobanRPCPort)+"/soroban/rpc", nil) ///soroban/rpc: + sorobanRPCClient := jrpc2.NewClient(ch, nil) + err := sorobanRPCClient.CallResult(context.Background(), "getLatestLedger", nil, &result) + if err != nil { + panic(err) + } + if result.Sequence >= ledgerToWaitFor { + return + } + time.Sleep(500 * time.Millisecond) + } + panic("Time out waiting for soroban-rpc to sync") +} + +func GetBaseTransactionParamsWithFee(source txnbuild.Account, fee int64, ops ...txnbuild.Operation) txnbuild.TransactionParams { + return txnbuild.TransactionParams{ + SourceAccount: source, + Operations: ops, + BaseFee: fee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, + IncrementSequenceNum: true, + } +} + +func CreateSignedTransactionWithParams(signers []*keypair.Full, txParams txnbuild.TransactionParams, +) (*txnbuild.Transaction, error) { + tx, err := txnbuild.NewTransaction(txParams) + if err != nil { + return nil, err + } + + for _, signer := range signers { + tx, err = tx.Sign(NETWORK_PASSPHRASE, signer) + if err != nil { + return nil, err + } + } + return tx, nil +} + +func (h *StellarClient) TestInteractContract(ctx context.Context, kp *keypair.Full, tokenAddress xdr.ScAddress, contractAddress xdr.ScAddress) error { + + // hzAcc := h.GetKeyPair() + + tokenAddr := tokenAddress + tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) + testArgs := xdr.ScVec{tokenAddrXdr} + + auth := []xdr.SorobanAuthorizationEntry{} + + _, err := h.InvokeAndProcessHostFunction("testinteract", testArgs, contractAddress, kp, auth) + if err != nil { + return errors.New("error while invoking and processing host function for InitTokenContract") + } + + return nil } func BuildOpenTxArgs(params *pchannel.Params, state *pchannel.State) xdr.ScVec { @@ -49,6 +204,21 @@ func BuildOpenTxArgs(params *pchannel.Params, state *pchannel.State) xdr.ScVec { return openArgs } +func BuildMintTokenArgs(mintTo xdr.ScAddress, amount xdr.ScVal) (xdr.ScVec, error) { + + mintToSc, err := scval.WrapScAddress(mintTo) + if err != nil { + panic(err) + } + + MintTokenArgs := xdr.ScVec{ + mintToSc, + amount, + } + + return MintTokenArgs, nil +} + func BuildFundTxArgs(chanID pchannel.ID, funderIdx bool) (xdr.ScVec, error) { chanIDStellar := chanID[:] @@ -103,42 +273,75 @@ func BuildForceCloseTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { return getChannelArgs, nil } -func DecodeTxMeta(tx horizon.Transaction) (xdr.TransactionMeta, error) { - var transactionMeta xdr.TransactionMeta - err := xdr.SafeUnmarshalBase64(tx.ResultMetaXdr, &transactionMeta) - if err != nil { - return xdr.TransactionMeta{}, err - } +// func (h *StellarClient) MintToken(ctx context.Context, kp *keypair.Full, to xdr.ScAddress, tokenAddress xdr.ScAddress, contractAddress xdr.ScAddress) error { - return transactionMeta, nil -} +// hzAcc := h.GetAccount(kp) + +// tokenAddr := tokenAddress +// tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) +// testArgs := xdr.ScVec{tokenAddrXdr} + +// auth := []xdr.SorobanAuthorizationEntry{} +// _, err := h.InvokeAndProcessHostFunction(hzAcc, "mint", testArgs, contractAddress, kp, auth) +// if err != nil { +// return errors.New("error while invoking and processing host function for InitTokenContract") +// } + +// return nil +// } + +func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full, auth []xdr.SorobanAuthorizationEntry) (xdr.TransactionMeta, error) { + + // Build contract call operation + fnameXdr := xdr.ScSymbol(fname) + hzAcc := s.GetHorizonAccount(kp) + hzClient := s.GetHorizonClient() -func stellarAssetContractID(stellarEnv *IntegrationTestEnv, asset xdr.Asset) xdr.Hash { - contractID, err := asset.ContractID(stellarEnv.GetPassPhrase()) + invokeHostFunctionOp := BuildContractCallOp(hzAcc, fnameXdr, callTxArgs, contractAddr, auth) + preFlightOp, minFee := PreflightHostFunctions(hzClient, &hzAcc, *invokeHostFunctionOp) + + txParams := GetBaseTransactionParamsWithFee(&hzAcc, minFee, &preFlightOp) + txSigned, err := CreateSignedTransactionWithParams([]*keypair.Full{kp}, txParams) if err != nil { panic(err) } - return contractID -} - -func i128Param(hi int64, lo uint64) xdr.ScVal { - i128 := &xdr.Int128Parts{ - Hi: xdr.Int64(hi), - Lo: xdr.Uint64(lo), + tx, err := hzClient.SubmitTransaction(txSigned) + if err != nil { + panic(err) } - return xdr.ScVal{ - Type: xdr.ScValTypeScvI128, - I128: i128, + // Decode transaction metadata + txMeta, err := DecodeTxMeta(tx) + if err != nil { + return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") } + fmt.Println("txMeta: ", txMeta) + retval := txMeta.V3.SorobanMeta.ReturnValue + fmt.Println("retval: ", retval) + + return txMeta, nil } -func AccountAddressParam(accountID string) xdr.ScVal { - address := xdr.ScAddress{ - Type: xdr.ScAddressTypeScAddressTypeAccount, - AccountId: xdr.MustAddressPtr(accountID), +func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xdr.ScVec, contractIdAddress xdr.ScAddress, auth []xdr.SorobanAuthorizationEntry) *txnbuild.InvokeHostFunction { + + return &txnbuild.InvokeHostFunction{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, + InvokeContract: &xdr.InvokeContractArgs{ + ContractAddress: contractIdAddress, + FunctionName: fName, + Args: callArgs, + }, + }, + Auth: auth, + SourceAccount: caller.AccountID, } - return xdr.ScVal{ - Type: xdr.ScValTypeScvAddress, - Address: &address, +} +func DecodeTxMeta(tx horizon.Transaction) (xdr.TransactionMeta, error) { + var transactionMeta xdr.TransactionMeta + err := xdr.SafeUnmarshalBase64(tx.ResultMetaXdr, &transactionMeta) + if err != nil { + return xdr.TransactionMeta{}, err } + + return transactionMeta, nil } diff --git a/channel/event.go b/channel/event.go index 474feb1..62c32cf 100644 --- a/channel/event.go +++ b/channel/event.go @@ -88,16 +88,6 @@ type ( VersionV Version Timestamp uint64 } - - // ConcludedEvent struct { - // Finalized bool - // Alloc [2]uint64 - // Tout uint64 - // Timestamp uint64 - // concluded bool - // VersionV Version - // IDV pchannel.ID - // } ) type StellarEvent struct { diff --git a/channel/funder.go b/channel/funder.go index 8c4f798..783cbc4 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -2,19 +2,23 @@ package channel import ( "context" - "crypto/rand" - "crypto/sha256" + // "crypto/rand" + // "crypto/sha256" "errors" "fmt" + "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" + + // "github.com/stellar/go/xdr" "log" - "math/big" + // "math/big" pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/wallet" "perun.network/perun-stellar-backend/wire" + "perun.network/perun-stellar-backend/wire/scval" "time" @@ -27,20 +31,32 @@ type Funder struct { stellarClient *env.StellarClient acc *wallet.Account kpFull *keypair.Full + perunID xdr.ScAddress + assetID xdr.ScAddress maxIters int pollingInterval time.Duration } -func NewFunder(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient) *Funder { +func NewFunder(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *Funder { return &Funder{ stellarClient: stellarClient, acc: acc, kpFull: kp, + perunID: perunID, + assetID: assetID, maxIters: MaxIterationsUntilAbort, pollingInterval: DefaultPollingInterval, } } +func (f *Funder) GetPerunID() xdr.ScAddress { + return f.perunID +} + +func (f *Funder) GetAssetID() xdr.ScAddress { + return f.assetID +} + func (f *Funder) Fund(ctx context.Context, req pchannel.FundingReq) error { log.Println("Fund called") switch req.Idx { @@ -68,9 +84,6 @@ func (f *Funder) fundPartyA(ctx context.Context, req pchannel.FundingReq) error return errors.New("error while polling for opened channel A") } fmt.Println("polled chanState for PartyA: ", chanState.Control.FundedA, chanState.Control.FundedB) - // fund the channel: - - fmt.Println("funding channel in party A: ", req.Params, "state: ", req.State) err = f.FundChannel(ctx, req.Params, req.State, false) if err != nil { @@ -133,17 +146,13 @@ polling: func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { - //env := f.integrEnv - - contractAddress := f.stellarClient.GetPerunAddress() + perunAddress := f.GetPerunID() kp := f.kpFull - hz := f.stellarClient.GetHorizonAcc() // generate tx to open the channel openTxArgs := env.BuildOpenTxArgs(params, state) - fmt.Println("openTxArgs: ", openTxArgs) auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "open", openTxArgs, contractAddress, kp, auth) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("open", openTxArgs, perunAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: open") } @@ -158,11 +167,11 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State, funderIdx bool) error { - perunContractAddress := f.stellarClient.GetPerunAddress() - tokenContractAddress := f.stellarClient.GetTokenAddress() + perunAddress := f.GetPerunID() + tokenAddress := f.GetAssetID() kp := f.kpFull - hzAcc := f.stellarClient.GetHorizonAcc() + // hzAcc := f.stellarClient.GetHorizonAcc() chanId := state.ID // generate tx to open the channel @@ -177,22 +186,22 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state tokenIDAddrFromBals := balsStellar.Token - sameContractTokenID := tokenIDAddrFromBals.Equals(tokenContractAddress) + sameContractTokenID := tokenIDAddrFromBals.Equals(tokenAddress) if !sameContractTokenID { panic("tokenIDAddrFromBals not equal to tokenContractAddress") } - tokenAddr := tokenContractAddress - tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) + // tokenAddr := tokenContractAddress + tokenAddrXdr := scval.MustWrapScAddress(tokenAddress) testArgs := xdr.ScVec{tokenAddrXdr} - hzAcctInt0 := f.stellarClient.GetHorizonAcc() - _, err = f.stellarClient.InvokeAndProcessHostFunction(hzAcctInt0, "testinteract", testArgs, perunContractAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx + // hzAcctInt0 := f.stellarClient.GetHorizonAcc() + _, err = f.stellarClient.InvokeAndProcessHostFunction("testinteract", testArgs, perunAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx if err != nil { return errors.New("error while invoking and processing host function: testinteract") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hzAcc, "fund", fundTxArgs, perunContractAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("fund", fundTxArgs, perunAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx if err != nil { return errors.New("error while invoking and processing host function: fund") } @@ -205,37 +214,36 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state return nil } -func makePreImgAuth(passphrase string, rootInv xdr.SorobanAuthorizedInvocation) (xdr.HashIdPreimage, error) { - max := big.NewInt(0).SetInt64(int64(^uint64(0) >> 1)) - randomPart, err := rand.Int(rand.Reader, max) - if err != nil { - panic(err) - } - networkId := xdr.Hash(sha256.Sum256([]byte(passphrase))) - ledgerEntry := uint32(100) - lEntryXdr := xdr.Uint32(ledgerEntry) - nonce := randomPart.Int64() - ncXdr := xdr.Int64(nonce) - - srbPreImgAuth := xdr.HashIdPreimageSorobanAuthorization{ - NetworkId: networkId, - Nonce: ncXdr, - SignatureExpirationLedger: lEntryXdr, - Invocation: rootInv, - } - - srbAuth := xdr.HashIdPreimage{ - Type: xdr.EnvelopeTypeEnvelopeTypeSorobanAuthorization, - SorobanAuthorization: &srbPreImgAuth} - - return srbAuth, nil -} +// func makePreImgAuth(passphrase string, rootInv xdr.SorobanAuthorizedInvocation) (xdr.HashIdPreimage, error) { +// max := big.NewInt(0).SetInt64(int64(^uint64(0) >> 1)) +// randomPart, err := rand.Int(rand.Reader, max) +// if err != nil { +// panic(err) +// } +// networkId := xdr.Hash(sha256.Sum256([]byte(passphrase))) +// ledgerEntry := uint32(100) +// lEntryXdr := xdr.Uint32(ledgerEntry) +// nonce := randomPart.Int64() +// ncXdr := xdr.Int64(nonce) + +// srbPreImgAuth := xdr.HashIdPreimageSorobanAuthorization{ +// NetworkId: networkId, +// Nonce: ncXdr, +// SignatureExpirationLedger: lEntryXdr, +// Invocation: rootInv, +// } + +// srbAuth := xdr.HashIdPreimage{ +// Type: xdr.EnvelopeTypeEnvelopeTypeSorobanAuthorization, +// SorobanAuthorization: &srbPreImgAuth} + +// return srbAuth, nil +// } func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { - contractAddress := f.stellarClient.GetPerunAddress() + contractAddress := f.GetPerunID() kp := f.kpFull - reqAlice := f.stellarClient.GetHorizonAcc() chanId := state.ID // generate tx to open the channel @@ -244,7 +252,7 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat return errors.New("error while building get_channel tx") } auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(reqAlice, "abort_funding", openTxArgs, contractAddress, kp, auth) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("abort_funding", openTxArgs, contractAddress, kp, auth) if err != nil { return errors.New("error while invoking and processing host function: abort_funding") } @@ -259,9 +267,8 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { - contractAddress := f.stellarClient.GetPerunAddress() + contractAddress := f.GetPerunID() kp := f.kpFull - hz := f.stellarClient.GetHorizonAcc() chanId := state.ID // generate tx to open the channel @@ -270,7 +277,7 @@ func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, s return wire.Channel{}, errors.New("error while building get_channel tx") } auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction(hz, "get_channel", getchTxArgs, contractAddress, kp, auth) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp, auth) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } @@ -283,5 +290,4 @@ func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, s return wire.Channel{}, errors.New("error while decoding return value") } return getChan, nil - } diff --git a/channel/funder_test.go b/channel/funder_test.go deleted file mode 100644 index 5bef8d2..0000000 --- a/channel/funder_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package channel_test - -import ( - "context" - "fmt" - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/require" - pchannel "perun.network/go-perun/channel" - "perun.network/perun-stellar-backend/channel" - "perun.network/perun-stellar-backend/channel/env" - chtest "perun.network/perun-stellar-backend/channel/test" - "perun.network/perun-stellar-backend/util" - _ "perun.network/perun-stellar-backend/wallet/test" - "testing" -) - -const PerunContractPath = "../testdata/perun_soroban_contract.wasm" - -func TestOpenChannel(t *testing.T) { - itest := env.NewIntegrationEnv(t) - - kps, _ := itest.CreateAccounts(2, "10000000") - - kpAlice := kps[0] - kpBob := kps[1] - - reqAlice := itest.AccountDetails(kpAlice) - _ = itest.AccountDetails(kpBob) - - installContractOp := channel.AssembleInstallContractCodeOp(kpAlice.Address(), channel.PerunContractPath) - preFlightOp, minFee := itest.PreflightHostFunctions(&reqAlice, *installContractOp) - _ = itest.MustSubmitOperationsWithFee(&reqAlice, kpAlice, 5*minFee, &preFlightOp) - - // Create the contract - - fmt.Println("itest.GetPassPhrase(): ", itest.GetPassPhrase()) - - createContractOp := channel.AssembleCreateContractOp(kpAlice.Address(), channel.PerunContractPath, "a1", itest.GetPassPhrase()) - preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *createContractOp) - _, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) - - require.NoError(t, err) - - // contract has been deployed, now invoke 'open' fn on the contract - contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId - require.NotNil(t, contractID) - contractIDAddress := xdr.ScAddress{ - Type: xdr.ScAddressTypeScAddressTypeContract, - ContractId: contractID, - } - - perunFirstParams, perunFirstState := chtest.NewParamsState(t) - - openArgs := env.BuildOpenTxArgs(perunFirstParams, perunFirstState) - auth := []xdr.SorobanAuthorizationEntry{} - invokeHostFunctionOp := env.BuildContractCallOp(reqAlice, "open", openArgs, contractIDAddress, auth) - - preFlightOp, minFee = itest.PreflightHostFunctions(&reqAlice, *invokeHostFunctionOp) - - tx, err := itest.SubmitOperationsWithFee(&reqAlice, kpAlice, minFee, &preFlightOp) - require.NoError(t, err) - - txMeta, err := env.DecodeTxMeta(tx) - require.NoError(t, err) - - _, err = channel.DecodeEventsPerun(txMeta) - require.NoError(t, err) - -} - -func TestFundChannel(t *testing.T) { - itest := env.NewBackendEnv() - - kps, _ := itest.CreateAccounts(3, "1000000000") - - kpAlice := kps[0] - kpBob := kps[1] - kpDeployer := kps[2] - - _ = itest.AccountDetails(kpAlice) - _ = itest.AccountDetails(kpBob) - reqDeployer := itest.AccountDetails(kpDeployer) - - contractAddr, _ := util.Deploy(itest, kpDeployer, reqDeployer, PerunContractPath) - itest.SetPerunAddress(contractAddr) - - perunFirstParams, perunFirstState := chtest.NewParamsState(t) - - _, accAlice, _ := util.MakeRandPerunWallet() - _, accBob, _ := util.MakeRandPerunWallet() - - freqAlice := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 0, perunFirstState.Balances) - freqBob := pchannel.NewFundingReq(perunFirstParams, perunFirstState, 1, perunFirstState.Balances) - freqs := []*pchannel.FundingReq{freqAlice, freqBob} - - // Creating the client and funders as pointers - aliceClient := env.NewStellarClient(itest, kpAlice) - bobClient := env.NewStellarClient(itest, kpBob) - - aliceFunder := channel.NewFunder(accAlice, kpAlice, aliceClient) - bobFunder := channel.NewFunder(accBob, kpBob, bobClient) - funders := []*channel.Funder{aliceFunder, bobFunder} - chanID := perunFirstParams.ID() - aliceIdx := false - _, err := env.BuildFundTxArgs(chanID, aliceIdx) - require.NoError(t, err) - - // Calling the function - err = chtest.FundAll(context.TODO(), funders, freqs) - require.NoError(t, err) - -} diff --git a/channel/subscribe.go b/channel/subscribe.go index 18edf21..94603d0 100644 --- a/channel/subscribe.go +++ b/channel/subscribe.go @@ -1,10 +1,9 @@ package channel import ( - "fmt" - "reflect" - + "log" pchannel "perun.network/go-perun/channel" + "reflect" ) func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { @@ -19,7 +18,6 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { select { case event := <-s.Events(): if event == nil { - fmt.Println("Event nil received: ", event) return nil } @@ -27,7 +25,7 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { switch e := event.(type) { case *DisputedEvent: - fmt.Println("DisputedEvent received") + log.Println("DisputedEvent received") dispEvent := pchannel.AdjudicatorEventBase{ VersionV: e.Version(), IDV: e.ID(), @@ -39,7 +37,7 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { case *CloseEvent: - fmt.Println("CloseEvent received") + log.Println("CloseEvent received") conclEvent := pchannel.AdjudicatorEventBase{ VersionV: e.Version(), IDV: e.ID(), @@ -50,7 +48,7 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { return ccn default: - fmt.Printf("Received an unknown event type: %v\n", reflect.TypeOf(e)) + log.Printf("Received an unknown event type: %v\n", reflect.TypeOf(e)) return nil } diff --git a/channel/test/fund.go b/channel/test/fund.go index f6ab831..aa21403 100644 --- a/channel/test/fund.go +++ b/channel/test/fund.go @@ -1,23 +1,23 @@ package test import ( - "context" - pchannel "perun.network/go-perun/channel" - "perun.network/perun-stellar-backend/channel" - pkgerrors "polycry.pt/poly-go/errors" +// "context" +// pchannel "perun.network/go-perun/channel" +// "perun.network/perun-stellar-backend/channel" +// pkgerrors "polycry.pt/poly-go/errors" ) -func FundAll(ctx context.Context, funders []*channel.Funder, reqs []*pchannel.FundingReq) error { - g := pkgerrors.NewGatherer() - for i := range funders { - i := i - g.Go(func() error { - return funders[i].Fund(ctx, *reqs[i]) - }) - } +// func FundAll(ctx context.Context, funders []*channel.Funder, reqs []*pchannel.FundingReq) error { +// g := pkgerrors.NewGatherer() +// for i := range funders { +// i := i +// g.Go(func() error { +// return funders[i].Fund(ctx, *reqs[i]) +// }) +// } - if g.WaitDoneOrFailedCtx(ctx) { - return ctx.Err() - } - return g.Err() -} +// if g.WaitDoneOrFailedCtx(ctx) { +// return ctx.Err() +// } +// return g.Err() +// } diff --git a/channel/token.go b/channel/token.go index aacd1cd..d5e8bf6 100644 --- a/channel/token.go +++ b/channel/token.go @@ -1,13 +1,52 @@ package channel import ( - "context" - "errors" + // "context" + // "errors" + // "github.com/stellar/go/keypair" + // "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/xdr" - "perun.network/perun-stellar-backend/channel/env" + // "github.com/stellar/protocols/horizon" + // "github.com/stellar/go/clients/horizonclient" + + // "perun.network/perun-stellar-backend/channel/env" + // "perun.network/perun-stellar-backend/channel/types" + + // "perun.network/perun-stellar-backend/wire" + "perun.network/perun-stellar-backend/wire/scval" ) +const tokenDecimals = uint32(7) +const tokenName = "PerunToken" +const tokenSymbol = "PRN" + +type TokenParams struct { + decimals uint32 + name string + symbol string +} + +func NewTokenParams() *TokenParams { + return &TokenParams{ + decimals: tokenDecimals, + name: tokenName, + symbol: tokenSymbol, + } +} + +func (t *TokenParams) GetDecimals() uint32 { + return t.decimals +} + +func (t *TokenParams) GetName() string { + return t.name +} + +func (t *TokenParams) GetSymbol() string { + return t.symbol +} + func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName string, tokenSymbol string) (xdr.ScVec, error) { adminScAddr, err := scval.WrapScAddress(adminAddr) @@ -38,28 +77,28 @@ func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName stri return initTokenArgs, nil } -func BuildMintTokenArgs(mintTo xdr.ScAddress, amount int64) (xdr.ScVec, error) { +// func InitTokenContract(hzClient *env.StellarClient, hzAccount horizon.Account, kpAdmin *keypair.Full, contractAddress xdr.ScAddress) error { - recScAddr, err := scval.WrapScAddress(mintTo) - if err != nil { - panic(err) - } +// tokenParams := NewTokenParams() +// adminScAddr, err := types.MakeAccountAddress(kpAdmin) - amounti64 := xdr.Int64(amount) - scvaltype := xdr.ScValTypeScvI64 +// if err != nil { +// panic(err) +// } +// initTokenArgs, err := wire.BuildInitTokenArgs(adminScAddr, tokenParams.decimals, tokenParams.name, tokenParams.symbol) - amountSc, err := xdr.NewScVal(scvaltype, amounti64) - if err != nil { - panic(err) - } +// if err != nil { +// return errors.New("error while building fund tx") +// } +// auth := []xdr.SorobanAuthorizationEntry{} - MintTokenArgs := xdr.ScVec{ - recScAddr, - amountSc, - } +// _, err = hzClient.GetHorizonClient().InvokeAndProcessHostFunction(hzAccount, "initialize", initTokenArgs, contractAddress, kpAdmin, auth) +// if err != nil { +// return errors.New("error while invoking and processing host function for InitTokenContract") +// } - return MintTokenArgs, nil -} +// return nil +// } func BuildTokenNameArgs() (xdr.ScVec, error) { @@ -106,99 +145,81 @@ func BuildTransferTokenArgs(from xdr.ScAddress, to xdr.ScAddress, amount xdr.Int return GetTokenBalanceArgs, nil } -func InitTokenContract(ctx context.Context, stellarClient *env.StellarClient, adminAddr xdr.ScAddress, decimals uint32, tokenName, tokenSymbol string, contractAddress xdr.ScAddress) error { - - hzAcc := stellarClient.GetHorizonAcc() - kp := stellarClient.GetAccount() - - initTokenArgs, err := BuildInitTokenArgs(adminAddr, decimals, tokenName, tokenSymbol) - if err != nil { - return errors.New("error while building fund tx") - } - auth := []xdr.SorobanAuthorizationEntry{} - _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "initialize", initTokenArgs, contractAddress, kp, auth) - if err != nil { - return errors.New("error while invoking and processing host function for InitTokenContract") - } - - return nil -} - -func GetTokenName(ctx context.Context, stellarClient *env.StellarClient, contractAddress xdr.ScAddress) error { - - hzAcc := stellarClient.GetHorizonAcc() - kp := stellarClient.GetAccount() - // generate tx to open the channel - TokenNameArgs, err := BuildTokenNameArgs() - if err != nil { - return errors.New("error while building fund tx") - } - auth := []xdr.SorobanAuthorizationEntry{} - - _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "name", TokenNameArgs, contractAddress, kp, auth) - if err != nil { - return errors.New("error while invoking and processing host function for GetTokenName") - } - - return nil -} - -func MintToken(ctx context.Context, stellarClient *env.StellarClient, mintTo xdr.ScAddress, mintAmount int64, contractAddress xdr.ScAddress) error { - - hzAcc := stellarClient.GetHorizonAcc() - kp := stellarClient.GetAccount() - TokenNameArgs, err := BuildMintTokenArgs(mintTo, mintAmount) - if err != nil { - return errors.New("error while building fund tx") - } - auth := []xdr.SorobanAuthorizationEntry{} - _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "mint", TokenNameArgs, contractAddress, kp, auth) - if err != nil { - return errors.New("error while invoking and processing host function for GetTokenName") - } - - return nil -} - -func GetTokenBalance(ctx context.Context, stellarClient *env.StellarClient, balanceOf xdr.ScAddress, contractAddress xdr.ScAddress) error { - - hzAcc := stellarClient.GetHorizonAcc() - kp := stellarClient.GetAccount() - // generate tx to open the channel - getBalanceArgs, err := BuildGetTokenBalanceArgs(balanceOf) - if err != nil { - return errors.New("error while building fund tx") - } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "balance", getBalanceArgs, contractAddress, kp, auth) - if err != nil { - return errors.New("error while invoking and processing host function for GetTokenName") - } - - _ = txMeta.V3.SorobanMeta.ReturnValue - - // rVal := reVal.MustI128() - - return nil -} - -func TransferToken(ctx context.Context, stellarClient *env.StellarClient, from xdr.ScAddress, to xdr.ScAddress, amount128 xdr.Int128Parts, contractAddress xdr.ScAddress) error { - - hzAcc := stellarClient.GetHorizonAcc() - kp := stellarClient.GetAccount() - transferArgs, err := BuildTransferTokenArgs(from, to, amount128) - if err != nil { - return errors.New("error while building fund tx") - } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "transfer", transferArgs, contractAddress, kp, auth) - if err != nil { - return errors.New("error while invoking and processing host function for GetTokenName") - } - - _ = txMeta.V3.SorobanMeta.ReturnValue - - // rVal := reVal.MustI128() - - return nil -} +// func GetTokenName(ctx context.Context, stellarClient *env.StellarClient, contractAddress xdr.ScAddress) error { + +// hzAcc := stellarClient.GetHorizonAcc() +// kp := stellarClient.GetAccount() +// // generate tx to open the channel +// TokenNameArgs, err := BuildTokenNameArgs() +// if err != nil { +// return errors.New("error while building fund tx") +// } +// auth := []xdr.SorobanAuthorizationEntry{} + +// _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "name", TokenNameArgs, contractAddress, kp, auth) +// if err != nil { +// return errors.New("error while invoking and processing host function for GetTokenName") +// } + +// return nil +// } + +// func MintToken(ctx context.Context, stellarClient *env.StellarClient, mintTo xdr.ScAddress, mintAmount int64, contractAddress xdr.ScAddress) error { + +// hzAcc := stellarClient.GetHorizonAcc() +// kp := stellarClient.GetAccount() +// TokenNameArgs, err := BuildMintTokenArgs(mintTo, mintAmount) +// if err != nil { +// return errors.New("error while building fund tx") +// } +// auth := []xdr.SorobanAuthorizationEntry{} +// _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "mint", TokenNameArgs, contractAddress, kp, auth) +// if err != nil { +// return errors.New("error while invoking and processing host function for GetTokenName") +// } + +// return nil +// } + +// func GetTokenBalance(ctx context.Context, stellarClient *env.StellarClient, balanceOf xdr.ScAddress, contractAddress xdr.ScAddress) error { + +// hzAcc := stellarClient.GetHorizonAcc() +// kp := stellarClient.GetAccount() +// // generate tx to open the channel +// getBalanceArgs, err := BuildGetTokenBalanceArgs(balanceOf) +// if err != nil { +// return errors.New("error while building fund tx") +// } +// auth := []xdr.SorobanAuthorizationEntry{} +// txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "balance", getBalanceArgs, contractAddress, kp, auth) +// if err != nil { +// return errors.New("error while invoking and processing host function for GetTokenName") +// } + +// _ = txMeta.V3.SorobanMeta.ReturnValue + +// // rVal := reVal.MustI128() + +// return nil +// } + +// func TransferToken(ctx context.Context, stellarClient *env.StellarClient, from xdr.ScAddress, to xdr.ScAddress, amount128 xdr.Int128Parts, contractAddress xdr.ScAddress) error { + +// hzAcc := stellarClient.GetHorizonAcc() +// kp := stellarClient.GetAccount() +// transferArgs, err := BuildTransferTokenArgs(from, to, amount128) +// if err != nil { +// return errors.New("error while building fund tx") +// } +// auth := []xdr.SorobanAuthorizationEntry{} +// txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "transfer", transferArgs, contractAddress, kp, auth) +// if err != nil { +// return errors.New("error while invoking and processing host function for GetTokenName") +// } + +// _ = txMeta.V3.SorobanMeta.ReturnValue + +// // rVal := reVal.MustI128() + +// return nil +// } diff --git a/client/client.go b/client/client.go index b4b5e66..931b7a8 100644 --- a/client/client.go +++ b/client/client.go @@ -5,7 +5,8 @@ import ( "errors" "fmt" "github.com/stellar/go/keypair" - //"github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/xdr" + "math/big" pchannel "perun.network/go-perun/channel" "perun.network/go-perun/client" @@ -30,21 +31,20 @@ type PaymentClient struct { } func SetupPaymentClient( - stellarEnv *env.IntegrationTestEnv, w *wallet.EphemeralWallet, // w is the wallet used to resolve addresses to accounts for channels. acc *wallet.Account, - stellarKp *keypair.Full, - stellarTokenID types.StellarAsset, + kp *keypair.Full, + tokenAddr xdr.ScAddress, + perunAddr xdr.ScAddress, bus *wire.LocalBus, - //hzAcc *horizon.Account, ) (*PaymentClient, error) { // Connect to Perun pallet and get funder + adjudicator from it. - perunConn := env.NewStellarClient(stellarEnv, stellarKp) - funder := channel.NewFunder(acc, stellarKp, perunConn) - adj := channel.NewAdjudicator(acc, stellarKp, perunConn) + perunConn := env.NewStellarClient(kp) + funder := channel.NewFunder(acc, kp, perunConn, perunAddr, tokenAddr) + adj := channel.NewAdjudicator(acc, kp, perunConn, perunAddr, tokenAddr) // Setup dispute watcher. watcher, err := local.NewWatcher(adj) @@ -59,17 +59,21 @@ func SetupPaymentClient( return nil, errors.New("creating client") } + assetContractID, err := types.NewStellarAssetFromScAddress(tokenAddr) + if err != nil { + panic(err) + } + // Create client and start request handler. c := &PaymentClient{ perunClient: perunClient, account: acc, - currency: &stellarTokenID, + currency: assetContractID, channels: make(chan *PaymentChannel, 1), wAddr: wireAddr, balance: big.NewInt(0), } - //go c.PollBalances() go perunClient.Handle(c, c) return c, nil } diff --git a/go.mod b/go.mod index 4b6233f..a8fa9b5 100644 --- a/go.mod +++ b/go.mod @@ -2,84 +2,34 @@ module perun.network/perun-stellar-backend go 1.19 -require github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 +require github.com/stellar/go v0.0.0-20231212225359-bc7173e667a6 require ( - github.com/stellar/go-xdr v0.0.0-20230919160922-6c7b68458206 - github.com/stretchr/testify v1.8.1 + github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 + github.com/creachadair/jrpc2 v1.1.0 + github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 + github.com/stretchr/testify v1.8.4 perun.network/go-perun v0.10.6 polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b ) -replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => github.com/perun-network/go v0.0.0-20231212081247-248b5be6c4ba - -// replace github.com/stellar/go v0.0.0-20231003185205-facabfc2f4c4 => ../stellarfork/go - require ( - github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 // indirect - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/Masterminds/squirrel v1.5.0 // indirect - github.com/Microsoft/go-winio v0.4.14 // indirect - github.com/adjust/goautoneg v0.0.0-20150426214442-d788f35a0315 // indirect - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go v1.44.326 // indirect - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect - github.com/creachadair/jrpc2 v0.41.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1 // indirect - github.com/go-chi/chi v4.0.3+incompatible // indirect - github.com/go-errors/errors v0.0.0-20150906023321-a41850380601 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/gorilla/schema v1.1.0 // indirect - github.com/guregu/null v2.1.3-0.20151024101046-79c5bd36b615+incompatible // indirect - github.com/hashicorp/golang-lru v0.5.1 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/holiman/uint256 v1.2.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jmoiron/sqlx v1.2.0 // indirect + github.com/creachadair/mds v0.0.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-errors/errors v1.5.1 // indirect + github.com/gorilla/schema v1.2.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.2.0 // indirect - github.com/magiconair/properties v1.8.0 // indirect github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.1.2 // indirect - github.com/pelletier/go-toml v1.9.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 // indirect - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect - github.com/prometheus/common v0.2.0 // indirect - github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 // indirect - github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect - github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079 // indirect github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spf13/afero v1.1.2 // indirect - github.com/spf13/cast v1.3.0 // indirect - github.com/spf13/cobra v0.0.5 // indirect - github.com/spf13/jwalterweatherman v1.0.0 // indirect - github.com/spf13/pflag v1.0.3 // indirect - github.com/spf13/viper v1.3.2 // indirect - github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/xdrpp/goxdr v0.1.1 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/gorp.v1 v1.7.1 // indirect - gopkg.in/tylerb/graceful.v1 v1.2.13 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/objx v0.5.1 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.13.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 64e1282..ff09925 100644 --- a/go.sum +++ b/go.sum @@ -1,90 +1,31 @@ github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 h1:vvMDiVd621MU1Djr7Ep7OXu8gHOtsdwrI4tjnIGvpTg= github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95/go.mod h1:Gv4NIpY67KDahg+DtIG5/2Ok4l8vzYEekiirSCH+IGA= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= -github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/adjust/goautoneg v0.0.0-20150426214442-d788f35a0315 h1:zje9aPr1kQ5nKwjO5MC0S/jehRtNrjfYuLfFRWZH6kY= -github.com/adjust/goautoneg v0.0.0-20150426214442-d788f35a0315/go.mod h1:4U522XvlkqOY2AVBUM7ISHODDb6tdB+KAXfGaBDsWts= github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.326 h1:/6xD/9mKZ2RMTDfbhh9qCxw+CaTbJRvfHJ/NHPFbI38= -github.com/aws/aws-sdk-go v1.44.326/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creachadair/jrpc2 v0.41.1 h1:GnSQNk+vt8B/oayJlfOXVRi4hg8DuB9NsppFGe8iVJ0= -github.com/creachadair/jrpc2 v0.41.1/go.mod h1:k2mGfjsgE2h2Vo12C9NzZguUzzl3gnfGCmLIvg84pVE= +github.com/creachadair/jrpc2 v1.1.0 h1:SgpJf0v1rVCZx68+4APv6dgsTFsIHlpgFD1NlQAWA0A= +github.com/creachadair/jrpc2 v1.1.0/go.mod h1:5jN7MKwsm8qvgfTsTzLX3JIfidsAkZ1c8DZSQmp+g38= +github.com/creachadair/mds v0.0.1 h1:2nX6Sww4dXpScx3b6aYjH1n7iuEH715+jj+cKkKw9BY= +github.com/creachadair/mds v0.0.1/go.mod h1:caBACU+n1Q/rZ252FTzfnG0/H+ZUi+UnIQtEOraMv/g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955 h1:gmtGRvSexPU4B1T/yYo0sLOKzER1YT+b4kPxPpm0Ty4= -github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1 h1:qIqziX4EA/OBdmMgtaqdKBWWOZIfyXYClCoa56NgVEk= -github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= -github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-errors/errors v0.0.0-20150906023321-a41850380601 h1:jxTbmDuqQUTI6MscgbqB39vtxGfr2fi61nYIcFQUnlE= -github.com/go-errors/errors v0.0.0-20150906023321-a41850380601/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= -github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw= -github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 h1:oERTZ1buOUYlpmKaqlO5fYmz8cZ1rYu5DieJzF4ZVmU= -github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= -github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= -github.com/guregu/null v2.1.3-0.20151024101046-79c5bd36b615+incompatible h1:SZmF1M6CdAm4MmTPYYTG+x9EC8D3FOxUq9S4D37irQg= -github.com/guregu/null v2.1.3-0.20151024101046-79c5bd36b615+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jarcoal/httpmock v0.0.0-20161210151336-4442edb3db31 h1:Aw95BEvxJ3K6o9GGv5ppCd1P8hkeIeEJ30FO+OhOJpM= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -92,185 +33,68 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 h1:ykXz+pRRTibcSjG1yRhpdSHInF8yZY/mfn+Rz2Nd1rE= github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739/go.mod h1:zUx1mhth20V3VKgL5jbd1BSQcW4Fy6Qs4PZvQwRFwzM= -github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moul/http2curl v0.0.0-20161031194548-4e24498b31db h1:eZgFHVkk9uOTaOQLC6tgjkzdp7Ays8eEVecBcfHZlJQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0= -github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= -github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079 h1:xPeaaIHjF9j8jbYQ5xdvLnFp+lpmGYFG1uBPtXNBHno= -github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 h1:S4OC0+OBKz6mJnzuHioeEat74PuQ4Sgvbf8eus695sc= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2/go.mod h1:8zLRYR5npGjaOXgPSKat5+oOh+UHd8OdbS18iqX9F6Y= github.com/sergi/go-diff v0.0.0-20161205080420-83532ca1c1ca h1:oR/RycYTFTVXzND5r4FdsvbnBn0HJXSVeNAnwaTXRwk= -github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stellar/go-xdr v0.0.0-20230919160922-6c7b68458206 h1:UFuvvpbWL8+jqO1QmKYWSVhiMp4MRiIFd8/zQlUINH0= -github.com/stellar/go-xdr v0.0.0-20230919160922-6c7b68458206/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= -github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible h1:jMXXAcz6xTarGDQ4VtVbtERogcmDQw4RaE85Cr9CgoQ= -github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible/go.mod h1:7CJ23pXirXBJq45DqvO6clzTEGM/l1SfKrgrzLry8b4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stellar/go v0.0.0-20231212225359-bc7173e667a6 h1:LcQ01nwgxVoCmzAthjGSbxun9z/mxuqDy4uYETw4jEQ= +github.com/stellar/go v0.0.0-20231212225359-bc7173e667a6/go.mod h1:PAWie4LYyDzJXqDVG4Qcj1Nt+uNk7sjzgSCXndQYsBA= +github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 h1:OzCVd0SV5qE3ZcDeSFCmOWLZfEWZ3Oe8KtmSOYKEVWE= +github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= github.com/xdrpp/goxdr v0.1.1 h1:E1B2c6E8eYhOVyd7yEpOyopzTPirUeF6mVOfXfGyJyc= -github.com/xdrpp/goxdr v0.1.1/go.mod h1:dXo1scL/l6s7iME1gxHWo2XCppbHEKZS7m/KyYWkNzA= github.com/xeipuuv/gojsonpointer v0.0.0-20151027082146-e0fe6f683076 h1:KM4T3G70MiR+JtqplcYkNVoNz7pDwYaBxWBXQK804So= github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c h1:XZWnr3bsDQWAZg4Ne+cPoXRPILrNlPNQfxBuwLl43is= github.com/xeipuuv/gojsonschema v0.0.0-20161231055540-f06f290571ce h1:cVSRGH8cOveJNwFEEZLXtB+XMnRqKLjUP6V/ZFYQCXI= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yalp/jsonpath v0.0.0-20150812003900-31a79c7593bb h1:06WAhQa+mYv7BiOk13B/ywyTlkoE/S7uu6TBKU6FHnE= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M= github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce h1:888GrqRxabUce7lj4OaoShPxodm3kXOMpSa85wdYzfY= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/gavv/httpexpect.v1 v1.0.0-20170111145843-40724cf1e4a0 h1:r5ptJ1tBxVAeqw4CrYWhXIMr0SybY3CDHuIbCg5CFVw= -gopkg.in/gorp.v1 v1.7.1 h1:GBB9KrWRATQZh95HJyVGUZrWwOPswitEYEyqlK8JbAA= -gopkg.in/gorp.v1 v1.7.1/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tylerb/graceful.v1 v1.2.13 h1:UWJlWJHZepntB0PJ9RTgW3X+zVLjfmWbx/V1X/V/XoA= -gopkg.in/tylerb/graceful.v1 v1.2.13/go.mod h1:yBhekWvR20ACXVObSSdD3u6S9DeSylanL2PAbAC/uJ8= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index f7ef742..344d614 100644 --- a/main.go +++ b/main.go @@ -2,12 +2,10 @@ package main import ( "context" - "github.com/stellar/go/xdr" + "fmt" "log" "perun.network/go-perun/wire" - "perun.network/perun-stellar-backend/channel" "perun.network/perun-stellar-backend/channel/env" - "perun.network/perun-stellar-backend/channel/types" "perun.network/perun-stellar-backend/client" "perun.network/perun-stellar-backend/util" ) @@ -17,123 +15,60 @@ const StellarAssetContractPath = "./testdata/perun_soroban_token.wasm" func main() { - stellarEnv := env.NewBackendEnv() - kps, _ := stellarEnv.CreateAccounts(5, "1000000000") + kps, _ := util.CreateFundNewRandomStellarKP(4, "1000000") kpAlice := kps[0] kpBob := kps[1] - kpDeployerPerun := kps[2] - kpDeployerToken := kps[3] - tokenContractIDAddress, _ := util.Deploy(stellarEnv, kpDeployerToken, stellarEnv.AccountDetails(kpDeployerToken), StellarAssetContractPath) + kpDeployerToken := kps[2] + kpDeployerPerun := kps[3] - adminScAddr, err := types.MakeAccountAddress(kpDeployerToken) - if err != nil { - panic(err) - } - decims := uint32(7) - tokenName := "PerunToken" - tokenSymbol := "PRN" - - deployerStellarClient := env.NewStellarClient(stellarEnv, kpDeployerToken) - aliceStellarClient := env.NewStellarClient(stellarEnv, kpAlice) - - err = channel.InitTokenContract(context.TODO(), deployerStellarClient, adminScAddr, decims, tokenName, tokenSymbol, tokenContractIDAddress) - if err != nil { - panic(err) - } - - err = channel.GetTokenName(context.TODO(), deployerStellarClient, tokenContractIDAddress) - if err != nil { - panic(err) - } - - aliceAddrXdr, err := types.MakeAccountAddress(kpAlice) - if err != nil { - panic(err) - } - bobAddrXdr, err := types.MakeAccountAddress(kpBob) - if err != nil { - panic(err) - } - - mintAmount := int64(100000000) - - err = channel.MintToken(context.TODO(), deployerStellarClient, aliceAddrXdr, mintAmount, tokenContractIDAddress) - if err != nil { - panic(err) - } - err = channel.MintToken(context.TODO(), deployerStellarClient, bobAddrXdr, mintAmount, tokenContractIDAddress) - if err != nil { - panic(err) - } + tokenAddr, assetHash := util.Deploy(kpDeployerToken, StellarAssetContractPath) + // util.MintAsset(kpDeployerToken, tokenAddr, kpAlice.Address(), 1000000) + // util.MintAsset(kpDeployerToken, tokenAddr, kpBob.Address(), 1000000) - err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, aliceAddrXdr, tokenContractIDAddress) - if err != nil { - panic(err) - } - err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, bobAddrXdr, tokenContractIDAddress) + err := util.InitTokenContract(kpDeployerToken, tokenAddr) if err != nil { panic(err) } - txAmountBob128 := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(mintAmount / 10.)} - err = channel.TransferToken(context.TODO(), aliceStellarClient, aliceAddrXdr, bobAddrXdr, txAmountBob128, tokenContractIDAddress) + aliceAddr, err := util.MakeAccountAddress(kpAlice) if err != nil { panic(err) } - err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, aliceAddrXdr, tokenContractIDAddress) - if err != nil { - panic(err) - } - err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, bobAddrXdr, tokenContractIDAddress) + bobAddr, err := util.MakeAccountAddress(kpBob) if err != nil { panic(err) } - perunContractIDAddress, _ := util.Deploy(stellarEnv, kpDeployerPerun, stellarEnv.AccountDetails(kpDeployerPerun), PerunContractPath) - stellarEnv.SetPerunAddress(perunContractIDAddress) - stellarEnv.SetTokenAddress(tokenContractIDAddress) - - err = channel.MintToken(context.TODO(), deployerStellarClient, perunContractIDAddress, mintAmount, tokenContractIDAddress) + err = util.MintToken(kpDeployerToken, tokenAddr, int64(10000000000), aliceAddr) if err != nil { panic(err) } - txAmountPerun128 := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(mintAmount / 20.)} - - err = channel.TransferToken(context.TODO(), aliceStellarClient, aliceAddrXdr, perunContractIDAddress, txAmountPerun128, tokenContractIDAddress) + err = util.MintToken(kpDeployerToken, tokenAddr, int64(10000000000), bobAddr) if err != nil { panic(err) } - err = channel.GetTokenBalance(context.TODO(), deployerStellarClient, perunContractIDAddress, tokenContractIDAddress) - if err != nil { - panic(err) - } + perunAddr, perunHash := util.Deploy(kpDeployerPerun, PerunContractPath) + fmt.Println("assetID, assetHash, perunID, perunHas: ", perunAddr, assetHash, perunHash) // // Generate L2 accounts for the payment channel wAlice, accAlice, _ := util.MakeRandPerunWallet() wBob, accBob, _ := util.MakeRandPerunWallet() - assetCID, err := types.NewStellarAssetFromScAddress(tokenContractIDAddress) - if err != nil { - panic(err) - } - assetContractID := *assetCID - bus := wire.NewLocalBus() - alicePerun, err := client.SetupPaymentClient(stellarEnv, wAlice, accAlice, kpAlice, assetContractID, bus) + alicePerun, err := client.SetupPaymentClient(wAlice, accAlice, kpAlice, tokenAddr, perunAddr, bus) if err != nil { panic(err) } - bobPerun, err := client.SetupPaymentClient(stellarEnv, wBob, accBob, kpBob, assetContractID, bus) + bobPerun, err := client.SetupPaymentClient(wBob, accBob, kpBob, tokenAddr, perunAddr, bus) if err != nil { panic(err) } - - alicePerun.OpenChannel(bobPerun.WireAddress(), 1000) + alicePerun.OpenChannel(bobPerun.WireAddress(), 10) aliceChannel := alicePerun.Channel bobChannel := bobPerun.AcceptedChannel() @@ -144,5 +79,14 @@ func main() { bobPerun.Shutdown() log.Println("Done") + // // initialize the contract + + cl := env.NewStellarClient(kpAlice) + + err = cl.TestInteractContract(context.TODO(), kpAlice, tokenAddr, perunAddr) + if err != nil { + panic(err) + } + log.Println("DONE") } diff --git a/quickstart.sh b/quickstart.sh index 424fb6c..9189f0f 100755 --- a/quickstart.sh +++ b/quickstart.sh @@ -1,8 +1,64 @@ -#!/bin/sh - -docker run --rm -it \ - -p "8000:8000" \ - --name stellar \ - stellar/quickstart:soroban-dev \ - --testnet \ - --enable-soroban-rpc \ No newline at end of file +#!/bin/bash + +set -e + +case "$1" in +standalone) + echo "Using standalone network" + ARGS="--local" + ;; +futurenet) + echo "Using Futurenet network" + ARGS="--futurenet" + ;; +*) + echo "Usage: $0 standalone|futurenet" + exit 1 + ;; +esac + +# this is set to the quickstart `soroban-dev` image annointed as the release +# for a given Soroban Release, it is captured on Soroban Releases - https://soroban.stellar.org/docs/reference/releases +# QUICKSTART_SOROBAN_DOCKER_SHA=stellar/quickstart:soroban-dev +QUICKSTART_SOROBAN_DOCKER_SHA=stellar/quickstart:testing@sha256:0c756150e7b3c53603fe36bb932c4e7d7ceaef691906b2d3d952771ccc195559 + +shift + +# Run the soroban-preview container +# Remember to do: +# make build-docker + +echo "Creating docker soroban network" +(docker network inspect soroban-network -f '{{.Id}}' 2>/dev/null) \ + || docker network create soroban-network + +echo "Searching for a previous soroban-preview docker container" +containerID=$(docker ps --filter="name=soroban-preview" --all --quiet) +if [[ ${containerID} ]]; then + echo "Start removing soroban-preview container." + docker rm --force soroban-preview + echo "Finished removing soroban-preview container." +else + echo "No previous soroban-preview container was found" +fi + +currentDir=$(pwd) +docker run -dti \ + --volume ${currentDir}:/workspace \ + --name soroban-preview \ + -p 8001:8000 \ + --ipc=host \ + --network soroban-network \ + soroban-preview:10 + +# Run the stellar quickstart image + +docker run --rm -ti \ + --name stellar \ + --pull always \ + --network soroban-network \ + -p 8000:8000 \ + "$QUICKSTART_SOROBAN_DOCKER_SHA" \ + $ARGS \ + --enable-soroban-rpc \ + "$@" # Pass through args from the CLI diff --git a/services/horizon/docker/Dockerfile b/services/horizon/docker/Dockerfile deleted file mode 100644 index 3c090d2..0000000 --- a/services/horizon/docker/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM ubuntu:focal - -ARG VERSION -ARG STELLAR_CORE_VERSION -ARG DEBIAN_FRONTEND=noninteractive -ARG ALLOW_CORE_UNSTABLE=no - -RUN apt-get update && apt-get install -y wget apt-transport-https gnupg2 && \ - wget -qO /etc/apt/trusted.gpg.d/SDF.asc https://apt.stellar.org/SDF.asc && \ - echo "deb https://apt.stellar.org focal stable" | tee -a /etc/apt/sources.list.d/SDF.list && \ - if [ "${ALLOW_CORE_UNSTABLE}" = "yes" ]; then echo "deb https://apt.stellar.org focal unstable" | tee -a /etc/apt/sources.list.d/SDF.list; fi && \ - cat /etc/apt/sources.list.d/SDF.list && \ - apt-get update && apt-cache madison stellar-core && eval "apt-get install -y stellar-core${STELLAR_CORE_VERSION+=$STELLAR_CORE_VERSION}" && \ - if [ "${ALLOW_CORE_UNSTABLE}" = "yes" ]; then sed -i '/unstable/d' /etc/apt/sources.list.d/SDF.list; fi && \ - cat /etc/apt/sources.list.d/SDF.list && \ - echo "deb https://apt.stellar.org focal testing" | tee -a /etc/apt/sources.list.d/SDF.list && \ - apt-get update && apt-cache madison stellar-horizon && apt-get install -y stellar-horizon=${VERSION} && \ - apt-get clean && rm -rf /var/lib/apt/lists/* /var/log/*.log /var/log/*/*.log - -EXPOSE 8000 -ENTRYPOINT ["/usr/bin/stellar-horizon"] diff --git a/services/horizon/docker/Dockerfile.dev b/services/horizon/docker/Dockerfile.dev deleted file mode 100644 index 1d1be8d..0000000 --- a/services/horizon/docker/Dockerfile.dev +++ /dev/null @@ -1,28 +0,0 @@ -FROM golang:1.20-bullseye AS builder - -ARG VERSION="devel" -WORKDIR /go/src/github.com/stellar/go -COPY go.mod go.sum ./ -RUN go mod download -COPY . ./ -ENV GOFLAGS="-ldflags=-X=github.com/stellar/go/support/app.version=${VERSION}-(built-from-source)" -RUN go install github.com/stellar/go/services/horizon - -FROM ubuntu:22.04 -ARG STELLAR_CORE_VERSION -ENV STELLAR_CORE_VERSION=${STELLAR_CORE_VERSION:-*} -ENV STELLAR_CORE_BINARY_PATH /usr/bin/stellar-core - -ENV DEBIAN_FRONTEND=noninteractive -# ca-certificates are required to make tls connections -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl wget gnupg apt-utils -RUN wget -qO - https://apt.stellar.org/SDF.asc | APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=true apt-key add - -RUN echo "deb https://apt.stellar.org focal stable" >/etc/apt/sources.list.d/SDF.list -RUN echo "deb https://apt.stellar.org focal unstable" >/etc/apt/sources.list.d/SDF-unstable.list -RUN apt-get update && apt-get install -y stellar-core=${STELLAR_CORE_VERSION} -RUN apt-get clean - -COPY --from=builder /go/bin/horizon ./ - -ENTRYPOINT ["./horizon"] - diff --git a/services/horizon/docker/Makefile b/services/horizon/docker/Makefile deleted file mode 100644 index 3207445..0000000 --- a/services/horizon/docker/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -SUDO := $(shell docker version >/dev/null 2>&1 || echo "sudo") - -# https://github.com/opencontainers/image-spec/blob/master/annotations.md -BUILD_DATE := $(shell date -u +%FT%TZ) - -TAG ?= stellar/stellar-horizon:$(VERSION) -ifeq ($(ALLOW_CORE_UNSTABLE),yes) - TAG := $(TAG)-UNSTABLE -endif - -docker-build: -ifndef VERSION - $(error VERSION environment variable must be set. For example VERSION=2.4.1-101 ) -endif -ifndef STELLAR_CORE_VERSION - $(SUDO) docker build --pull $(DOCKER_OPTS) \ - --label org.opencontainers.image.created="$(BUILD_DATE)" \ - --build-arg VERSION=$(VERSION) --build-arg ALLOW_CORE_UNSTABLE=$(ALLOW_CORE_UNSTABLE) \ - -t $(TAG) . -else - $(SUDO) docker build --pull $(DOCKER_OPTS) \ - --label org.opencontainers.image.created="$(BUILD_DATE)" \ - --build-arg VERSION=$(VERSION) --build-arg STELLAR_CORE_VERSION=$(STELLAR_CORE_VERSION) \ - --build-arg ALLOW_CORE_UNSTABLE=$(ALLOW_CORE_UNSTABLE) \ - -t $(TAG) . -endif - -docker-push: -ifndef TAG - $(error Must set VERSION or TAG environment variable. For example VERSION=2.4.1-101 ) -endif - $(SUDO) docker push $(TAG) diff --git a/services/horizon/docker/captive-core-classic-integration-tests.cfg b/services/horizon/docker/captive-core-classic-integration-tests.cfg deleted file mode 100644 index ed8ac3e..0000000 --- a/services/horizon/docker/captive-core-classic-integration-tests.cfg +++ /dev/null @@ -1,13 +0,0 @@ -PEER_PORT=11725 -ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true - -UNSAFE_QUORUM=true -FAILURE_SAFETY=0 - -[[VALIDATORS]] -NAME="local_core" -HOME_DOMAIN="core.local" -# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" -PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" -ADDRESS="localhost" -QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-integration-tests.cfg b/services/horizon/docker/captive-core-integration-tests.cfg deleted file mode 100644 index 2215cb9..0000000 --- a/services/horizon/docker/captive-core-integration-tests.cfg +++ /dev/null @@ -1,15 +0,0 @@ -PEER_PORT=11725 -ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true -ENABLE_SOROBAN_DIAGNOSTIC_EVENTS=true -TESTING_SOROBAN_HIGH_LIMIT_OVERRIDE=true - -UNSAFE_QUORUM=true -FAILURE_SAFETY=0 - -[[VALIDATORS]] -NAME="local_core" -HOME_DOMAIN="core.local" -# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" -PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" -ADDRESS="localhost" -QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg b/services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg deleted file mode 100644 index 2e3dd34..0000000 --- a/services/horizon/docker/captive-core-integration-tests.soroban-rpc.cfg +++ /dev/null @@ -1,14 +0,0 @@ -PEER_PORT=11725 -ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true -ENABLE_SOROBAN_DIAGNOSTIC_EVENTS=true - -UNSAFE_QUORUM=true -FAILURE_SAFETY=0 - -[[VALIDATORS]] -NAME="local_core" -HOME_DOMAIN="core.local" -# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" -PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" -ADDRESS="core" -QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg b/services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg deleted file mode 100644 index 4902cf8..0000000 --- a/services/horizon/docker/captive-core-reingest-range-classic-integration-tests.cfg +++ /dev/null @@ -1,9 +0,0 @@ -ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true - -[[VALIDATORS]] -NAME="local_core" -HOME_DOMAIN="core.local" -# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" -PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" -ADDRESS="localhost" -QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-reingest-range-integration-tests.cfg b/services/horizon/docker/captive-core-reingest-range-integration-tests.cfg deleted file mode 100644 index 26a4cd6..0000000 --- a/services/horizon/docker/captive-core-reingest-range-integration-tests.cfg +++ /dev/null @@ -1,10 +0,0 @@ -ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true -TESTING_SOROBAN_HIGH_LIMIT_OVERRIDE=true - -[[VALIDATORS]] -NAME="local_core" -HOME_DOMAIN="core.local" -# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" -PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" -ADDRESS="localhost" -QUALITY="MEDIUM" diff --git a/services/horizon/docker/captive-core-standalone.cfg b/services/horizon/docker/captive-core-standalone.cfg deleted file mode 100644 index d54b0ec..0000000 --- a/services/horizon/docker/captive-core-standalone.cfg +++ /dev/null @@ -1,12 +0,0 @@ -PEER_PORT=11725 - -UNSAFE_QUORUM=true -FAILURE_SAFETY=0 - -[[VALIDATORS]] -NAME="local_core" -HOME_DOMAIN="core.local" -# From "SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" -PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" -ADDRESS="host.docker.internal" -QUALITY="MEDIUM" \ No newline at end of file diff --git a/services/horizon/docker/core-start.sh b/services/horizon/docker/core-start.sh deleted file mode 100755 index 9dd89ba..0000000 --- a/services/horizon/docker/core-start.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -source /etc/profile -# work within the current docker working dir -if [ ! -f "./stellar-core.cfg" ]; then - cp /stellar-core.cfg ./ -fi - -echo "using config:" -cat stellar-core.cfg - -# initialize new db -stellar-core new-db - -if [ "$1" = "standalone" ]; then - # initialize for new history archive path, remove any pre-existing on same path from base image - rm -rf ./history - stellar-core new-hist vs - - # serve history archives to horizon on port 1570 - pushd ./history/vs/ - python3 -m http.server 1570 & - popd -fi - -exec stellar-core run --console diff --git a/services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml b/services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml deleted file mode 100644 index 940c340..0000000 --- a/services/horizon/docker/docker-compose.integration-tests.soroban-rpc.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3' -services: - soroban-rpc: - platform: linux/amd64 - image: ${SOROBAN_RPC_IMAGE:-stellar/soroban-rpc} - depends_on: - - core - restart: on-failure - ports: - - "8080:8080" - environment: - - ENDPOINT=:8080 - - NETWORK_PASSPHRASE=Standalone Network ; February 2017 - - CAPTIVE_CORE_CONFIG_PATH=/captive-core.cfg - - HISTORY_ARCHIVE_URLS=http://core:1570 - - CHECKPOINT_FREQUENCY=8 - - LOG_LEVEL=debug - volumes: - - ./captive-core-integration-tests.soroban-rpc.cfg:/captive-core.cfg - diff --git a/services/horizon/docker/docker-compose.integration-tests.yml b/services/horizon/docker/docker-compose.integration-tests.yml deleted file mode 100644 index efe27ca..0000000 --- a/services/horizon/docker/docker-compose.integration-tests.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: '3' -services: - core-postgres: - image: postgres:9.6.17-alpine - restart: on-failure - environment: - - POSTGRES_PASSWORD=mysecretpassword - - POSTGRES_DB=stellar - ports: - - "5641:5641" - command: ["-p", "5641"] - core: - platform: linux/amd64 - # Note: Please keep the image pinned to an immutable tag matching the Captive Core version. - # This avoid implicit updates which break compatibility between - # the Core container and captive core. - image: ${CORE_IMAGE:-chowbao/stellar-core:19.11.1-1357.e38ee728d.focal-sorobanP10} - depends_on: - - core-postgres - restart: on-failure - environment: - - TRACY_NO_INVARIANT_CHECK=1 - ports: - - "11625:11625" - - "11626:11626" - # add extra port for history archive server - - "1570:1570" - entrypoint: /usr/bin/env - command: /start standalone - volumes: - - ./${CORE_CONFIG_FILE:-stellar-core-integration-tests.cfg}:/stellar-core.cfg - - ./core-start.sh:/start diff --git a/services/horizon/docker/docker-compose.pubnet.yml b/services/horizon/docker/docker-compose.pubnet.yml deleted file mode 100644 index 169045d..0000000 --- a/services/horizon/docker/docker-compose.pubnet.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: '3' -services: - horizon: - platform: linux/amd64 - environment: - - NETWORK=pubnet \ No newline at end of file diff --git a/services/horizon/docker/docker-compose.standalone.yml b/services/horizon/docker/docker-compose.standalone.yml deleted file mode 100644 index a537be0..0000000 --- a/services/horizon/docker/docker-compose.standalone.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: '3' -services: - core-postgres: - image: postgres:9.6.17-alpine - restart: on-failure - environment: - - POSTGRES_PASSWORD=mysecretpassword - - POSTGRES_DB=stellar - ports: - - "5641:5641" - command: ["-p", "5641"] - volumes: - - "core-db-data:/var/lib/postgresql/data" - - core: - platform: linux/amd64 - image: ${CORE_IMAGE:-stellar/stellar-core:19.11.0-1323.7fb6d5e88.focal} - depends_on: - - core-postgres - - core-upgrade - restart: on-failure - ports: - - "11625:11625" - - "11626:11626" - # add extra port for history archive server - - "1570:1570" - entrypoint: /usr/bin/env - command: /start standalone - volumes: - - ./stellar-core-standalone.cfg:/stellar-core.cfg - - ./core-start.sh:/start - extra_hosts: - - "host.docker.internal:host-gateway" - - horizon: - environment: - - HISTORY_ARCHIVE_URLS=http://host.docker.internal:1570 - - NETWORK_PASSPHRASE=Standalone Network ; February 2017 - - CAPTIVE_CORE_CONFIG_APPEND_PATH=/captive-core-standalone.cfg - - STELLAR_CORE_URL=http://host.docker.internal:11626 - volumes: - - ./captive-core-standalone.cfg:/captive-core-standalone.cfg - - # this container will invoke a request to upgrade stellar core to protocol 17 (by default) - core-upgrade: - restart: on-failure - image: curlimages/curl:7.69.1 - command: ["-v", "-f", "http://host.docker.internal:11626/upgrades?mode=set&upgradetime=1970-01-01T00:00:00Z&protocolversion=${PROTOCOL_VERSION:-18}"] - extra_hosts: - - "host.docker.internal:host-gateway" - -volumes: - core-db-data: diff --git a/services/horizon/docker/docker-compose.yml b/services/horizon/docker/docker-compose.yml deleted file mode 100644 index 309df94..0000000 --- a/services/horizon/docker/docker-compose.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: '3' -services: - horizon-postgres: - platform: linux/amd64 - image: postgres:12-bullseye - restart: on-failure - environment: - - POSTGRES_HOST_AUTH_METHOD=trust - - POSTGRES_DB=horizon - ports: - - "5432:5432" - volumes: - - "horizon-db-data:/var/lib/postgresql/data" - - horizon: - platform: linux/amd64 - depends_on: - - horizon-postgres - build: - # set build context to the root directory of the go monorepo - context: ../../../ - args: - STELLAR_CORE_VERSION: ${STELLAR_CORE_VERSION:-} - dockerfile: services/horizon/docker/Dockerfile.dev - restart: on-failure - ports: - - "8000:8000" - - "11725:11725" - environment: - - DATABASE_URL=postgres://postgres@host.docker.internal:5432/horizon?sslmode=disable - - NETWORK=testnet - - PER_HOUR_RATE_LIMIT=0 - command: ["--apply-migrations"] - extra_hosts: - - "host.docker.internal:host-gateway" - -volumes: - horizon-db-data: diff --git a/services/horizon/docker/start.sh b/services/horizon/docker/start.sh deleted file mode 100755 index 824fa19..0000000 --- a/services/horizon/docker/start.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -e - -NETWORK=${1:-testnet} - -case $NETWORK in - standalone) - DOCKER_FLAGS="-f docker-compose.yml -f docker-compose.standalone.yml" - echo "running on standalone network" - ;; - - pubnet) - DOCKER_FLAGS="-f docker-compose.yml -f docker-compose.pubnet.yml" - echo "running on public network" - ;; - - testnet) - echo "running on test network" - ;; - - *) - echo "$1 is not a supported option " - exit 1 - ;; -esac - -docker-compose $DOCKER_FLAGS up --build -d \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-classic-integration-tests.cfg b/services/horizon/docker/stellar-core-classic-integration-tests.cfg deleted file mode 100644 index e27cfe1..0000000 --- a/services/horizon/docker/stellar-core-classic-integration-tests.cfg +++ /dev/null @@ -1,24 +0,0 @@ -ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true - -NETWORK_PASSPHRASE="Standalone Network ; February 2017" - -PEER_PORT=11625 -HTTP_PORT=11626 -PUBLIC_HTTP_PORT=true - -NODE_SEED="SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" - -NODE_IS_VALIDATOR=true -UNSAFE_QUORUM=true -FAILURE_SAFETY=0 - -DATABASE="postgresql://user=postgres password=mysecretpassword host=core-postgres port=5641 dbname=stellar" - -[QUORUM_SET] -THRESHOLD_PERCENT=100 -VALIDATORS=["GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"] - -[HISTORY.vs] -get="cp history/vs/{0} {1}" -put="cp {0} history/vs/{1}" -mkdir="mkdir -p history/vs/{0}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-integration-tests.cfg b/services/horizon/docker/stellar-core-integration-tests.cfg deleted file mode 100644 index 53d5a98..0000000 --- a/services/horizon/docker/stellar-core-integration-tests.cfg +++ /dev/null @@ -1,25 +0,0 @@ -ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true -TESTING_SOROBAN_HIGH_LIMIT_OVERRIDE=true - -NETWORK_PASSPHRASE="Standalone Network ; February 2017" - -PEER_PORT=11625 -HTTP_PORT=11626 -PUBLIC_HTTP_PORT=true - -NODE_SEED="SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" - -NODE_IS_VALIDATOR=true -UNSAFE_QUORUM=true -FAILURE_SAFETY=0 - -DATABASE="postgresql://user=postgres password=mysecretpassword host=core-postgres port=5641 dbname=stellar" - -[QUORUM_SET] -THRESHOLD_PERCENT=100 -VALIDATORS=["GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"] - -[HISTORY.vs] -get="cp history/vs/{0} {1}" -put="cp {0} history/vs/{1}" -mkdir="mkdir -p history/vs/{0}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-pubnet.cfg b/services/horizon/docker/stellar-core-pubnet.cfg deleted file mode 100644 index b851fbb..0000000 --- a/services/horizon/docker/stellar-core-pubnet.cfg +++ /dev/null @@ -1,202 +0,0 @@ -# simple configuration for a standalone test "network" -# see stellar-core_example.cfg for a description of the configuration parameters - -HTTP_PORT=11626 -PUBLIC_HTTP_PORT=true -LOG_FILE_PATH="" - -DATABASE="postgresql://user=postgres password=mysecretpassword host=host.docker.internal port=5641 dbname=stellar" -NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015" -CATCHUP_RECENT=100 - -[HISTORY.cache] -get="cp /opt/stellar/history-cache/{0} {1}" - -[[HOME_DOMAINS]] -HOME_DOMAIN="stellar.org" -QUALITY="HIGH" - -[[HOME_DOMAINS]] -HOME_DOMAIN="satoshipay.io" -QUALITY="HIGH" - -[[HOME_DOMAINS]] -HOME_DOMAIN="lobstr.co" -QUALITY="HIGH" - -[[HOME_DOMAINS]] -HOME_DOMAIN="www.coinqvest.com" -QUALITY="HIGH" - -[[HOME_DOMAINS]] -HOME_DOMAIN="publicnode.org" -QUALITY="HIGH" - -[[HOME_DOMAINS]] -HOME_DOMAIN="stellar.blockdaemon.com" -QUALITY="HIGH" - -[[HOME_DOMAINS]] -HOME_DOMAIN = "www.franklintempleton.com" -QUALITY = "HIGH" - -[[VALIDATORS]] -NAME="sdf_1" -HOME_DOMAIN="stellar.org" -PUBLIC_KEY="GCGB2S2KGYARPVIA37HYZXVRM2YZUEXA6S33ZU5BUDC6THSB62LZSTYH" -ADDRESS="core-live-a.stellar.org:11625" -HISTORY="curl -sf https://history.stellar.org/prd/core-live/core_live_001/{0} -o {1}" - -[[VALIDATORS]] -NAME="sdf_2" -HOME_DOMAIN="stellar.org" -PUBLIC_KEY="GCM6QMP3DLRPTAZW2UZPCPX2LF3SXWXKPMP3GKFZBDSF3QZGV2G5QSTK" -ADDRESS="core-live-b.stellar.org:11625" -HISTORY="curl -sf https://history.stellar.org/prd/core-live/core_live_002/{0} -o {1}" - -[[VALIDATORS]] -NAME="sdf_3" -HOME_DOMAIN="stellar.org" -PUBLIC_KEY="GABMKJM6I25XI4K7U6XWMULOUQIQ27BCTMLS6BYYSOWKTBUXVRJSXHYQ" -ADDRESS="core-live-c.stellar.org:11625" -HISTORY="curl -sf https://history.stellar.org/prd/core-live/core_live_003/{0} -o {1}" - -[[VALIDATORS]] -NAME="satoshipay_singapore" -HOME_DOMAIN="satoshipay.io" -PUBLIC_KEY="GBJQUIXUO4XSNPAUT6ODLZUJRV2NPXYASKUBY4G5MYP3M47PCVI55MNT" -ADDRESS="stellar-sg-sin.satoshipay.io:11625" -HISTORY="curl -sf https://stellar-history-sg-sin.satoshipay.io/{0} -o {1}" - -[[VALIDATORS]] -NAME="satoshipay_iowa" -HOME_DOMAIN="satoshipay.io" -PUBLIC_KEY="GAK6Z5UVGUVSEK6PEOCAYJISTT5EJBB34PN3NOLEQG2SUKXRVV2F6HZY" -ADDRESS="stellar-us-iowa.satoshipay.io:11625" -HISTORY="curl -sf https://stellar-history-us-iowa.satoshipay.io/{0} -o {1}" - -[[VALIDATORS]] -NAME="satoshipay_frankfurt" -HOME_DOMAIN="satoshipay.io" -PUBLIC_KEY="GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE" -ADDRESS="stellar-de-fra.satoshipay.io:11625" -HISTORY="curl -sf https://stellar-history-de-fra.satoshipay.io/{0} -o {1}" - -[[VALIDATORS]] -NAME="lobstr_1_europe" -HOME_DOMAIN="lobstr.co" -PUBLIC_KEY="GCFONE23AB7Y6C5YZOMKUKGETPIAJA4QOYLS5VNS4JHBGKRZCPYHDLW7" -ADDRESS="v1.stellar.lobstr.co:11625" -HISTORY="curl -sf https://stellar-archive-1-lobstr.s3.amazonaws.com/{0} -o {1}" - -[[VALIDATORS]] -NAME="lobstr_2_europe" -HOME_DOMAIN="lobstr.co" -PUBLIC_KEY="GDXQB3OMMQ6MGG43PWFBZWBFKBBDUZIVSUDAZZTRAWQZKES2CDSE5HKJ" -ADDRESS="v2.stellar.lobstr.co:11625" -HISTORY="curl -sf https://stellar-archive-2-lobstr.s3.amazonaws.com/{0} -o {1}" - -[[VALIDATORS]] -NAME="lobstr_3_north_america" -HOME_DOMAIN="lobstr.co" -PUBLIC_KEY="GD5QWEVV4GZZTQP46BRXV5CUMMMLP4JTGFD7FWYJJWRL54CELY6JGQ63" -ADDRESS="v3.stellar.lobstr.co:11625" -HISTORY="curl -sf https://stellar-archive-3-lobstr.s3.amazonaws.com/{0} -o {1}" - -[[VALIDATORS]] -NAME="lobstr_4_asia" -HOME_DOMAIN="lobstr.co" -PUBLIC_KEY="GA7TEPCBDQKI7JQLQ34ZURRMK44DVYCIGVXQQWNSWAEQR6KB4FMCBT7J" -ADDRESS="v4.stellar.lobstr.co:11625" -HISTORY="curl -sf https://stellar-archive-4-lobstr.s3.amazonaws.com/{0} -o {1}" - -[[VALIDATORS]] -NAME="lobstr_5_australia" -HOME_DOMAIN="lobstr.co" -PUBLIC_KEY="GA5STBMV6QDXFDGD62MEHLLHZTPDI77U3PFOD2SELU5RJDHQWBR5NNK7" -ADDRESS="v5.stellar.lobstr.co:11625" -HISTORY="curl -sf https://stellar-archive-5-lobstr.s3.amazonaws.com/{0} -o {1}" - -[[VALIDATORS]] -NAME="coinqvest_hong_kong" -HOME_DOMAIN="www.coinqvest.com" -PUBLIC_KEY="GAZ437J46SCFPZEDLVGDMKZPLFO77XJ4QVAURSJVRZK2T5S7XUFHXI2Z" -ADDRESS="hongkong.stellar.coinqvest.com:11625" -HISTORY="curl -sf https://hongkong.stellar.coinqvest.com/history/{0} -o {1}" - -[[VALIDATORS]] -NAME="coinqvest_germany" -HOME_DOMAIN="www.coinqvest.com" -PUBLIC_KEY="GD6SZQV3WEJUH352NTVLKEV2JM2RH266VPEM7EH5QLLI7ZZAALMLNUVN" -ADDRESS="germany.stellar.coinqvest.com:11625" -HISTORY="curl -sf https://germany.stellar.coinqvest.com/history/{0} -o {1}" - -[[VALIDATORS]] -NAME="coinqvest_finland" -HOME_DOMAIN="www.coinqvest.com" -PUBLIC_KEY="GADLA6BJK6VK33EM2IDQM37L5KGVCY5MSHSHVJA4SCNGNUIEOTCR6J5T" -ADDRESS="finland.stellar.coinqvest.com:11625" -HISTORY="curl -sf https://finland.stellar.coinqvest.com/history/{0} -o {1}" - -[[VALIDATORS]] -NAME="bootes" -HOME_DOMAIN="publicnode.org" -PUBLIC_KEY="GCVJ4Z6TI6Z2SOGENSPXDQ2U4RKH3CNQKYUHNSSPYFPNWTLGS6EBH7I2" -ADDRESS="bootes.publicnode.org" -HISTORY="curl -sf https://bootes-history.publicnode.org/{0} -o {1}" - -[[VALIDATORS]] -NAME="hercules" -HOME_DOMAIN="publicnode.org" -PUBLIC_KEY="GBLJNN3AVZZPG2FYAYTYQKECNWTQYYUUY2KVFN2OUKZKBULXIXBZ4FCT" -ADDRESS="hercules.publicnode.org" -HISTORY="curl -sf https://hercules-history.publicnode.org/{0} -o {1}" - -[[VALIDATORS]] -NAME="lyra" -HOME_DOMAIN="publicnode.org" -PUBLIC_KEY="GCIXVKNFPKWVMKJKVK2V4NK7D4TC6W3BUMXSIJ365QUAXWBRPPJXIR2Z" -ADDRESS="lyra.publicnode.org" -HISTORY="curl -sf https://lyra-history.publicnode.org/{0} -o {1}" - -[[VALIDATORS]] -NAME="Blockdaemon_Validator_1" -HOME_DOMAIN="stellar.blockdaemon.com" -PUBLIC_KEY="GAAV2GCVFLNN522ORUYFV33E76VPC22E72S75AQ6MBR5V45Z5DWVPWEU" -ADDRESS="stellar-full-validator1.bdnodes.net" -HISTORY="curl -sf https://stellar-full-history1.bdnodes.net/{0} -o {1}" - -[[VALIDATORS]] -NAME="Blockdaemon_Validator_2" -HOME_DOMAIN="stellar.blockdaemon.com" -PUBLIC_KEY="GAVXB7SBJRYHSG6KSQHY74N7JAFRL4PFVZCNWW2ARI6ZEKNBJSMSKW7C" -ADDRESS="stellar-full-validator2.bdnodes.net" -HISTORY="curl -sf https://stellar-full-history2.bdnodes.net/{0} -o {1}" - -[[VALIDATORS]] -NAME="Blockdaemon_Validator_3" -HOME_DOMAIN="stellar.blockdaemon.com" -PUBLIC_KEY="GAYXZ4PZ7P6QOX7EBHPIZXNWY4KCOBYWJCA4WKWRKC7XIUS3UJPT6EZ4" -ADDRESS="stellar-full-validator3.bdnodes.net" -HISTORY="curl -sf https://stellar-full-history3.bdnodes.net/{0} -o {1}" - -[[VALIDATORS]] -NAME = "FT_SCV_1" -HOME_DOMAIN = "www.franklintempleton.com" -PUBLIC_KEY = "GARYGQ5F2IJEBCZJCBNPWNWVDOFK7IBOHLJKKSG2TMHDQKEEC6P4PE4V" -ADDRESS = "stellar1.franklintempleton.com:11625" -HISTORY = "curl -sf https://stellar-history-usw.franklintempleton.com/azuswshf401/{0} -o {1}" - -[[VALIDATORS]] -NAME = "FT_SCV_2" -HOME_DOMAIN = "www.franklintempleton.com" -PUBLIC_KEY = "GCMSM2VFZGRPTZKPH5OABHGH4F3AVS6XTNJXDGCZ3MKCOSUBH3FL6DOB" -ADDRESS = "stellar2.franklintempleton.com:11625" -HISTORY = "curl -sf https://stellar-history-usc.franklintempleton.com/azuscshf401/{0} -o {1}" - -[[VALIDATORS]] -NAME = "FT_SCV_3" -HOME_DOMAIN = "www.franklintempleton.com" -PUBLIC_KEY = "GA7DV63PBUUWNUFAF4GAZVXU2OZMYRATDLKTC7VTCG7AU4XUPN5VRX4A" -ADDRESS = "stellar3.franklintempleton.com:11625" -HISTORY = "curl -sf https://stellar-history-ins.franklintempleton.com/azinsshf401/{0} -o {1}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-standalone.cfg b/services/horizon/docker/stellar-core-standalone.cfg deleted file mode 100644 index a2b7e80..0000000 --- a/services/horizon/docker/stellar-core-standalone.cfg +++ /dev/null @@ -1,25 +0,0 @@ -# simple configuration for a standalone test "network" -# see stellar-core_example.cfg for a description of the configuration parameters - -NETWORK_PASSPHRASE="Standalone Network ; February 2017" - -PEER_PORT=11625 -HTTP_PORT=11626 -PUBLIC_HTTP_PORT=true - -NODE_SEED="SACJC372QBSSKJYTV5A7LWT4NXWHTQO6GHG4QDAVC2XDPX6CNNXFZ4JK" - -NODE_IS_VALIDATOR=true -UNSAFE_QUORUM=true -FAILURE_SAFETY=0 - -DATABASE="postgresql://user=postgres password=mysecretpassword host=host.docker.internal port=5641 dbname=stellar" - -[QUORUM_SET] -THRESHOLD_PERCENT=100 -VALIDATORS=["GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"] - -[HISTORY.vs] -get="cp history/vs/{0} {1}" -put="cp {0} history/vs/{1}" -mkdir="mkdir -p history/vs/{0}" \ No newline at end of file diff --git a/services/horizon/docker/stellar-core-testnet.cfg b/services/horizon/docker/stellar-core-testnet.cfg deleted file mode 100644 index cf8546a..0000000 --- a/services/horizon/docker/stellar-core-testnet.cfg +++ /dev/null @@ -1,41 +0,0 @@ -# simple configuration for a standalone test "network" -# see stellar-core_example.cfg for a description of the configuration parameters - -HTTP_PORT=11626 -PUBLIC_HTTP_PORT=true -LOG_FILE_PATH="" - -NETWORK_PASSPHRASE="Test SDF Network ; September 2015" - -DATABASE="postgresql://user=postgres password=mysecretpassword host=host.docker.internal port=5641 dbname=stellar" -UNSAFE_QUORUM=true -FAILURE_SAFETY=1 -CATCHUP_RECENT=100 - -[HISTORY.cache] -get="cp /opt/stellar/history-cache/{0} {1}" - -[[HOME_DOMAINS]] -HOME_DOMAIN="testnet.stellar.org" -QUALITY="HIGH" - -[[VALIDATORS]] -NAME="sdf_testnet_1" -HOME_DOMAIN="testnet.stellar.org" -PUBLIC_KEY="GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y" -ADDRESS="core-testnet1.stellar.org" -HISTORY="curl -sf http://history.stellar.org/prd/core-testnet/core_testnet_001/{0} -o {1}" - -[[VALIDATORS]] -NAME="sdf_testnet_2" -HOME_DOMAIN="testnet.stellar.org" -PUBLIC_KEY="GCUCJTIYXSOXKBSNFGNFWW5MUQ54HKRPGJUTQFJ5RQXZXNOLNXYDHRAP" -ADDRESS="core-testnet2.stellar.org" -HISTORY="curl -sf http://history.stellar.org/prd/core-testnet/core_testnet_002/{0} -o {1}" - -[[VALIDATORS]] -NAME="sdf_testnet_3" -HOME_DOMAIN="testnet.stellar.org" -PUBLIC_KEY="GC2V2EFSXN6SQTWVYA5EPJPBWWIMSD2XQNKUOHGEKB535AQE2I6IXV2Z" -ADDRESS="core-testnet3.stellar.org" -HISTORY="curl -sf http://history.stellar.org/prd/core-testnet/core_testnet_003/{0} -o {1}" diff --git a/testdata/perun_soroban_contract.wasm b/testdata/perun_soroban_contract.wasm index cbc3d1c0eb2161dfffe6cbd2e1faa1e46574dde8..c53bc3fb602da19b6e2c7fcb2acaaf469822ca1b 100755 GIT binary patch delta 3319 zcmZ8keQZ?65#QN;z7Id2y|w}S9NXP{w&5(>#Sr4N4Y+;=0%_C+)K;P&0uFN_KEOuC zDQ(qqM)`jEI(T(aQ%keg=kp04_xnYz*K2yY*Xzym1-!y1@^eIi!MQX9_e;(SivSHv$ylLo zg383F{b^<O0dV0UlnbET~*3IS`&pzM1fz8k9+}yJ*#unuD#kTcz zb@#=3qn&;13G>;W-p<%sSJ>p;(6w#r^L;Va?AzYe_uPiw=yui;h_3JH?ORJ`y1F;A zR&!&lZ*Avu(eCcpW|p%U*0QJc@1V+TTfXG!6<=POdoW9L45Az z;mV<8GRZ<*v0A}GLa}+QIbfKi?BZbtqm-fGag(6yy52 zlm`4O@wvvo6He({{?+iBZpp1EzD8b^DQ>4Bu6V6ba$I-iT5wJ8%WW*mNEyn*ar-ut z*YxjmUoRS=MaX-XIcvglSRXPQU|9dwoQ>8$Hpvfv-hJH98(2B5{(-CBE+=(YpaCZJ z;XnixuLqiNkthF{|F~n9$q7A?|7{r4o`Pq}hKNV7$)7uvAjs^-xE$0y1vTYaG1zGq zuSN}W%q(W8e_T%$bej9KtKRZqzh`D#{1NeE*S{v_)WS)=etMCa+k zH88Fx3U`$_8_S)fEc@wc6)BKYdTCMBtkd*v8<Kusk_I-$tsI*_8NfKgd>pWPoq8~OaYlXH=(X=+ZIC%EC6&|4 z#sFv0#KSo8xp0~MgpV7Ql(H?L?m66Ozfo*kp`O% zFRl!mEw_wTHclEIOdg*u=s%?H=ep$_3c3i(2|_zWwPBpaplMAR#>*8cO!2rpMhX%u zUjL<}Eb%|A!AuVBAc+7_I&LHB!f)R~5>X)T7>`pnh#W-{p&)VuNd$q&VI(npB2OWS z86$Ft$Ej?H96&C@L1aIYC6R<C3d=_bNdA}B(sMN$|Rk{C@ZB3 zA)DwEVI*wj`lZcaJsz9Q!EXi+kx3=OT#~t3QHtG+p2$ObB3K8f_4~m_yk@**C*W{u ztjxnRN2Q=P#-s9Lc@-X^H_Ok@bC1Rh-|^N(43Ds%(W{9yY2*R*xZxg`Z&w7r_H}?n z=IoQ3EO4R02wBHxu&d)5TNPl=35p>w7ik3;lmY|?XL*)PBj)hnT-EPagz>`qtfCy< zFRz@B?mttx9?qrCSN;<+?t_gThVGwHC55r-7wYmk)X{Z^5GpVCBMNplEf9i7u>$+^3%ESxm#%!xFBER+ z6P{@mI*T4{4?Ihz+v8ut!}0!e$8?)a!E`Y=smA1w(iXfOYXb|w_gX-=)#anZU#S~j zk=fAQFU0HvPG|WtomHSfc~p)HJVs50lUp>-0S?OSP%o;y-6|Iax}$!TM0IQ&TVxwg zkJLBM%rqC)ZaxA^6`7B4-l?Bin1_Xl7yJqG&aDG~LkQulYY0|2mvaXXq1y@?*vt6g zLDw_Ys+>qg8%~>$)~i~S4!49IY8|9g=UW8e%j>Pbfy?>_3+KTVJ-KiXyrUN^8o=k} zMSJBtv{?x7ro`khd0DSnyr($J#E%S4Uk*8v7S7g{ZBbm7Y_k^K+=Cg9hdFEF203l- z?w@8bv(^d3=6x2}lMe*0<{$N6+a7~Use5fdgrfJHSj`vxfZz1VG&4U<^|$+lcYSno zw7WB=4=!ofHTx`dqXoy8a4f^I631#iv9zw{J4kKB(TigTj+by8 z#xaWH9FDheyr=IiJ;HZ2re0ZAS&Sbgj6M2)0%Jf-{29>=8+vgr#z8zYHM#nt@qZ&T BL|Om< delta 2932 zcmZ8jd2E!&6`wcr?fH4VA6?@E1A#S5O#E5FII0vNt*V45H4bbN7O;tN zf~vIalC)r23SNf8gd^t0T(Q~6iXubY5Yeb^RobG;YN8gT9!XlMDB=&A2GTd*VNzCl z-@JMAX6C)$J7%w4hQGcCD(Tp`9ta_j3T4$5g*2TeD+yVtr11x7`ZFlbZhpem0S$y^ zuQkn`KhNX!dYPB{e9Z6hs2=L^c=Egf5A(8o7gK0ZVKhhyF919>7$k&?1yGv4(>q4g zG%+&Uar9&$&%HjmzN`BOlqg*rlAWYMdEu4L)nu+~)w-^YN%DyLLRa^yWV4`eL%Debq}Hot?>bWFfSZCI0sI z)g3(@?a5B@Hz-jbU2Htj+ScOV`lETmM;F5Zv6Rk+i((5c!S6vDD?OUYWJr{1q?(Z^ z)5vVn7*#Zo^9FuO+@jTRG^es(0vr;5P#%G|gx6IGM?}4=1o<}CHP|hJ?g;D@Gu`vx zs95Qa!HC%I)=fJr%G=lzVr; zVKL%zoDtvl&nO(1r$sbsj6q7Cpz%TRnqP-;am3#cvRCms zBbG8gC45}m^pAvW6O50DxhKYti!*8+92b97tMK%D>I^t3rsdtk`$_`KCe+KnkPZBa>Oj|1>GNKz4ok4SL5s>yF{Ax zyf_=PCyja_&fc{rF+D%d&e#)8560PmJqhbP&ic)XanrJ(AkNY^Es1SYD9)~1f;}bz zdSQa>F)c8JB`8V|VF`9yf+-2I+Y(@2AeQ>n1j$%}yVfSWkb7jlpXoZQMi_9hd79$XfUt*O5i%rF1V%$#6(^53-1WWCgP5yJUAGi(X4M zi!6prvVAlq(?_xylp+d}y^H4-$J6G)w~J5|3!o#UI{!@t#*4z?MOcIh)?z%2cr%qYi}FgWAVY^&65xxB%?8cXuLoT?a9B@bi~DOUOKC@m3R zmR4cQjFwHus}_~b!K+>_dmb+3Zj^lv_EjM33A@aAR9^M(=&*g2E&t|Kkq1{X>nf&d z=&<;muB#(blc+wzFN^znJ$hSL{_6DoQmU~S4F_<*2O|m^lbwX}{o?&{Y1b#^5`sX* zmMOzhd2xWDMlYC6*3XB<@rs?!3O1N;uC!JjgMRT@fm%}y93M3 zw3#+*P|duj5m)T7;gpd3~*_*-&`|6GjLHP+h?3#MK^Vk}#7 z{$v$^fnl&6u(M-UeN=*6UxFa7t)B+xa@*=psc=QqC)&)fCiW+q;Yw~I!2kuHEW8R= z#lWMp;X_gSSd}=tC?xz%OJPi`XzIi7Kby7}jhP-m9+l4l;aBBU{I^L*<&HvU5g$DH zENCt+588%XPd%nTePbJfkcv? zX%SKz(lRlzxTf;E$h9M-kX}W49cdTRA*4a15u^`Atn~o>?X29@*3!b#gV5E%$^XHzn>f_FG(ptGTi)-zyZ8IJR`w)&RwD(NXx29Y9^GDi5LR9R zV_uEwM>vOb3>sDkc64Grqj?Q6Vucno%)sucq#z0r2DI!r4oXQV#ImpJbhzNYlpZaIUrHpv!%aA`2iBBvI5}rwH{3E2aC!+=SenobT d0bPMlzMO2t!)vAOy{+BV!c+#y6S^O4DPQ$Xhm-&S delta 570 zcmb7>ze)o^5XNtIE?|VqZ4$xru$Y`pt}<2;gGE3=D{UgSL90~uA{^KWiZCb$Hi7uB zu#m$S5Wy#~w6PL>0Kv+g+Y3RhY;M1u-Tl7b%pJ5ITgQFuLgQ|*4e*r~)$TYvaZ;RQ z-}g6FJLnPU^CU?eQZWYW%U&iZ0#faIFGeRqeJSVqRKAg`_hk+BP4>tkAk>3QD{`7x zCq*uM-V7kmdGD~s)1|N&LH;bAFEs&9c*pNFtbfhikCAExa`HP=AeCp1LQIX{{3Voi zR^P%kzv$H!!*39c1;(gr?#ROZVD2YxMgHgZc8D*cmcI0;Jy zI5`(Tvrt|28g^ e((aJ**BH`|YUIKvmx3!koSNXha4dZe?%i)QC4=t( diff --git a/util/deploy.go b/util/deploy.go new file mode 100644 index 0000000..15a3113 --- /dev/null +++ b/util/deploy.go @@ -0,0 +1,59 @@ +package util + +import ( + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" + "github.com/stellar/go/xdr" + "perun.network/perun-stellar-backend/channel" + + "perun.network/perun-stellar-backend/channel/env" +) + +func Deploy(kp *keypair.Full, contractPath string) (xdr.ScAddress, xdr.Hash) { + // Install contract + deployerClient := env.NewStellarClient(kp) //.GetHorizonClient() + hzClient := deployerClient.GetHorizonClient() + deployerAccReq := horizonclient.AccountRequest{AccountID: kp.Address()} + deployerAcc, err := hzClient.AccountDetail(deployerAccReq) + if err != nil { + panic(err) + } + + installContractOpInstall := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) + preFlightOp, minFeeInstall := env.PreflightHostFunctions(hzClient, &deployerAcc, *installContractOpInstall) + txParamsInstall := env.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeInstall), &preFlightOp) + txSignedInstall, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsInstall) + if err != nil { + panic(err) + } + _, err = hzClient.SubmitTransaction(txSignedInstall) + if err != nil { + panic(err) + } + + // Create the contract + createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", NETWORK_PASSPHRASE) + preFlightOpCreate, minFeeCreate := env.PreflightHostFunctions(hzClient, &deployerAcc, *createContractOp) + txParamsCreate := env.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeCreate), &preFlightOpCreate) + txSignedCreate, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsCreate) + if err != nil { + panic(err) + } + _, err = hzClient.SubmitTransaction(txSignedCreate) + if err != nil { + panic(err) + } + contractID := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + contractHash := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash + contractIDAddress := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: contractID, + } + + return contractIDAddress, contractHash +} + +// func MintAsset() { +// env.BuildMint + +// } diff --git a/util/util.go b/util/util.go index 4d12ad3..4dab2c1 100644 --- a/util/util.go +++ b/util/util.go @@ -1,34 +1,81 @@ package util import ( + "crypto/rand" + "encoding/binary" + "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" + "log" + mathrand "math/rand" "perun.network/perun-stellar-backend/channel" "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/channel/types" "perun.network/perun-stellar-backend/wallet" - - "crypto/rand" - "encoding/binary" - mathrand "math/rand" ) -func Deploy(stellarEnv *env.IntegrationTestEnv, kp *keypair.Full, hz horizon.Account, contractPath string) (xdr.ScAddress, xdr.Hash) { +const NETWORK_PASSPHRASE = "Standalone Network ; February 2017" + +type StellarClient struct { + horizonClient *horizonclient.Client +} + +func (s *StellarClient) SubmitTx(tx *txnbuild.Transaction) error { + _, err := s.horizonClient.SubmitTransaction(tx) + if err != nil { + panic(err) + } + return err +} + +func CreateSignedTransaction(signers []*keypair.Full, txParams txnbuild.TransactionParams, +) (*txnbuild.Transaction, error) { + tx, err := txnbuild.NewTransaction(txParams) + if err != nil { + return nil, err + } + + for _, signer := range signers { + tx, err = tx.Sign(NETWORK_PASSPHRASE, signer) + if err != nil { + return nil, err + } + } + return tx, nil +} + +func DeployStandard(hzClient *env.StellarClient, kp *keypair.Full, hz horizon.Account, contractPath string) (xdr.ScAddress, xdr.Hash) { // Install contract - installContractOp := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) - preFlightOp, minFee := stellarEnv.PreflightHostFunctions(&hz, *installContractOp) - _ = stellarEnv.MustSubmitOperationsWithFee(&hz, kp, minFee, &preFlightOp) + cl := hzClient.GetHorizonClient() + + installContractOpInstall := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) + preFlightOp, minFeeInstall := env.PreflightHostFunctions(cl, &hz, *installContractOpInstall) + txParamsInstall := env.GetBaseTransactionParamsWithFee(&hz, int64(minFeeInstall), &preFlightOp) + txSignedInstall, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsInstall) + if err != nil { + panic(err) + } + _, err = cl.SubmitTransaction(txSignedInstall) + if err != nil { + panic(err) + } // Create the contract - createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", stellarEnv.GetPassPhrase()) - preFlightOp, minFee = stellarEnv.PreflightHostFunctions(&hz, *createContractOp) - _, err := stellarEnv.SubmitOperationsWithFee(&hz, kp, minFee, &preFlightOp) + createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", NETWORK_PASSPHRASE) + preFlightOpCreate, minFeeCreate := env.PreflightHostFunctions(cl, &hz, *createContractOp) + txParamsCreate := env.GetBaseTransactionParamsWithFee(&hz, int64(minFeeCreate), &preFlightOpCreate) + txSignedCreate, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsCreate) + if err != nil { + panic(err) + } + _, err = cl.SubmitTransaction(txSignedCreate) if err != nil { panic(err) } - contractID := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId - contractHash := preFlightOp.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash + contractID := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + contractHash := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash contractIDAddress := xdr.ScAddress{ Type: xdr.ScAddressTypeScAddressTypeContract, ContractId: contractID, @@ -37,20 +84,110 @@ func Deploy(stellarEnv *env.IntegrationTestEnv, kp *keypair.Full, hz horizon.Acc } +func CreateFundNewRandomStellarKP(count int, initialBalance string) ([]*keypair.Full, []txnbuild.Account) { + + masterClient := env.NewHorizonMasterClient() + masterHzClient := masterClient.GetMaster() + sourceKey := masterClient.GetSourceKey() + + hzClient := env.NewHorizonClient() + + pairs := make([]*keypair.Full, count) + ops := make([]txnbuild.Operation, count) + + // kp := keypair.MustRandom() + accReq := horizonclient.AccountRequest{AccountID: sourceKey.Address()} + sourceAccount, err := masterHzClient.AccountDetail(accReq) + if err != nil { + panic(err) + } + + masterAccount := txnbuild.SimpleAccount{ + AccountID: sourceKey.Address(), + Sequence: sourceAccount.Sequence, + } + + // use masteraccount to generate new accounts + for i := 0; i < count; i++ { + pair, _ := keypair.Random() + pairs[i] = pair + + ops[i] = &txnbuild.CreateAccount{ + SourceAccount: masterAccount.AccountID, + Destination: pair.Address(), + Amount: initialBalance, + } + } + + txParams := env.GetBaseTransactionParamsWithFee(&masterAccount, txnbuild.MinBaseFee, ops...) + + txSigned, err := env.CreateSignedTransactionWithParams([]*keypair.Full{sourceKey}, txParams) + + if err != nil { + panic(err) + } + _, err = hzClient.SubmitTransaction(txSigned) + if err != nil { + panic(err) + } + + accounts := make([]txnbuild.Account, count) + for i, kp := range pairs { + request := horizonclient.AccountRequest{AccountID: kp.Address()} + account, err := hzClient.AccountDetail(request) + if err != nil { + panic(err) + } + + accounts[i] = &account + } + + for _, keys := range pairs { + log.Printf("Funded %s (%s) with %s XLM.\n", + keys.Seed(), keys.Address(), initialBalance) + } + + return pairs, accounts +} + +func InitTokenContract(kp *keypair.Full, contractIDAddress xdr.ScAddress) error { + + stellarClient := env.NewStellarClient(kp) + // cl := stellarClient.GetHorizonClient() + adminScAddr, err := types.MakeAccountAddress(kp) + if err != nil { + panic(err) + } + + tokenParams := channel.NewTokenParams() + decimals := tokenParams.GetDecimals() + name := tokenParams.GetName() + symbol := tokenParams.GetSymbol() + + initArgs, err := channel.BuildInitTokenArgs(adminScAddr, decimals, name, symbol) + if err != nil { + panic(err) + } + auth := []xdr.SorobanAuthorizationEntry{} + _, err = stellarClient.InvokeAndProcessHostFunction("initialize", initArgs, contractIDAddress, kp, auth) + if err != nil { + panic(err) + } + + return nil +} + func MakeRandPerunWallet() (*wallet.EphemeralWallet, *wallet.Account, *keypair.Full) { w := wallet.NewEphemeralWallet() - // Read 8 bytes from crypto/rand var b [8]byte _, err := rand.Read(b[:]) if err != nil { panic(err) } - // Convert 8 bytes to uint64 for seeding math/rand seed := binary.LittleEndian.Uint64(b[:]) - // Create a math/rand.Rand with the seed r := mathrand.New(mathrand.NewSource(int64(seed))) acc, kp, err := w.AddNewAccount(r) @@ -60,15 +197,51 @@ func MakeRandPerunWallet() (*wallet.EphemeralWallet, *wallet.Account, *keypair.F return w, acc, kp } -func NewRandAsset() types.StellarAsset { - var contractID xdr.Hash - _, err := rand.Read(contractID[:]) +func NewAssetFromScAddress(contractAddr xdr.ScAddress) types.StellarAsset { + contractAsset, err := types.NewStellarAssetFromScAddress(contractAddr) if err != nil { panic(err) } + return *contractAsset +} + +func i128Param(hi int64, lo uint64) xdr.ScVal { + i128 := &xdr.Int128Parts{ + Hi: xdr.Int64(hi), + Lo: xdr.Uint64(lo), + } + return xdr.ScVal{ + Type: xdr.ScValTypeScvI128, + I128: i128, + } +} - stellarAsset := types.NewStellarAsset(contractID) +func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount int64, recipientAddr xdr.ScAddress) error { //stellarCl *env.StellarClient, + stellarClient := env.NewStellarClient(kp) + // cl := stellarClient.GetHorizonClient() - return *stellarAsset + // amount128 := i128Param(0, amount) + amountSc, err := xdr.NewScVal(xdr.ScValTypeScvI64, xdr.Int64(amount)) + if err != nil { + panic(err) + } + mintTokenArgs, err := env.BuildMintTokenArgs(recipientAddr, amountSc) + if err != nil { + panic(err) + } + // contractAsset := NewAssetFromScAddress(contractAddr) + _, err = stellarClient.InvokeAndProcessHostFunction("mint", mintTokenArgs, contractAddr, kp, []xdr.SorobanAuthorizationEntry{}) + if err != nil { + panic(err) + } + return nil +} + +func MakeAccountAddress(kp keypair.KP) (xdr.ScAddress, error) { + accountId, err := xdr.AddressToAccountId(kp.Address()) + if err != nil { + return xdr.ScAddress{}, err + } + return xdr.NewScAddress(xdr.ScAddressTypeScAddressTypeAccount, accountId) } diff --git a/wire/invocation.go b/wire/invocation.go new file mode 100644 index 0000000..6e9dbc2 --- /dev/null +++ b/wire/invocation.go @@ -0,0 +1,36 @@ +package wire + +import ( + "github.com/stellar/go/xdr" + "perun.network/perun-stellar-backend/wire/scval" +) + +func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName string, tokenSymbol string) (xdr.ScVec, error) { + + adminScAddr, err := scval.WrapScAddress(adminAddr) + if err != nil { + panic(err) + } + + decim := xdr.Uint32(decimals) + scvaltype := xdr.ScValTypeScvU32 + decimSc, err := xdr.NewScVal(scvaltype, decim) + if err != nil { + panic(err) + } + + tokenNameScString := xdr.ScString(tokenName) + tokenNameXdr := scval.MustWrapScString(tokenNameScString) + + tokenSymbolString := xdr.ScString(tokenSymbol) + tokenSymbolXdr := scval.MustWrapScString(tokenSymbolString) + + initTokenArgs := xdr.ScVec{ + adminScAddr, + decimSc, + tokenNameXdr, + tokenSymbolXdr, + } + + return initTokenArgs, nil +} From fd4d0e6bf2e10152eb49828eeff000b248da6939 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Sun, 17 Dec 2023 21:04:54 +0100 Subject: [PATCH 13/51] main: Create keypairs in wallet package instead of keypair API. Include Payments between clients channel: Increase buffer size to arbitrarily high value, include "transfer" event hotfix. env: Add BuildGetTokenBalanceArgs function. util: Add wrapping functions to generate accounts and to make it easier to create a payment demo. --- channel/adjudicator.go | 17 +++++---- channel/adjudicator_sub.go | 4 +-- channel/env/transaction.go | 23 ++++++++++--- channel/event.go | 10 ++++-- channel/funder.go | 70 +++++++++++++++----------------------- main.go | 60 ++++++++++++++++++++------------ 6 files changed, 103 insertions(+), 81 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 7aa31de..c3393bb 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -14,10 +14,9 @@ import ( "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/wallet" - "time" - "perun.network/perun-stellar-backend/wire" "perun.network/perun-stellar-backend/wire/scval" + "time" ) var ErrChannelAlreadyClosed = errors.New("Channel is already closed") @@ -72,10 +71,10 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs) if err != nil { - // getChanArgs, err := env.BuildGetChannelTxArgs(req.Tx.ID) - if err != nil { - panic(err) - } + panic(err) + } + if err != nil { + chanControl, err := a.GetChannelState(ctx, req.Tx.State) if err != nil { return err @@ -197,9 +196,7 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) perunAddress := a.GetPerunID() kp := a.kpFull - // hzAcc := a.stellarClient.GetHorizonAcc() - // generate tx to open the channel withdrawTxArgs, err := a.BuildWithdrawTxArgs(req) if err != nil { return errors.New("error while building fund tx") @@ -219,10 +216,10 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) } func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig) error { + log.Println("Close called") contractAddress := a.GetPerunID() kp := a.kpFull - // hzAcc := a.stellarClient.GetHorizonAcc() closeTxArgs, err := BuildCloseTxArgs(*state, sigs) if err != nil { return errors.New("error while building fund tx") @@ -232,10 +229,12 @@ func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel if err != nil { return errors.New("error while invoking and processing host function: close") } + _, err = DecodeEventsPerun(txMeta) if err != nil { return errors.New("error while decoding events") } + return nil } diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index d295476..eef1dff 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -14,8 +14,8 @@ import ( ) const ( - DefaultBufferSize = 3 - DefaultSubscriptionPollingInterval = time.Duration(10) * time.Second + DefaultBufferSize = 1024 + DefaultSubscriptionPollingInterval = time.Duration(5) * time.Second ) type AdjEvent interface { diff --git a/channel/env/transaction.go b/channel/env/transaction.go index f34f13f..6b09b5e 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -62,7 +62,7 @@ func PreflightHostFunctions(hzClient *horizonclient.Client, if err != nil { panic(err) } - fmt.Printf("Result:\n\n%# +v\n\n", pretty.Formatter(decodedRes)) + // fmt.Printf("Result:\n\n%# +v\n\n", pretty.Formatter(decodedRes)) for _, authBase64 := range res.Auth { var authEntry xdr.SorobanAuthorizationEntry err = xdr.SafeUnmarshalBase64(authBase64, &authEntry) @@ -115,6 +115,7 @@ func simulateTransaction(hzClient *horizonclient.Client, panic(err) } fmt.Printf("Transaction Data:\n\n%# +v\n\n", pretty.Formatter(transactionData)) + // fmt.Printf("Result:\n\n%# +v\n\n", pretty.Formatter(result)) return result, transactionData } func syncWithSorobanRPC(ledgerToWaitFor uint32) { @@ -168,7 +169,7 @@ func (h *StellarClient) TestInteractContract(ctx context.Context, kp *keypair.Fu tokenAddr := tokenAddress tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) - testArgs := xdr.ScVec{tokenAddrXdr} + testArgs := xdr.ScVec{tokenAddrXdr, tokenAddrXdr} auth := []xdr.SorobanAuthorizationEntry{} @@ -219,6 +220,20 @@ func BuildMintTokenArgs(mintTo xdr.ScAddress, amount xdr.ScVal) (xdr.ScVec, erro return MintTokenArgs, nil } +func BuildGetTokenBalanceArgs(balanceOf xdr.ScAddress) (xdr.ScVec, error) { + + balanceOfSc, err := scval.WrapScAddress(balanceOf) + if err != nil { + panic(err) + } + + GetTokenBalanceArgs := xdr.ScVec{ + balanceOfSc, + } + + return GetTokenBalanceArgs, nil +} + func BuildFundTxArgs(chanID pchannel.ID, funderIdx bool) (xdr.ScVec, error) { chanIDStellar := chanID[:] @@ -314,9 +329,9 @@ func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xd if err != nil { return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") } - fmt.Println("txMeta: ", txMeta) + fmt.Println("txMetaof calling: ", fname, ": ", txMeta) retval := txMeta.V3.SorobanMeta.ReturnValue - fmt.Println("retval: ", retval) + fmt.Println("retval of calling: ", fname, ": ", retval) return txMeta, nil } diff --git a/channel/event.go b/channel/event.go index 62c32cf..7a0fb71 100644 --- a/channel/event.go +++ b/channel/event.go @@ -206,6 +206,12 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { panic(ErrNotStellarPerunContract) } perunString, ok := topics[0].GetSym() + + if perunString == "transfer" { + // TODO: Improve this fix + continue + } + if perunString != AssertPerunSymbol { panic(ErrNotStellarPerunContract) } @@ -244,7 +250,6 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { } evs = append(evs, &openEvent) - log.Println("OpenEvent: ", openEvent) log.Println("OpenEvent-Params: ", openEvent.Channel.Params) log.Println("OpenEvent-State: ", openEvent.Channel.State) log.Println("OpenEvent-Control: ", openEvent.Channel.Control) @@ -259,7 +264,7 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { Channel: fundEventchanStellar, } evs = append(evs, &fundEvent) - + log.Println("FundEvent: ", fundEvent) case EventTypeClosed: closedEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) if err != nil { @@ -270,6 +275,7 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { Channel: closedEventchanStellar, } evs = append(evs, &closeEvent) + log.Println("CloseEvent: ", closeEvent) case EventTypeWithdrawn: withdrawnEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) if err != nil { diff --git a/channel/funder.go b/channel/funder.go index 783cbc4..a2d8475 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -2,25 +2,22 @@ package channel import ( "context" - // "crypto/rand" - // "crypto/sha256" + "crypto/rand" + "crypto/sha256" "errors" "fmt" "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" - // "github.com/stellar/go/xdr" "log" - // "math/big" + "math/big" pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/wallet" "perun.network/perun-stellar-backend/wire" - "perun.network/perun-stellar-backend/wire/scval" - "time" ) @@ -171,7 +168,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state tokenAddress := f.GetAssetID() kp := f.kpFull - // hzAcc := f.stellarClient.GetHorizonAcc() chanId := state.ID // generate tx to open the channel @@ -191,16 +187,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state panic("tokenIDAddrFromBals not equal to tokenContractAddress") } - // tokenAddr := tokenContractAddress - tokenAddrXdr := scval.MustWrapScAddress(tokenAddress) - - testArgs := xdr.ScVec{tokenAddrXdr} - // hzAcctInt0 := f.stellarClient.GetHorizonAcc() - _, err = f.stellarClient.InvokeAndProcessHostFunction("testinteract", testArgs, perunAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx - if err != nil { - return errors.New("error while invoking and processing host function: testinteract") - } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("fund", fundTxArgs, perunAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx if err != nil { return errors.New("error while invoking and processing host function: fund") @@ -214,31 +200,31 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state return nil } -// func makePreImgAuth(passphrase string, rootInv xdr.SorobanAuthorizedInvocation) (xdr.HashIdPreimage, error) { -// max := big.NewInt(0).SetInt64(int64(^uint64(0) >> 1)) -// randomPart, err := rand.Int(rand.Reader, max) -// if err != nil { -// panic(err) -// } -// networkId := xdr.Hash(sha256.Sum256([]byte(passphrase))) -// ledgerEntry := uint32(100) -// lEntryXdr := xdr.Uint32(ledgerEntry) -// nonce := randomPart.Int64() -// ncXdr := xdr.Int64(nonce) - -// srbPreImgAuth := xdr.HashIdPreimageSorobanAuthorization{ -// NetworkId: networkId, -// Nonce: ncXdr, -// SignatureExpirationLedger: lEntryXdr, -// Invocation: rootInv, -// } - -// srbAuth := xdr.HashIdPreimage{ -// Type: xdr.EnvelopeTypeEnvelopeTypeSorobanAuthorization, -// SorobanAuthorization: &srbPreImgAuth} - -// return srbAuth, nil -// } +func makePreImgAuth(passphrase string, rootInv xdr.SorobanAuthorizedInvocation) (xdr.HashIdPreimage, error) { + max := big.NewInt(0).SetInt64(int64(^uint64(0) >> 1)) + randomPart, err := rand.Int(rand.Reader, max) + if err != nil { + panic(err) + } + networkId := xdr.Hash(sha256.Sum256([]byte(passphrase))) + ledgerEntry := uint32(100) + lEntryXdr := xdr.Uint32(ledgerEntry) + nonce := randomPart.Int64() + ncXdr := xdr.Int64(nonce) + + srbPreImgAuth := xdr.HashIdPreimageSorobanAuthorization{ + NetworkId: networkId, + Nonce: ncXdr, + SignatureExpirationLedger: lEntryXdr, + Invocation: rootInv, + } + + srbAuth := xdr.HashIdPreimage{ + Type: xdr.EnvelopeTypeEnvelopeTypeSorobanAuthorization, + SorobanAuthorization: &srbPreImgAuth} + + return srbAuth, nil +} func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { diff --git a/main.go b/main.go index 344d614..735c4ee 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,20 @@ package main import ( - "context" + // "context" "fmt" + + "github.com/stellar/go/keypair" + // "github.com/stellar/go/txnbuild" + // "github.com/stellar/go/xdr" + "log" + "perun.network/go-perun/wire" - "perun.network/perun-stellar-backend/channel/env" + // "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/client" "perun.network/perun-stellar-backend/util" + // "perun.network/perun-stellar-backend/wallet" ) const PerunContractPath = "./testdata/perun_soroban_contract.wasm" @@ -15,17 +22,20 @@ const StellarAssetContractPath = "./testdata/perun_soroban_token.wasm" func main() { - kps, _ := util.CreateFundNewRandomStellarKP(4, "1000000") - kpAlice := kps[0] - kpBob := kps[1] - kpDeployerToken := kps[2] - kpDeployerPerun := kps[3] + wAlice, accAlice, kpAlice := util.MakeRandPerunWallet() + wBob, accBob, kpBob := util.MakeRandPerunWallet() + _, _, kpDepToken := util.MakeRandPerunWallet() + _, _, kpDepPerun := util.MakeRandPerunWallet() + kps := []*keypair.Full{kpAlice, kpBob, kpDepToken, kpDepPerun} + + err := util.CreateFundStellarAccounts(kps, len(kps), "1000000") + if err != nil { + panic(err) + } - tokenAddr, assetHash := util.Deploy(kpDeployerToken, StellarAssetContractPath) - // util.MintAsset(kpDeployerToken, tokenAddr, kpAlice.Address(), 1000000) - // util.MintAsset(kpDeployerToken, tokenAddr, kpBob.Address(), 1000000) + tokenAddr, assetHash := util.Deploy(kpDepToken, StellarAssetContractPath) - err := util.InitTokenContract(kpDeployerToken, tokenAddr) + err = util.InitTokenContract(kpDepToken, tokenAddr) if err != nil { panic(err) } @@ -40,22 +50,23 @@ func main() { panic(err) } - err = util.MintToken(kpDeployerToken, tokenAddr, int64(10000000000), aliceAddr) + err = util.MintToken(kpDepToken, tokenAddr, int64(10000000000), aliceAddr) if err != nil { panic(err) } - err = util.MintToken(kpDeployerToken, tokenAddr, int64(10000000000), bobAddr) + err = util.MintToken(kpDepToken, tokenAddr, int64(10000000000), bobAddr) if err != nil { panic(err) } - perunAddr, perunHash := util.Deploy(kpDeployerPerun, PerunContractPath) + perunAddr, perunHash := util.Deploy(kpDepPerun, PerunContractPath) fmt.Println("assetID, assetHash, perunID, perunHas: ", perunAddr, assetHash, perunHash) - // // Generate L2 accounts for the payment channel - wAlice, accAlice, _ := util.MakeRandPerunWallet() - wBob, accBob, _ := util.MakeRandPerunWallet() + err = util.MintToken(kpDepToken, tokenAddr, int64(10000000000), perunAddr) + if err != nil { + panic(err) + } bus := wire.NewLocalBus() alicePerun, err := client.SetupPaymentClient(wAlice, accAlice, kpAlice, tokenAddr, perunAddr, bus) @@ -68,22 +79,27 @@ func main() { if err != nil { panic(err) } - alicePerun.OpenChannel(bobPerun.WireAddress(), 10) + alicePerun.OpenChannel(bobPerun.WireAddress(), 100) aliceChannel := alicePerun.Channel bobChannel := bobPerun.AcceptedChannel() + aliceChannel.SendPayment(10) + bobChannel.SendPayment(2) + aliceChannel.Settle() bobChannel.Settle() alicePerun.Shutdown() bobPerun.Shutdown() - log.Println("Done") - // // initialize the contract + fmt.Println("Get Balances: ") - cl := env.NewStellarClient(kpAlice) + err = util.GetTokenBalance(kpAlice, tokenAddr, aliceAddr) + if err != nil { + panic(err) + } - err = cl.TestInteractContract(context.TODO(), kpAlice, tokenAddr, perunAddr) + err = util.GetTokenBalance(kpBob, tokenAddr, bobAddr) if err != nil { panic(err) } From 04027af3ba41a39d4ae62329478fa1579e7f6fc4 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Sun, 17 Dec 2023 21:06:48 +0100 Subject: [PATCH 14/51] util: Add wrapping functions for payment demo. --- util/util.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/util/util.go b/util/util.go index 4dab2c1..dcbd15d 100644 --- a/util/util.go +++ b/util/util.go @@ -150,10 +150,76 @@ func CreateFundNewRandomStellarKP(count int, initialBalance string) ([]*keypair. return pairs, accounts } +func CreateFundStellarAccounts(pairs []*keypair.Full, count int, initialBalance string) error { + + masterClient := env.NewHorizonMasterClient() + masterHzClient := masterClient.GetMaster() + sourceKey := masterClient.GetSourceKey() + + hzClient := env.NewHorizonClient() + + // pairs := make([]*keypair.Full, count) + ops := make([]txnbuild.Operation, count) + + // kp := keypair.MustRandom() + accReq := horizonclient.AccountRequest{AccountID: sourceKey.Address()} + sourceAccount, err := masterHzClient.AccountDetail(accReq) + if err != nil { + panic(err) + } + + masterAccount := txnbuild.SimpleAccount{ + AccountID: sourceKey.Address(), + Sequence: sourceAccount.Sequence, + } + + // use masteraccount to generate new accounts + for i := 0; i < count; i++ { + // pair, _ := keypair.Random() + pair := pairs[i] + // pairs[i] = pair + + ops[i] = &txnbuild.CreateAccount{ + SourceAccount: masterAccount.AccountID, + Destination: pair.Address(), + Amount: initialBalance, + } + } + + txParams := env.GetBaseTransactionParamsWithFee(&masterAccount, txnbuild.MinBaseFee, ops...) + + txSigned, err := env.CreateSignedTransactionWithParams([]*keypair.Full{sourceKey}, txParams) + + if err != nil { + panic(err) + } + _, err = hzClient.SubmitTransaction(txSigned) + if err != nil { + panic(err) + } + + accounts := make([]txnbuild.Account, count) + for i, kp := range pairs { + request := horizonclient.AccountRequest{AccountID: kp.Address()} + account, err := hzClient.AccountDetail(request) + if err != nil { + panic(err) + } + + accounts[i] = &account + } + + for _, keys := range pairs { + log.Printf("Funded %s (%s) with %s XLM.\n", + keys.Seed(), keys.Address(), initialBalance) + } + + return nil +} + func InitTokenContract(kp *keypair.Full, contractIDAddress xdr.ScAddress) error { stellarClient := env.NewStellarClient(kp) - // cl := stellarClient.GetHorizonClient() adminScAddr, err := types.MakeAccountAddress(kp) if err != nil { panic(err) @@ -218,9 +284,6 @@ func i128Param(hi int64, lo uint64) xdr.ScVal { func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount int64, recipientAddr xdr.ScAddress) error { //stellarCl *env.StellarClient, stellarClient := env.NewStellarClient(kp) - // cl := stellarClient.GetHorizonClient() - - // amount128 := i128Param(0, amount) amountSc, err := xdr.NewScVal(xdr.ScValTypeScvI64, xdr.Int64(amount)) if err != nil { @@ -238,6 +301,20 @@ func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount int64, recip return nil } +func GetTokenBalance(kp *keypair.Full, contractAddr xdr.ScAddress, balanceOf xdr.ScAddress) error { //stellarCl *env.StellarClient, + stellarClient := env.NewStellarClient(kp) + + GetTokenBalanceArgs, err := env.BuildGetTokenBalanceArgs(balanceOf) + if err != nil { + panic(err) + } + _, err = stellarClient.InvokeAndProcessHostFunction("balance", GetTokenBalanceArgs, contractAddr, kp, []xdr.SorobanAuthorizationEntry{}) + if err != nil { + panic(err) + } + return nil +} + func MakeAccountAddress(kp keypair.KP) (xdr.ScAddress, error) { accountId, err := xdr.AddressToAccountId(kp.Address()) if err != nil { From d19fd3389f0bf9491ad6d427c996081dbd9d18c4 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 19 Dec 2023 12:24:49 +0100 Subject: [PATCH 15/51] main: Use error wrapper to make workflow more concise channel: Remove xdr.SorobanAuthorizationEntry since the authorization is completed by stellar/go since the recent versions. Also remove makePreImgAuth, which was used to calculate the Signature field in the SorobanAuthorizationEntry. This is now obsolete. channel/env: Remove the TestInteractContract function, which was used to invoke the testinteract function of the payment channel contract. This was used for testing purposes only and is now obsolete. Include a global shared mutex, sharedMtx, to prevent both users invoking the same contract function at the same time. util: Remove CreateFundNewRandomStellarKP, which is now substituted by CreateFundStellarAccounts. Remove SorobanAuthorizationEntry and the i128Param function because it is unused. testdata: perun_soroban_contract and perun_soroban_token are updated to represent the most recent versions of the contracts. --- channel/adjudicator.go | 21 ++---- channel/adjudicator_sub.go | 4 +- channel/env/transaction.go | 53 +++------------ channel/funder.go | 44 ++---------- main.go | 78 +++++---------------- testdata/perun_soroban_contract.wasm | Bin 24262 -> 24135 bytes testdata/perun_soroban_token.wasm | Bin 7562 -> 7620 bytes util/util.go | 97 +++------------------------ 8 files changed, 44 insertions(+), 253 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index c3393bb..610d2e3 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -13,7 +13,6 @@ import ( pwallet "perun.network/go-perun/wallet" "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/wallet" - "perun.network/perun-stellar-backend/wire" "perun.network/perun-stellar-backend/wire/scval" "time" @@ -64,15 +63,10 @@ func (a *Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel. func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { - // cid := req.Tx.State.ID - if req.Tx.State.IsFinal { log.Println("Withdraw called") err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs) - if err != nil { - panic(err) - } if err != nil { chanControl, err := a.GetChannelState(ctx, req.Tx.State) @@ -148,8 +142,7 @@ func (a *Adjudicator) GetChannelState(ctx context.Context, state *pchannel.State if err != nil { return wire.Channel{}, errors.New("error while building get_channel tx") } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } @@ -201,8 +194,7 @@ func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) if err != nil { return errors.New("error while building fund tx") } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("withdraw", withdrawTxArgs, perunAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("withdraw", withdrawTxArgs, perunAddress, kp) if err != nil { return errors.New("error while invoking and processing host function: withdraw") } @@ -224,8 +216,7 @@ func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel if err != nil { return errors.New("error while building fund tx") } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("close", closeTxArgs, contractAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("close", closeTxArgs, contractAddress, kp) if err != nil { return errors.New("error while invoking and processing host function: close") } @@ -258,8 +249,7 @@ func (a *Adjudicator) Dispute(ctx context.Context, state *pchannel.State, sigs [ if err != nil { return errors.New("error while building fund tx") } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("dispute", closeTxArgs, contractAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("dispute", closeTxArgs, contractAddress, kp) if err != nil { return errors.New("error while invoking and processing host function: dispute") } @@ -278,8 +268,7 @@ func (a *Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pch if err != nil { return errors.New("error while building fund tx") } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("force_close", forceCloseTxArgs, contractAddress, kp, auth) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("force_close", forceCloseTxArgs, contractAddress, kp) if err != nil { return errors.New("error while invoking and processing host function") } diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index eef1dff..a3ca1c6 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -125,9 +125,7 @@ polling: func (s *AdjEventSub) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) { contractAddress := s.perunID kp := s.stellarClient.GetKeyPair() - // hz := s.GetHorizonAcc() - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := s.stellarClient.InvokeAndProcessHostFunction("get_channel", chanArgs, contractAddress, kp, auth) + txMeta, err := s.stellarClient.InvokeAndProcessHostFunction("get_channel", chanArgs, contractAddress, kp) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } diff --git a/channel/env/transaction.go b/channel/env/transaction.go index 6b09b5e..e7f3551 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -15,13 +15,13 @@ import ( pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/wire" "perun.network/perun-stellar-backend/wire/scval" - - // "perun.network/perun-stellar-backend/util" - "strconv" + "sync" "time" ) +var sharedMtx sync.Mutex + const sorobanRPCPort = 8000 type RPCSimulateTxResponse struct { @@ -62,7 +62,6 @@ func PreflightHostFunctions(hzClient *horizonclient.Client, if err != nil { panic(err) } - // fmt.Printf("Result:\n\n%# +v\n\n", pretty.Formatter(decodedRes)) for _, authBase64 := range res.Auth { var authEntry xdr.SorobanAuthorizationEntry err = xdr.SafeUnmarshalBase64(authBase64, &authEntry) @@ -163,24 +162,6 @@ func CreateSignedTransactionWithParams(signers []*keypair.Full, txParams txnbuil return tx, nil } -func (h *StellarClient) TestInteractContract(ctx context.Context, kp *keypair.Full, tokenAddress xdr.ScAddress, contractAddress xdr.ScAddress) error { - - // hzAcc := h.GetKeyPair() - - tokenAddr := tokenAddress - tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) - testArgs := xdr.ScVec{tokenAddrXdr, tokenAddrXdr} - - auth := []xdr.SorobanAuthorizationEntry{} - - _, err := h.InvokeAndProcessHostFunction("testinteract", testArgs, contractAddress, kp, auth) - if err != nil { - return errors.New("error while invoking and processing host function for InitTokenContract") - } - - return nil -} - func BuildOpenTxArgs(params *pchannel.Params, state *pchannel.State) xdr.ScVec { paramsStellar, err := wire.MakeParams(*params) if err != nil { @@ -288,31 +269,14 @@ func BuildForceCloseTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { return getChannelArgs, nil } -// func (h *StellarClient) MintToken(ctx context.Context, kp *keypair.Full, to xdr.ScAddress, tokenAddress xdr.ScAddress, contractAddress xdr.ScAddress) error { - -// hzAcc := h.GetAccount(kp) - -// tokenAddr := tokenAddress -// tokenAddrXdr := scval.MustWrapScAddress(tokenAddr) -// testArgs := xdr.ScVec{tokenAddrXdr} - -// auth := []xdr.SorobanAuthorizationEntry{} -// _, err := h.InvokeAndProcessHostFunction(hzAcc, "mint", testArgs, contractAddress, kp, auth) -// if err != nil { -// return errors.New("error while invoking and processing host function for InitTokenContract") -// } - -// return nil -// } - -func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full, auth []xdr.SorobanAuthorizationEntry) (xdr.TransactionMeta, error) { - - // Build contract call operation +func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { + sharedMtx.Lock() + defer sharedMtx.Unlock() fnameXdr := xdr.ScSymbol(fname) hzAcc := s.GetHorizonAccount(kp) hzClient := s.GetHorizonClient() - invokeHostFunctionOp := BuildContractCallOp(hzAcc, fnameXdr, callTxArgs, contractAddr, auth) + invokeHostFunctionOp := BuildContractCallOp(hzAcc, fnameXdr, callTxArgs, contractAddr) preFlightOp, minFee := PreflightHostFunctions(hzClient, &hzAcc, *invokeHostFunctionOp) txParams := GetBaseTransactionParamsWithFee(&hzAcc, minFee, &preFlightOp) @@ -336,7 +300,7 @@ func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xd return txMeta, nil } -func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xdr.ScVec, contractIdAddress xdr.ScAddress, auth []xdr.SorobanAuthorizationEntry) *txnbuild.InvokeHostFunction { +func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xdr.ScVec, contractIdAddress xdr.ScAddress) *txnbuild.InvokeHostFunction { return &txnbuild.InvokeHostFunction{ HostFunction: xdr.HostFunction{ @@ -347,7 +311,6 @@ func BuildContractCallOp(caller horizon.Account, fName xdr.ScSymbol, callArgs xd Args: callArgs, }, }, - Auth: auth, SourceAccount: caller.AccountID, } } diff --git a/channel/funder.go b/channel/funder.go index a2d8475..4b6fc4e 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -2,22 +2,15 @@ package channel import ( "context" - "crypto/rand" - "crypto/sha256" "errors" "fmt" - "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" - "log" - "math/big" - pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/wallet" "perun.network/perun-stellar-backend/wire" - "time" ) @@ -148,8 +141,7 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state // generate tx to open the channel openTxArgs := env.BuildOpenTxArgs(params, state) - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("open", openTxArgs, perunAddress, kp, auth) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("open", openTxArgs, perunAddress, kp) if err != nil { return errors.New("error while invoking and processing host function: open") } @@ -187,7 +179,7 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state panic("tokenIDAddrFromBals not equal to tokenContractAddress") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("fund", fundTxArgs, perunAddress, kp, []xdr.SorobanAuthorizationEntry{}) // authFundClx) // []xdr.SorobanAuthorizationEntry{}) //authFundClx + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("fund", fundTxArgs, perunAddress, kp) if err != nil { return errors.New("error while invoking and processing host function: fund") } @@ -200,32 +192,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state return nil } -func makePreImgAuth(passphrase string, rootInv xdr.SorobanAuthorizedInvocation) (xdr.HashIdPreimage, error) { - max := big.NewInt(0).SetInt64(int64(^uint64(0) >> 1)) - randomPart, err := rand.Int(rand.Reader, max) - if err != nil { - panic(err) - } - networkId := xdr.Hash(sha256.Sum256([]byte(passphrase))) - ledgerEntry := uint32(100) - lEntryXdr := xdr.Uint32(ledgerEntry) - nonce := randomPart.Int64() - ncXdr := xdr.Int64(nonce) - - srbPreImgAuth := xdr.HashIdPreimageSorobanAuthorization{ - NetworkId: networkId, - Nonce: ncXdr, - SignatureExpirationLedger: lEntryXdr, - Invocation: rootInv, - } - - srbAuth := xdr.HashIdPreimage{ - Type: xdr.EnvelopeTypeEnvelopeTypeSorobanAuthorization, - SorobanAuthorization: &srbPreImgAuth} - - return srbAuth, nil -} - func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { contractAddress := f.GetPerunID() @@ -237,8 +203,7 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat if err != nil { return errors.New("error while building get_channel tx") } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("abort_funding", openTxArgs, contractAddress, kp, auth) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("abort_funding", openTxArgs, contractAddress, kp) if err != nil { return errors.New("error while invoking and processing host function: abort_funding") } @@ -262,8 +227,7 @@ func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, s if err != nil { return wire.Channel{}, errors.New("error while building get_channel tx") } - auth := []xdr.SorobanAuthorizationEntry{} - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp, auth) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } diff --git a/main.go b/main.go index 735c4ee..41ca2cb 100644 --- a/main.go +++ b/main.go @@ -1,90 +1,50 @@ package main import ( - // "context" "fmt" - "github.com/stellar/go/keypair" - // "github.com/stellar/go/txnbuild" - // "github.com/stellar/go/xdr" - "log" - "perun.network/go-perun/wire" - // "perun.network/perun-stellar-backend/channel/env" "perun.network/perun-stellar-backend/client" "perun.network/perun-stellar-backend/util" - // "perun.network/perun-stellar-backend/wallet" ) const PerunContractPath = "./testdata/perun_soroban_contract.wasm" const StellarAssetContractPath = "./testdata/perun_soroban_token.wasm" func main() { - wAlice, accAlice, kpAlice := util.MakeRandPerunWallet() wBob, accBob, kpBob := util.MakeRandPerunWallet() _, _, kpDepToken := util.MakeRandPerunWallet() _, _, kpDepPerun := util.MakeRandPerunWallet() kps := []*keypair.Full{kpAlice, kpBob, kpDepToken, kpDepPerun} - err := util.CreateFundStellarAccounts(kps, len(kps), "1000000") - if err != nil { - panic(err) - } - - tokenAddr, assetHash := util.Deploy(kpDepToken, StellarAssetContractPath) + checkErr(util.CreateFundStellarAccounts(kps, len(kps), "1000000")) - err = util.InitTokenContract(kpDepToken, tokenAddr) - if err != nil { - panic(err) - } + tokenAddr, _ := util.Deploy(kpDepToken, StellarAssetContractPath) + checkErr(util.InitTokenContract(kpDepToken, tokenAddr)) aliceAddr, err := util.MakeAccountAddress(kpAlice) - if err != nil { - panic(err) - } - + checkErr(err) bobAddr, err := util.MakeAccountAddress(kpBob) - if err != nil { - panic(err) - } + checkErr(err) - err = util.MintToken(kpDepToken, tokenAddr, int64(10000000000), aliceAddr) - if err != nil { - panic(err) - } + checkErr(util.MintToken(kpDepToken, tokenAddr, 1000000, aliceAddr)) + checkErr(util.MintToken(kpDepToken, tokenAddr, 1000000, bobAddr)) - err = util.MintToken(kpDepToken, tokenAddr, int64(10000000000), bobAddr) - if err != nil { - panic(err) - } - - perunAddr, perunHash := util.Deploy(kpDepPerun, PerunContractPath) - fmt.Println("assetID, assetHash, perunID, perunHas: ", perunAddr, assetHash, perunHash) - - err = util.MintToken(kpDepToken, tokenAddr, int64(10000000000), perunAddr) - if err != nil { - panic(err) - } + perunAddr, _ := util.Deploy(kpDepPerun, PerunContractPath) bus := wire.NewLocalBus() alicePerun, err := client.SetupPaymentClient(wAlice, accAlice, kpAlice, tokenAddr, perunAddr, bus) - if err != nil { - panic(err) - } - + checkErr(err) bobPerun, err := client.SetupPaymentClient(wBob, accBob, kpBob, tokenAddr, perunAddr, bus) + checkErr(err) - if err != nil { - panic(err) - } alicePerun.OpenChannel(bobPerun.WireAddress(), 100) - aliceChannel := alicePerun.Channel - bobChannel := bobPerun.AcceptedChannel() + aliceChannel, bobChannel := alicePerun.Channel, bobPerun.AcceptedChannel() - aliceChannel.SendPayment(10) - bobChannel.SendPayment(2) + aliceChannel.SendPayment(150) + bobChannel.SendPayment(220) aliceChannel.Settle() bobChannel.Settle() @@ -93,16 +53,14 @@ func main() { bobPerun.Shutdown() fmt.Println("Get Balances: ") + checkErr(util.GetTokenBalance(kpAlice, tokenAddr, aliceAddr)) + checkErr(util.GetTokenBalance(kpBob, tokenAddr, bobAddr)) - err = util.GetTokenBalance(kpAlice, tokenAddr, aliceAddr) - if err != nil { - panic(err) - } + log.Println("DONE") +} - err = util.GetTokenBalance(kpBob, tokenAddr, bobAddr) +func checkErr(err error) { if err != nil { panic(err) } - - log.Println("DONE") } diff --git a/testdata/perun_soroban_contract.wasm b/testdata/perun_soroban_contract.wasm index c53bc3fb602da19b6e2c7fcb2acaaf469822ca1b..1ae547c44d5de98ce4130b57646319cd2925786e 100755 GIT binary patch delta 3893 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 delta 4102 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 delta 585 zcma)2y-UMD6u)#gV5E%$^XHzn>f_FG(ptGTi)-zyZ8IJR`w)&RwD(NXx29Y9^GDi5LR9R zV_uEwM>vOb3>sDkc64Grqj?Q6Vucno%)sucq#z0r2DI!r4oXQV#ImpJbhzNYlpZaIUrHpv!%aA`2iBBvI5}rwH{3E2aC!+=SenobT d0bPMlzMO2t!)vAOy{+BV!c+#y6S^O4DPQ$Xhm-&S diff --git a/util/util.go b/util/util.go index dcbd15d..4b7802d 100644 --- a/util/util.go +++ b/util/util.go @@ -84,72 +84,6 @@ func DeployStandard(hzClient *env.StellarClient, kp *keypair.Full, hz horizon.Ac } -func CreateFundNewRandomStellarKP(count int, initialBalance string) ([]*keypair.Full, []txnbuild.Account) { - - masterClient := env.NewHorizonMasterClient() - masterHzClient := masterClient.GetMaster() - sourceKey := masterClient.GetSourceKey() - - hzClient := env.NewHorizonClient() - - pairs := make([]*keypair.Full, count) - ops := make([]txnbuild.Operation, count) - - // kp := keypair.MustRandom() - accReq := horizonclient.AccountRequest{AccountID: sourceKey.Address()} - sourceAccount, err := masterHzClient.AccountDetail(accReq) - if err != nil { - panic(err) - } - - masterAccount := txnbuild.SimpleAccount{ - AccountID: sourceKey.Address(), - Sequence: sourceAccount.Sequence, - } - - // use masteraccount to generate new accounts - for i := 0; i < count; i++ { - pair, _ := keypair.Random() - pairs[i] = pair - - ops[i] = &txnbuild.CreateAccount{ - SourceAccount: masterAccount.AccountID, - Destination: pair.Address(), - Amount: initialBalance, - } - } - - txParams := env.GetBaseTransactionParamsWithFee(&masterAccount, txnbuild.MinBaseFee, ops...) - - txSigned, err := env.CreateSignedTransactionWithParams([]*keypair.Full{sourceKey}, txParams) - - if err != nil { - panic(err) - } - _, err = hzClient.SubmitTransaction(txSigned) - if err != nil { - panic(err) - } - - accounts := make([]txnbuild.Account, count) - for i, kp := range pairs { - request := horizonclient.AccountRequest{AccountID: kp.Address()} - account, err := hzClient.AccountDetail(request) - if err != nil { - panic(err) - } - - accounts[i] = &account - } - - for _, keys := range pairs { - log.Printf("Funded %s (%s) with %s XLM.\n", - keys.Seed(), keys.Address(), initialBalance) - } - - return pairs, accounts -} - func CreateFundStellarAccounts(pairs []*keypair.Full, count int, initialBalance string) error { masterClient := env.NewHorizonMasterClient() @@ -158,10 +92,8 @@ func CreateFundStellarAccounts(pairs []*keypair.Full, count int, initialBalance hzClient := env.NewHorizonClient() - // pairs := make([]*keypair.Full, count) ops := make([]txnbuild.Operation, count) - // kp := keypair.MustRandom() accReq := horizonclient.AccountRequest{AccountID: sourceKey.Address()} sourceAccount, err := masterHzClient.AccountDetail(accReq) if err != nil { @@ -175,9 +107,7 @@ func CreateFundStellarAccounts(pairs []*keypair.Full, count int, initialBalance // use masteraccount to generate new accounts for i := 0; i < count; i++ { - // pair, _ := keypair.Random() pair := pairs[i] - // pairs[i] = pair ops[i] = &txnbuild.CreateAccount{ SourceAccount: masterAccount.AccountID, @@ -234,8 +164,7 @@ func InitTokenContract(kp *keypair.Full, contractIDAddress xdr.ScAddress) error if err != nil { panic(err) } - auth := []xdr.SorobanAuthorizationEntry{} - _, err = stellarClient.InvokeAndProcessHostFunction("initialize", initArgs, contractIDAddress, kp, auth) + _, err = stellarClient.InvokeAndProcessHostFunction("initialize", initArgs, contractIDAddress, kp) if err != nil { panic(err) } @@ -271,21 +200,12 @@ func NewAssetFromScAddress(contractAddr xdr.ScAddress) types.StellarAsset { return *contractAsset } -func i128Param(hi int64, lo uint64) xdr.ScVal { - i128 := &xdr.Int128Parts{ - Hi: xdr.Int64(hi), - Lo: xdr.Uint64(lo), - } - return xdr.ScVal{ - Type: xdr.ScValTypeScvI128, - I128: i128, - } -} - -func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount int64, recipientAddr xdr.ScAddress) error { //stellarCl *env.StellarClient, +func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount uint64, recipientAddr xdr.ScAddress) error { //stellarCl *env.StellarClient, stellarClient := env.NewStellarClient(kp) - amountSc, err := xdr.NewScVal(xdr.ScValTypeScvI64, xdr.Int64(amount)) + amountTo128Xdr := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(amount)} + + amountSc, err := xdr.NewScVal(xdr.ScValTypeScvI128, amountTo128Xdr) if err != nil { panic(err) } @@ -293,22 +213,21 @@ func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount int64, recip if err != nil { panic(err) } - // contractAsset := NewAssetFromScAddress(contractAddr) - _, err = stellarClient.InvokeAndProcessHostFunction("mint", mintTokenArgs, contractAddr, kp, []xdr.SorobanAuthorizationEntry{}) + _, err = stellarClient.InvokeAndProcessHostFunction("mint", mintTokenArgs, contractAddr, kp) if err != nil { panic(err) } return nil } -func GetTokenBalance(kp *keypair.Full, contractAddr xdr.ScAddress, balanceOf xdr.ScAddress) error { //stellarCl *env.StellarClient, +func GetTokenBalance(kp *keypair.Full, contractAddr xdr.ScAddress, balanceOf xdr.ScAddress) error { stellarClient := env.NewStellarClient(kp) GetTokenBalanceArgs, err := env.BuildGetTokenBalanceArgs(balanceOf) if err != nil { panic(err) } - _, err = stellarClient.InvokeAndProcessHostFunction("balance", GetTokenBalanceArgs, contractAddr, kp, []xdr.SorobanAuthorizationEntry{}) + _, err = stellarClient.InvokeAndProcessHostFunction("balance", GetTokenBalanceArgs, contractAddr, kp) if err != nil { panic(err) } From 8219a9cbb1944f714d0b08cd42cd57954ebef3b5 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 19 Dec 2023 12:27:45 +0100 Subject: [PATCH 16/51] testdata: Remove perun_soroban_contract_noapprove because it was used for testing and debugging purposes only and is now obsolete. --- testdata/perun_soroban_contract_noapprove.wasm | Bin 24135 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 testdata/perun_soroban_contract_noapprove.wasm diff --git a/testdata/perun_soroban_contract_noapprove.wasm b/testdata/perun_soroban_contract_noapprove.wasm deleted file mode 100755 index 1ae547c44d5de98ce4130b57646319cd2925786e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24135 zcmcJ1eUKc-bzjfSe%UC}Do3XZrJ~Dnf^u95V^>9}N=iki z6czqZ#grq7`Tbt^%+4+Vib|9v_qwO2`}OGV`SLt1|zjGp*7XuVBsz=P>5f1ZK^d z6lfC|nRAx#_ZViT@VBZm&PjoM(iz9v1;9*slM~roF6ZUkzCJIX%@(q5Hk<9w6|-K> z8%TSbQm&u!Tvu)0;`m^h?<=*rzu$3EX=laB<#CAkT&*vt(e(UsqZO+0!eXO2AI_SqiR?nHwX)g{)l}}G zTKmXCv-*&lE>`Cn&GszU)#``Up2FdI0zTuKeumLbY9;4eJY@Q<$AS z5>{7c=c=vHbNh;p3Yy7P#t`pJdVcWI`UZe{+6g@WpIQ)rt4f|$ z!)mIO;`AL}#s4yIPI`m5PGKP$uLCnU^9aFUa*&F_IN0A}>K6XOb==c4j{=Baa%Nnz z=bitj)xh=FZBvfxrP7&ft}kEcFAi+-H*eW`T|r>#lGDG{bSvm?G2IP?{$|s^$i8p- zm)PHA`j^=sF#UB0{i5lgW53_@&$C}J{g>I#oBjs-eWrhr{haAvVn1v8m)Xyl{<@2P z+Vsz{pECXP?0csFGW)LSZ?Nx}{zdc)8fsP0ZsBRiao0Nf;6_|FdgN-zNd*w0cN$9Q z1^$PMa_R2%sJMAfxG_mq7IxI%hB0n?qSgkl{K-Z zp9<&tf5b(Fd!VsSq_KXivB9gNZLa?X0WHefQ6eahU0;Gl0E0-)3xDc(&YBCq2cp~= z5JuGjEx^st(syUkh0UcvF05#2mr~_)@O^bB>@IylhSR};r69k*s4yiRd~0nf*p3Ty z{|Mb1K3q)e)XctzODXUky$`boey3*cLyt5&OR%k!_GU7VpRr8xW^#|u44eT=)P))S z_)O}Ic5KIgO4zU+Jh|f5lXB}0$B85e{#;#x7Y(=FxMQ?TAvspXJWN zdLaO+C4GeBf1JRmn~<6DV(<&9@Veu=9TkAnvLsS1ucC*&+C;R7m^jey83!74_~9>s zC12#u+TG##Wi#(DF%KN#(4hY%-X$r}?iV3xv^jXQ2+JAT2s9Q}0C%Qfej4<$M~7^B zBuaWzfXD_4w+U38x+>FS4bWc1_K5%fGsI|n;g>icQdc`4evSL9nYZjmJpkeqRWT!~ z=+nol!V77qFH*2|H)emtSN{dh0^c0}40|)qZxT%4?oq!>#pjL3c%$SbBsQ4v9K8}t z$jX8B{_x`)P)84pgRDFws|2Je18Kvd6S@LaLZ*~G;hh(FS-``ZAZr0a+Pl<-Q}zP& zp}=WtFL3U>v78F%76Q5XTJOzt;^xKPo0-JT*L!bf<)+cEOOF4~$R6zuxhmT0jouZx z1h~t+H~TC&>LpMmT}E@iVgT>(Hk85fUh%)jyW2bSwf|K!f0ugGDd#QF zU?tXP(N`dR1L#JyobFyyWln+|xS3zO{68LR?a@ zf+loC8yx{wSE`7+H~KH3cd+eX!v-RPcPjD`!E2;@hd1Q^am3uaEN@>|OJ$D%4`i^z zI{+5}PMg`4?%6mhIWKMIWiW40fKGK(VXz2|-{I*J6*VV!_a;42&Uzxh#BdLe2TkOO zAg-+ONuW4qdU{ExO%GdTOb;GQ{8T#fP6#6aMaF)i7rUga6be6~1Tv8XF29`wfW$@zM#YS|73j7GDWem}k9tWcg8q3SCyQD*<~UI8NW|Ux-BdpcUyGT_Qas zBDJ&cx?(nDUQWv~V~!7zF@(FrJ0wyByU`yqQlz>2Mc25NN{`#|XnP|6L5Nq5A*&kh zUc%@cLwyiFWIY(`fcEyNDO^F=LufkE@0K}k^-X#e#E`~SPl@R1w5Q|%Gd&()rpE(J zdNjbxvFP6eyJtu*X8@9oKOqC*@E>Qpy>OZNMk>;b4QPgABMue(gIXF^2*LpOXQqoh zA$Zetak>USG2OIwxW`JAHVok!#A!6pdL|Kw5|~6FiezGjOhx{@i`>RJ1GtvMZ^xo(_6t1!wqoZ6NU?qk_!$3X#UrU zkiHW~NCiaI*qY`zbK57xHqpzWmoqVhiJWkWIEpbnf~8MKIQzdZJ>m`kjF=!>m@qO3 zcp`{RY`m~fVQ-T&V*ZADxaZi{>M`8D?GxI0?97ZrNfHSZ7zqHj&)7{Bo+K%5Bv74@ zCzKKxm^=#6o%m2#G zleod$YSKH;7RG*#Ev)w}+x#RJ1SVk-EQ~grgx^0)NtCw$vv05mdIJ|u%ohm<9=Zi# z0kPlWzsNa3ie7G)`WbR4=%xQJI7keGi*S?u)Gu?a4VW(7Z_#w8mh>j{;G%i-!N3DG z;czD7yf)HL>V(iicF}z89EvCFA zwgykxCqXXnCP?Nb&ksX3iMmW7f~uF<_6i$>J;(8@3Vr6&Klq2g{hJJ$|MB^v|A!qq zjTiPm$&u~g;=mbQ1aHq+bq5YYlnv!#Fc-i5fSpV~)3&PGQ2GdHSFgh@3 z*|VH*ps<1@n`U`4E}>CKOO^5a?p}AV{}~RJua+s&51PzS{2u-En@mRi4K6_{noJVm z?(AXW6uz0uWY%)0QQyO^xyS*$+{sozF@h)+MNq_pCvXl1;Y2R^Dxp6~h3(TUePbS6 zw@ZD-MO8q|8$e*tbQpt(QJQ8xI#7`)R)&J^QJfY*n*8PCD5hY{j0l}QiiS?5mD^9rZ8{+MPDTYtP0M{H=9?A(5P)71h6fn*RDkgmVL$^D zTM#@J8}~I-&H;v72+~N!-2$1+>J6P%-bAT5Li{Jlf2In9OR+_PFJe(bIk3c%vULx< zMRSr{=J!1x*HUnGs zsHZuAwBaeaLAPAq3KwanLFK`g^0qx{-3-8Bx#e|}-kSyogftw0A&NnQKX;X{pY&dn zy9f=-@0#>B?8Q`haMF9ltgFbf8?5~z+7f4t<+{AxdTjWk=NyKuKrn^uvWy((Iidjv za?Xv-@2~#l&+^9jN;iUbu+Z3X=_anb0LWZ(6M(v!6VAJ~u4WA#x|CtcI6v zd#zM1p=*!jieGy%HZGilYizLDL-)GSU-#PjWy5KmwPooQ7v-=dfU^lzx+$Cxn zyGYG6xXkXGfD8Vzdl_AD7F`|5B;swv)`+9Moa|y^%&Q((74yon!bB{eqR#4q&int0 zJ?M49f4$uCUxmMAO)%lEDo4v{<5_~Uk8i+#4bY<5oUqV z^nDmwhr^9PnQ})0hyZva=`#PkFT>=_L)4bX1dGkOLZ%>6v%qfG1q?AfZ*uCIJu@yjKAJkFU;st_X|!?kd;s#`i6eiM+A=Xq2;2nAr68CCL9xFM2ZxiFvR529!n}FNCTZHKg?o#GJi)F_{^_qx`N2=g-0W6wT!40181*XC+80_eT>68`PBtBl6bWMVE*@Ms7NAHx7T}+7EJ%q8CxQ%C$~Ve!;L)w(IFLbIrssBI z5B67Id;W#*M!}X*3CW#fXAV`*>FgOg1w?MNn?T#MmsQnmCWzi@nk}Z;Y?|w6!Y_z) zEb}bOETf9Ff#;kLAT+lev3!WBkLZm^?WMApK%otU3cq3+FFx}9|B|us zBPie&r_(KsQ>{NuFjX3%cfX3MVS1R5d!bfUw>N zB3QIs0XikY2i6g7SQaCgEDRbYAOPVY4PQDiMBYV#gu^yk>%&}7#ktUyCx|LIC03j_ zmSF~H#RX%eW`I`QZ_L^Zz>15;?w#OCnBIV~G;@xnMUTDnRSiU6 zH$@L3`;`A@^cbq}rF_~1HK|UL$z-n+(!87uX@t4(iFm3Kg*Avq)ZaN1?hxH`@I(6) z&B+XVm?8nvN4t)@0awn5W{y zKqkjIo6VezPL;N3Uk^NnYRBfsfF@sp=s}U8WG{*wGZ^UHW9S=nb;guHdDkGNv%G64 z05(z}b#ZRNxQeL8iqb@352eF6-vmxjp(xsUy(>bLXaTv@8w?N-WnE$mqkfsH0vO2% zajG{$gd|Ob2%3`-BE0wo#~vqygL!cE5oWl04ObR3T)~Y;$l`pa}{(#0P+0@Vs29_sbQDIRu)J8l5+|*vf&Bm&A)4OCZL}1ZKSKpmrh3 zH4PLVg+eAew<`4k#iWY@`5NX)#nB7C+KCFkg077T2{u6_FB4SqvO}iOVB+87EB#xtp)ACXe3;3n&3rWhpk zGM3+<`_DLNAyjx2DWO52?6MMON=c?{HpI$T6a(deo78|p7fg?_L+fX6IxUCpeO zq8iF>+~`?u1pOfN{umY#UsOv3V?=CHVuN*3tq40S8yW1%8MDz)xlf+wB#LiNr`GmH zny54@Wc=n+-O z3e7kS?!jeY>N&Pu6*7c~&vYteZ~ow$fAg2$L|s*C-6fVIaa7qV8A@WEDjDLJ^RDIu z}!&7S%PJj zBXJ(4xA~@I=AdX+G(|Ib)2<*556e0}sPJ7EVOR>jg$J(M?z240GdRHvjbap#f%%h&ydqc%+u$`HG`VuB3El&K ziq&bD`4S`vr@n+Kd>=9cn2=>D=kE1T_rTVfU=2@PnLej<8n0vucB!54UVXvZM*swm zNtPh@K8mT-N1ILUQciH&afrV!c<*t-LTC2Fn41eSZJlpIKM?+76=&x;h9xeRG)sAz zymBul=P|v%Z69RWB1U80YC@%&n%q}2PPaV~)>SA}^Lm?PC0Q5+Q z@PJh0g0;H^vqOA$G#9L}vFKRLNDMLIaI`Uq?QCwhc5o~L{p>!0j>8V1C?jA(bKuBFT?(hmUBqM0`GV!ja~EHnXJ_gABE>Qp6!eUYb}(!4Z0Z!o@iw7Y@KL+Ywd2- zCn4m-g`2$a;tNri6x5~27mmpXs*=EqL17MoL1VJitSqMju{0;6ZVl`^`$B`#c>51` zpbI;pRK!IF<%ypRx^|GUrh5X~oYxS47Mu3r><3tg8+Bv(vmbtgl=nmF+;*ub;>zRI zx#Q&l5tS&Urw4G?*a9-lQi=qK0T?@>;#RQ=Nk7?*S>eH zMY$z>z@-%2NEv1b?FCOk-cis9&$a|3$_2R-!CQ~~GF%Ye*!uN#YeuW ztSV91l8YJY$~kVFvXrDclt4bHMZ(ZEW~8o8&yq}n3}jM{HY5yzd3P9sNziy|r?585 z&0TUI{y=m#1tSMmRKpCTY~y+O!U8lnQ|bfTVZyQ2(a$6<5-OO2)<<_hwAgBh5(?=JU$mdZ) zwc3TeJ>@FANo@#*i=`8UNu02j!Cn?#ible>uf*;lY!z&y7U9T+-Q#6L@z4+;w1di- zK|bEsIU*?d0I&A*_y9&Z>!SE<%Ymc7%;=~vh!Ps+fj3YzofX+dI9vrZZmj`5I@9d_ z_!PRPvnq5dM&C(0g(yV9^S!80gs1o0KrR{|`i-Wai33h7hzLyzmVkzD`gnmx%rfEf z%tKg;x>2C~<2I!aWrc))X%3)K`h6?q@pQABwe(twvi9P%Nk9j>5`&`N3= zAMNVQgxhZ%)e!!nZmbZZfej6C1jnBheZ!ZYyL-k~- ztl+Y=Oj$2yW-fojJB>Qqi)94HZ+XXq|E}urv`#xX^XSHURAqr%IL+hv`L$)Zd^53k z3Jz7ib%Hlyzx|(YeE@RIFzBOa1V=J2V%z_w;IAwifLHcVj_TT1z1;=?13*gtDNZ*K z)r&5DouQVF3)}ELPo!go;1=y2E%gZ`WS)1yE4^QQ7NgpmfFjQ5?2V8pqB%aOhfrYo zN8ufKzodd)^g@=AFk%(FyB)lD)$r;#qa>h+sUREDWIiL_w5-q*i@-A;sEhw^so2nK zh8%bR&@prQ_b~=Ws*Fc22xtG_0O4&0c|`2m?9`h zU)==KTJgQP`?Ox&UGxB0e zoLeXUaRXy>q=aRUc9c6&mX{mgnrHOc7t3k*S;IFVqJp=!G6@11$P66pfaE*nzeh6zPBZ{JgcW#nk@ml1Rxl%n3lBcdOP+@=%Ci{2yC`yD#}8s0 zPf96c9nceQ<$s$SqiiOI zXOVgI8pv1-ejH;Ep$+s%;}{yycx{C2=EUuNF>b{2z?BF=d}(?k;`z?-dWmo92xkII z!NF^?=Bk+Jt7C#=!wpm<<6nf#l@jalixNX2|Jn@dJ{ zo{c#s1=hF2tfNbYOK*c2*@c>uJ!YN44wwrB;kL_8@YkP!j7(S~|K>0FX=gxn&HUoo zo|)h445-33vV(^+|D^nHQDY0RJ-DQ8!K)-I*c4eq$2}HYWK#0~w_PVEr4``1NetRJ zN)-U&QDit}B%oYkLbJcb`Jhsx-?S`%{ENK3te%&|`HW!y0 z56xAVXRG`(*KQoePXpYfe3e#8m2CU#w%vaRmNr*b=Z=P_?!;IMbM8i)L5t508(GkB*OxkB?7`PmWKGPft`Ph9^cQMkmH5#wR8wCMTvQrY9?t!;>SE zqmyHk1m)ijqRradK$~7F|@DQY&3^< z>K&lzBd($GKC~{b52B@ z*29x4z$IKbcxol=T-^`5GBlAs(#0?P$uH?AKYY(ugZRD&-(SObC%z;2PT~8H@VyJ) z1Nc&}`R%vgBHHA40N4B;#N1hYm+(D-@5k}|eSDwC_pA8+DZa1btKPF&%mZu=>nDzd z?dpjNmh-a>-)Yqvi0*gYOgCGQv&>VO;oXxXyYVmS>?hZ3wHA(=+oL0u-S}@;bAIIJ z%JlTe*zoA$^ju{;9G_d98(y569GROBr>DcI>4oXxk?FaSZ~}n-!>#eIE$~)QinNBC zq=oZlyX)+(TW&XQZT}sV=&@>h{)pV6mhrRAitqvB>h4)RHYWtoBKYy|#ZRu>TfdVo z@qXhZ#3Fcan}r=LH^b_}DN&Oa#v`0I+57i2+ILdHxQ3GDr?{^^-{6n2Bygq%@YCN> zs|J}#4dJI~H@Z`Nq#2`i@U!i9{us=?^?MrKxYG0S<6D^b*ACaK?bRkNfu5Y7?V!>4 z?0f4UtsBp4QJ4GFEPk&0E&GEq_twdk%> z)-~FX+@qHAbHkndQ5#Ftm7vrnGfllaIq#$Rc^moho33r)|IKI1K^!K_jrpSx$i9;+ zwPq+HQJd}k8w37I4!H}tK<2{c1OYs?`01H<1$JA_H1+Ooc$PEWxY3;W8BES4pBt-! z3H6D(Onsf6u}8IThAYjm1^=e2dfq6wmLu$zmbT})1}oal+T3c)T0ns3iC~~l&(2R8 zOJ=L4AIVzAc)a4nu}?{W(b>4w*%^D%p||y|dVaOp4D0i!cI(@v*IquQ+ed0G9eIm< zYjtG>Om1m<*X3}RS!ro#?bN_mA6s2+*X$iTUjxQU11@=PIowU0(k9?xSCQY5z4E%x z%547~yfVCt?A^74xb}X8d)nTA@IVzO(tK^D3Z};FC6x#e;v2O($O$8s@6HFy%h5$k zAE}-Qb^D=4az#s!#U6zOd|V2?iww$=n-O+mJ219U-v!=aTTTsIt=;*9-E**fKCGPx z!Jg`=V?6P&#_&8^!tGRwS7^zrkC{M_JiT&9+*_{AYXpkBbQNCGG1T%O8Z)+_}2^ zQ1uiw>PWSP5PmVKN*d~cFqA?5Lq}@!M}%*NuRv_MQ9o=oDc^xf@?!qI6bJ1_M$0V# z3L)MmHZ(i}3NXV~4Pprww9MbF1Jkin=BsrIaGqz$LjEwqtZ;F48Ad#JO0Uvl1?NL@ z|B%LSg{?Gduonj8dai*#Np{`Rqb=06m~(wGz9hrg=;v*WNM_aQ^UaX@3{;`ejm1T4 z2!@u`75rKml)hSDAWF>;zkwENW(v^W){(H4C)Lf`d>hA8HJLDLRDLcd13*;In|D@ zLMvJ=Jw&;gS-|cv1`!KU>lPQeKYi~p(T5{pWPCk-A;KdarMwBdq>lWP+fAw|ryu}A zZISQtT&^yI)!--MQf5Pd6XGiX7hMSENhkv%m%sWriFj2!F0CT4L_hG1uZNXBpD+>a?zR|7<1h)X5I-5 zniA#sjkp+LjymhmDJ7~2B?i4ZY1F-ALXoQ%HSaJX()iu^{oTAGRrLn_5Kg1ce0brA zFFR$g4mLP&S3?rUWjvy5ix7CdZROP$&jbb+h~&pZyo$6Q8L`1CW7ZKRftft7zH83oE|Bp0 zIEnR8bQpFZ8;6xg{zX>?)2FJz&zZ2}T8F&xT9}7lK@BC~Ma(fI;r5Z*VJd5M+SVm~ z-vQ?dh-7^e%GClT5=C@H5b(O4IMUFW7W7D_p)(mra&V46zpD9%QJj?l&r0qjd`GXrdzCI*c>0p5Uq+khMBa0Qmn1jA0G8u|M|D6AJctan(zr*)`aw0yS zY1Aw9kK#O&w9xp38iTUM4e{%44JQRPXrIExNxGWF>T(N?p7aqEV#7Q}SDK9z2rwmP zn+p+UEYkbQLLEr3&;`UC76}-HoPd$?zh>OyRuvZ2W4BBatVkZF6vm>G#TN zJ1*YE#QGe+mQ4RxA~W+tz?ywz%cqQUPSBvMi!cZVZ9Hh?kcrL}-E|m8!F9eXj*~EM z2%6DT?%yHc5b>=bbpT6f0Tirt26hWEw5}g4HuMO!*OKG4=>0UxhAwIXXvaQL7XQQ%jgP ze-IjM60=Agd{;fmklO5l~kvPxg#V=#iFI1Die68gwX$*3<#D-vlNo2d>N%(R~xo%1_sD|u^Z@0X} z1TM@AU&vWhVV0}4W5%J1pG#;DA9CQ_Zpd9R3QL?hc2v)=bCX1gBiLSx57)8@KJAEp z*&A4)(u>22c4$g-l2Y+kzk5%o4!e5~v|Cyc4)AdDl!a(_{et*A#P^LjCb2Qj{{c1F BYVrU8 From c03b2f1ecadb4cab45e77c00b9d1f8bed9072d97 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 19 Dec 2023 14:25:15 +0100 Subject: [PATCH 17/51] README: Rewrite the guide to refer to the updated setup based on the stellar/go library. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 487790a..2e5c9c5 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ # [Perun](https://perun.network/) Stellar backend -This repository contains the [Stellar](https://dfinity.org/) backend for the [go-perun](https://github.com/perun-network/go-perun) channel library. It provides the necessary components to run our secure peer-to-peer Perun Payment Channels on the Stellar blockchain, using a [Soroban](https://soroban.stellar.org/) smart contract implementation. The payment channel implementation connects our Perun state machine with the [Perun contract](https://github.com/perun-network/perun-soroban-contract), which implements the Perun Payment Channel logic on the Stellar blockchain. This project is financed through the Stellar Community Fund grants program. +This repository contains the [Stellar](https://stellar.org/) backend for the [go-perun](https://github.com/perun-network/go-perun) channel library. It provides the necessary components to run our secure peer-to-peer Perun Payment Channels on the Stellar blockchain, using a [Soroban](https://soroban.stellar.org/) smart contract implementation. The payment channel implementation connects our Perun state machine with the [Perun contract](https://github.com/perun-network/perun-soroban-contract), which implements the Perun Payment Channel logic on the Stellar blockchain. This project is financed through the Stellar Community Fund grants program. In the following sections, we will describe how to run our Payment Channels on a local instance of the Stellar blockchain. ## [Setup](#setup) -1. We use a fork of the Stellar Go SDK, which is located [here](https://github.com/perun-network/go). Our payment channels use the integration test environment of the Stellar Go SDK, which supports Soroban functionality. You can find more information on the test environment [here](https://github.com/perun-network/go/tree/master/services/horizon/internal/docs). +1. We the Stellar Go SDK, which is located [here](https://github.com/stellar/go). Our payment channels use the integration test environment of the Stellar Go SDK, which supports Soroban functionality. You can find more information on the test environment [here](https://github.com/perun-network/go/tree/master/services/horizon/internal/docs). However, we will summarize the necessary steps to run the Stellar Go client with Soroban support here: From c13b326f1d8f4d3fedac4b474865de94d50b296d Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 19 Dec 2023 15:05:26 +0100 Subject: [PATCH 18/51] channel/env: Remove txMeta prints (introduced for debugging) util: Change GetTokenBalance to output token balance of user main: Adapt to new GetTokenBalance output --- channel/env/transaction.go | 4 +--- main.go | 11 +++++++---- util/util.go | 16 +++++++++++++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/channel/env/transaction.go b/channel/env/transaction.go index e7f3551..f3ba821 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -293,9 +293,7 @@ func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xd if err != nil { return xdr.TransactionMeta{}, errors.New("error while decoding tx meta") } - fmt.Println("txMetaof calling: ", fname, ": ", txMeta) - retval := txMeta.V3.SorobanMeta.ReturnValue - fmt.Println("retval of calling: ", fname, ": ", retval) + _ = txMeta.V3.SorobanMeta.ReturnValue return txMeta, nil } diff --git a/main.go b/main.go index 41ca2cb..fa238e3 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,7 @@ func main() { bobPerun, err := client.SetupPaymentClient(wBob, accBob, kpBob, tokenAddr, perunAddr, bus) checkErr(err) - alicePerun.OpenChannel(bobPerun.WireAddress(), 100) + alicePerun.OpenChannel(bobPerun.WireAddress(), 1000) aliceChannel, bobChannel := alicePerun.Channel, bobPerun.AcceptedChannel() aliceChannel.SendPayment(150) @@ -52,10 +52,13 @@ func main() { alicePerun.Shutdown() bobPerun.Shutdown() - fmt.Println("Get Balances: ") - checkErr(util.GetTokenBalance(kpAlice, tokenAddr, aliceAddr)) - checkErr(util.GetTokenBalance(kpBob, tokenAddr, bobAddr)) + tokenBalAlice, err := util.GetTokenBalance(kpAlice, tokenAddr, aliceAddr) + fmt.Println("New Token Balance (Alice): ", tokenBalAlice) + checkErr(err) + tokenBalBob, err := util.GetTokenBalance(kpBob, tokenAddr, bobAddr) + fmt.Println("New Token Balance (Bob): ", tokenBalBob) + checkErr(err) log.Println("DONE") } diff --git a/util/util.go b/util/util.go index 4b7802d..b099bf5 100644 --- a/util/util.go +++ b/util/util.go @@ -3,6 +3,7 @@ package util import ( "crypto/rand" "encoding/binary" + "errors" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" @@ -220,18 +221,27 @@ func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount uint64, reci return nil } -func GetTokenBalance(kp *keypair.Full, contractAddr xdr.ScAddress, balanceOf xdr.ScAddress) error { +func GetTokenBalance(kp *keypair.Full, contractAddr xdr.ScAddress, balanceOf xdr.ScAddress) (uint64, error) { //xdr.TransactionMeta stellarClient := env.NewStellarClient(kp) GetTokenBalanceArgs, err := env.BuildGetTokenBalanceArgs(balanceOf) if err != nil { panic(err) } - _, err = stellarClient.InvokeAndProcessHostFunction("balance", GetTokenBalanceArgs, contractAddr, kp) + txMeta, err := stellarClient.InvokeAndProcessHostFunction("balance", GetTokenBalanceArgs, contractAddr, kp) if err != nil { panic(err) } - return nil + + bal := txMeta.V3.SorobanMeta.ReturnValue.I128 + + if bal.Hi != 0 { + return 0, errors.New("balance too large - cannot be mapped to uint64") + } else { + Uint64Bal := uint64(bal.Lo) + return Uint64Bal, nil + } + } func MakeAccountAddress(kp keypair.KP) (xdr.ScAddress, error) { From 5f93c000f2c25d86d58d8f5f60ded07775d5a08d Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 19 Dec 2023 15:06:34 +0100 Subject: [PATCH 19/51] README: Update tutorial to use this repository --- README.md | 45 ++++++++++----------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 2e5c9c5..5cef364 100644 --- a/README.md +++ b/README.md @@ -11,55 +11,30 @@ In the following sections, we will describe how to run our Payment Channels on a ## [Setup](#setup) -1. We the Stellar Go SDK, which is located [here](https://github.com/stellar/go). Our payment channels use the integration test environment of the Stellar Go SDK, which supports Soroban functionality. You can find more information on the test environment [here](https://github.com/perun-network/go/tree/master/services/horizon/internal/docs). +1. We use the Horizon client service of the Stellar Go SDK, which is located [here](https://github.com/stellar/go). The first step is cloning this repository: -However, we will summarize the necessary steps to run the Stellar Go client with Soroban support here: - -Clone the forked Stellar Go SDK: - -```sh -git clone https://github.com/perun-network/go ``` - -Build the horizon binary: - -```sh -cd go/services/horizon/ -go build -o stellar-horizon && go install +git clone https://github.com/perun-network/perun-stellar-backend +cd perun-stellar-backend ``` -Add the stellar-horizon executable in you PATH in your ~/.bashrc file. You can find the exact description of the process [here](https://github.com/perun-network/go/blob/master/services/horizon/internal/docs/GUIDE_FOR_DEVELOPERS.md#building-horizon). - -To run the Postgres database server, you start a docker container in the docker folder: -```sh -docker-compose -f ./docker/docker-compose.yml up horizon-postgres -``` -After using the Stellar backend, you can stop the Postgres database server with: +2. To run a local Stellar blockchain with Soroban smart contract support, you initialize the docker images defined in the ```quickstart.sh``` script: ```sh -docker-compose -f ./docker/docker-compose.yml down -``` -2. To use the integration test environment of the Stellar Go SDK, you need to set the correct environment variables: - -```sh -export HORIZON_INTEGRATION_TESTS_ENABLED="true" -export HORIZON_INTEGRATION_TESTS_CORE_MAX_SUPPORTED_PROTOCOL="20" -export HORIZON_INTEGRATION_TESTS_ENABLE_SOROBAN_RPC="true" -export HORIZON_INTEGRATION_TESTS_DOCKER_IMG="stellar/stellar-core:19.13.1-1481.3acf6dd26.focal" -export HORIZON_INTEGRATION_TESTS_SOROBAN_RPC_DOCKER_IMG="stellar/soroban-rpc:20.0.0-rc3-39" +./quickstart.sh standalone ``` -Note that these settings are required to support the Soroban smart contract functionality using the Stellar Go SDK. You can find the exact specifications on the test environment [here](https://github.com/stellar/go/blob/master/.github/workflows/horizon.yml). +Note that this backend is customized to run on a local Stellar blockchain (standalone), but can be easily adapted to run on a public testnet. + 3. Running the payment channels: -Having set up the working environment in the previous steps, you can clone the repository and run the payment channel tests: - +To make sure that your setup is correct, you can run the payment channel tests after cloning this repository: + ```sh -git clone https://github.com/perun-network/perun-stellar-backend -cd perun-stellar-backend + go test ./... ``` From e6f5d5a6b3c62a328829d8c6b4f82029c1d9214f Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Thu, 21 Dec 2023 07:25:22 +0100 Subject: [PATCH 20/51] go.mod: Update dependecies channel: Remove Println statements channel/env: Remove Println statements --- channel/adjudicator_sub.go | 3 --- channel/env/transaction.go | 17 ----------------- channel/event.go | 7 +------ channel/funder.go | 20 +------------------- go.mod | 7 ++----- go.sum | 18 +++--------------- 6 files changed, 7 insertions(+), 65 deletions(-) diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index a3ca1c6..28720cb 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -3,7 +3,6 @@ package channel import ( "context" "errors" - "fmt" "github.com/stellar/go/xdr" pchannel "perun.network/go-perun/channel" log "perun.network/go-perun/log" @@ -183,8 +182,6 @@ func DifferencesInControls(controlCurr, controlNext wire.Control) (AdjEvent, err return nil, errors.New("channel cannot be reopened after closing") } if !controlCurr.Closed && controlNext.Closed { - fmt.Println("controlCurr.Closed need to insert data: ", controlCurr.Closed) - return &CloseEvent{}, nil } return &CloseEvent{}, nil diff --git a/channel/env/transaction.go b/channel/env/transaction.go index f3ba821..27f8f91 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -3,8 +3,6 @@ package env import ( "context" "errors" - "fmt" - "github.com/2opremio/pretty" "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/jhttp" "github.com/stellar/go/clients/horizonclient" @@ -39,24 +37,14 @@ type RPCSimulateHostFunctionResult struct { func PreflightHostFunctions(hzClient *horizonclient.Client, sourceAccount txnbuild.Account, function txnbuild.InvokeHostFunction, ) (txnbuild.InvokeHostFunction, int64) { - fmt.Println("Preflighting function call of type:", function.HostFunction.Type) - if function.HostFunction.Type == xdr.HostFunctionTypeHostFunctionTypeInvokeContract { - fmt.Printf("Preflighting function call to: %s\n", string(function.HostFunction.InvokeContract.FunctionName)) - } result, transactionData := simulateTransaction(hzClient, sourceAccount, &function) - if function.HostFunction.Type == xdr.HostFunctionTypeHostFunctionTypeInvokeContract { - fmt.Println("Preflight result in function: ", string(function.HostFunction.InvokeContract.FunctionName), ": ", result) - - } - function.Ext = xdr.TransactionExt{ V: 1, SorobanData: &transactionData, } var funAuth []xdr.SorobanAuthorizationEntry for _, res := range result.Results { - fmt.Println("Enter range loop") var decodedRes xdr.ScVal err := xdr.SafeUnmarshalBase64(res.XDR, &decodedRes) if err != nil { @@ -68,7 +56,6 @@ func PreflightHostFunctions(hzClient *horizonclient.Client, if err != nil { panic(err) } - fmt.Printf("Auth:\n\n%# +v\n\n", pretty.Formatter(authEntry)) funAuth = append(funAuth, authEntry) } } @@ -87,7 +74,6 @@ func simulateTransaction(hzClient *horizonclient.Client, } syncWithSorobanRPC(uint32(root.HorizonSequence)) - // TODO: soroban-tools should be exporting a proper Go client ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(sorobanRPCPort)+"/soroban/rpc", nil) sorobanRPCClient := jrpc2.NewClient(ch, nil) txParams := GetBaseTransactionParamsWithFee(sourceAccount, txnbuild.MinBaseFee, op) @@ -101,7 +87,6 @@ func simulateTransaction(hzClient *horizonclient.Client, panic(err) } result := RPCSimulateTxResponse{} - fmt.Printf("Preflight TX:\n\n%v \n\n", base64) err = sorobanRPCClient.CallResult(context.Background(), "simulateTransaction", struct { Transaction string `json:"transaction"` }{base64}, &result) @@ -113,8 +98,6 @@ func simulateTransaction(hzClient *horizonclient.Client, if err != nil { panic(err) } - fmt.Printf("Transaction Data:\n\n%# +v\n\n", pretty.Formatter(transactionData)) - // fmt.Printf("Result:\n\n%# +v\n\n", pretty.Formatter(result)) return result, transactionData } func syncWithSorobanRPC(ledgerToWaitFor uint32) { diff --git a/channel/event.go b/channel/event.go index 7a0fb71..49ece14 100644 --- a/channel/event.go +++ b/channel/event.go @@ -196,8 +196,6 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { txEvents := txMeta.V3.SorobanMeta.Events - fmt.Println("txEvents: ", txEvents) - for _, ev := range txEvents { sev := StellarEvent{} topics := ev.Body.V0.Topics @@ -208,7 +206,6 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { perunString, ok := topics[0].GetSym() if perunString == "transfer" { - // TODO: Improve this fix continue } @@ -250,9 +247,7 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { } evs = append(evs, &openEvent) - log.Println("OpenEvent-Params: ", openEvent.Channel.Params) - log.Println("OpenEvent-State: ", openEvent.Channel.State) - log.Println("OpenEvent-Control: ", openEvent.Channel.Control) + log.Println("OpenEvent: ", openEvent) case EventTypeFundChannel: fundEventchanStellar, _, err := GetChannelBoolFromEvents(ev.Body.V0.Data) diff --git a/channel/funder.go b/channel/funder.go index 4b6fc4e..09ff316 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -3,7 +3,6 @@ package channel import ( "context" "errors" - "fmt" "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" "log" @@ -60,21 +59,12 @@ func (f *Funder) Fund(ctx context.Context, req pchannel.FundingReq) error { } func (f *Funder) fundPartyA(ctx context.Context, req pchannel.FundingReq) error { - fmt.Println("req: polling for party A: ", req) err := f.OpenChannel(ctx, req.Params, req.State) if err != nil { return errors.New("error while opening channel in party A") } - fmt.Println("opened channel in party A, checking state") - chanState, err := f.GetChannelState(ctx, req.Params, req.State) - fmt.Println("chanState after opening channel: ", chanState) - if err != nil { - return errors.New("error while polling for opened channel A") - } - fmt.Println("polled chanState for PartyA: ", chanState.Control.FundedA, chanState.Control.FundedB) - err = f.FundChannel(ctx, req.Params, req.State, false) if err != nil { return err @@ -88,13 +78,10 @@ polling: case <-ctx.Done(): return f.AbortChannel(ctx, req.Params, req.State) case <-time.After(f.pollingInterval): - fmt.Println("Party A: Polling for opened channel...") chanState, err := f.GetChannelState(ctx, req.Params, req.State) if err != nil { continue polling } - fmt.Println("Party A: chanState.Control.FundedA && chanState.Control.FundedB: ", chanState.Control.FundedA, chanState.Control.FundedB) - if chanState.Control.FundedA && chanState.Control.FundedB { return nil } @@ -105,11 +92,6 @@ polling: } func (f *Funder) fundPartyB(ctx context.Context, req pchannel.FundingReq) error { - fmt.Println("req: polling for party B: ", req) - // err := f.OpenChannel(ctx, req.Params, req.State) - // if err != nil { - // return errors.New("error while opening channel in party A") - // } polling: for { @@ -119,7 +101,7 @@ polling: case <-time.After(f.pollingInterval): log.Println("Party B: Polling for opened channel...") chanState, err := f.GetChannelState(ctx, req.Params, req.State) - fmt.Println("polled chanState for PartyB: ", chanState.Control.FundedA, chanState.Control.FundedB) + // fmt.Println("polled chanState for PartyB: ", chanState.Control.FundedA, chanState.Control.FundedB) if err != nil { log.Println("Party B: Error while polling for opened channel:", err) continue polling diff --git a/go.mod b/go.mod index a8fa9b5..f624fe4 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,11 @@ go 1.19 require github.com/stellar/go v0.0.0-20231212225359-bc7173e667a6 require ( - github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 github.com/creachadair/jrpc2 v1.1.0 github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 github.com/stretchr/testify v1.8.4 perun.network/go-perun v0.10.6 - polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b + polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 ) require ( @@ -19,12 +18,10 @@ require ( github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/gorilla/schema v1.2.0 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/stretchr/objx v0.5.1 // indirect diff --git a/go.sum b/go.sum index ff09925..69db35a 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,9 @@ -github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95 h1:vvMDiVd621MU1Djr7Ep7OXu8gHOtsdwrI4tjnIGvpTg= -github.com/2opremio/pretty v0.2.2-0.20230601220618-e1d5758b2a95/go.mod h1:Gv4NIpY67KDahg+DtIG5/2Ok4l8vzYEekiirSCH+IGA= github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/creachadair/jrpc2 v1.1.0 h1:SgpJf0v1rVCZx68+4APv6dgsTFsIHlpgFD1NlQAWA0A= github.com/creachadair/jrpc2 v1.1.0/go.mod h1:5jN7MKwsm8qvgfTsTzLX3JIfidsAkZ1c8DZSQmp+g38= github.com/creachadair/mds v0.0.1 h1:2nX6Sww4dXpScx3b6aYjH1n7iuEH715+jj+cKkKw9BY= github.com/creachadair/mds v0.0.1/go.mod h1:caBACU+n1Q/rZ252FTzfnG0/H+ZUi+UnIQtEOraMv/g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -26,29 +23,22 @@ github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlI github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/jarcoal/httpmock v0.0.0-20161210151336-4442edb3db31 h1:Aw95BEvxJ3K6o9GGv5ppCd1P8hkeIeEJ30FO+OhOJpM= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 h1:ykXz+pRRTibcSjG1yRhpdSHInF8yZY/mfn+Rz2Nd1rE= github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739/go.mod h1:zUx1mhth20V3VKgL5jbd1BSQcW4Fy6Qs4PZvQwRFwzM= github.com/moul/http2curl v0.0.0-20161031194548-4e24498b31db h1:eZgFHVkk9uOTaOQLC6tgjkzdp7Ays8eEVecBcfHZlJQ= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 h1:S4OC0+OBKz6mJnzuHioeEat74PuQ4Sgvbf8eus695sc= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2/go.mod h1:8zLRYR5npGjaOXgPSKat5+oOh+UHd8OdbS18iqX9F6Y= github.com/sergi/go-diff v0.0.0-20161205080420-83532ca1c1ca h1:oR/RycYTFTVXzND5r4FdsvbnBn0HJXSVeNAnwaTXRwk= @@ -90,9 +80,7 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/gavv/httpexpect.v1 v1.0.0-20170111145843-40724cf1e4a0 h1:r5ptJ1tBxVAeqw4CrYWhXIMr0SybY3CDHuIbCg5CFVw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -100,5 +88,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= perun.network/go-perun v0.10.6 h1:uj1e33yfCSfE75DK/uwjNp+TwvGG85Qhi6HuYQ9EPrQ= perun.network/go-perun v0.10.6/go.mod h1:BGBZC3npkX457u87pjDd0NEIXr1a4dsH4H/YpLdGGe8= -polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b h1:BJsSrLQ3kLRNYXNqly//IYeXlVmAhpI5wYbg2WD1wR0= -polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b/go.mod h1:XUBrNtqgEhN3EEOP/5gh7IBd3xVHKidCjXDZfl9+kMU= +polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 h1:iA5GzEa/hHfVlQpimEjPV09NATwHXxSjWNB0VVodtew= +polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37/go.mod h1:XUBrNtqgEhN3EEOP/5gh7IBd3xVHKidCjXDZfl9+kMU= From 32313916afd9f6069df82e31bd85c639c326f91a Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Thu, 21 Dec 2023 07:26:55 +0100 Subject: [PATCH 21/51] channel: Remove comments --- channel/funder.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/channel/funder.go b/channel/funder.go index 09ff316..5a3356d 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -70,8 +70,6 @@ func (f *Funder) fundPartyA(ctx context.Context, req pchannel.FundingReq) error return err } - // await response from party B - polling: for i := 0; i < f.maxIters; i++ { select { @@ -101,13 +99,11 @@ polling: case <-time.After(f.pollingInterval): log.Println("Party B: Polling for opened channel...") chanState, err := f.GetChannelState(ctx, req.Params, req.State) - // fmt.Println("polled chanState for PartyB: ", chanState.Control.FundedA, chanState.Control.FundedB) if err != nil { log.Println("Party B: Error while polling for opened channel:", err) continue polling } log.Println("Party B: Found opened channel!") - // Optional: make some channel checks here if chanState.Control.FundedA && chanState.Control.FundedB { return nil } @@ -121,7 +117,6 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state perunAddress := f.GetPerunID() kp := f.kpFull - // generate tx to open the channel openTxArgs := env.BuildOpenTxArgs(params, state) txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("open", openTxArgs, perunAddress, kp) if err != nil { @@ -144,7 +139,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state kp := f.kpFull chanId := state.ID - // generate tx to open the channel fundTxArgs, err := env.BuildFundTxArgs(chanId, funderIdx) if err != nil { return errors.New("error while building fund tx") @@ -180,7 +174,6 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat kp := f.kpFull chanId := state.ID - // generate tx to open the channel openTxArgs, err := env.BuildGetChannelTxArgs(chanId) if err != nil { return errors.New("error while building get_channel tx") @@ -204,7 +197,6 @@ func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, s kp := f.kpFull chanId := state.ID - // generate tx to open the channel getchTxArgs, err := env.BuildGetChannelTxArgs(chanId) if err != nil { return wire.Channel{}, errors.New("error while building get_channel tx") From 72379e74cbcc677105e122f5830d265f2c16953b Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Thu, 21 Dec 2023 08:11:14 +0100 Subject: [PATCH 22/51] wallet: Change Participant.String method to display only the address string, not the public key --- wallet/types/address.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/types/address.go b/wallet/types/address.go index d311e5c..071f45f 100644 --- a/wallet/types/address.go +++ b/wallet/types/address.go @@ -51,7 +51,7 @@ func (p *Participant) UnmarshalBinary(data []byte) error { // String returns the string representation of the participant as [ParticipantAddress string]:[public key hex]. func (p Participant) String() string { - return p.AddressString() + ":" + p.PublicKeyString() + return p.AddressString() // + ":" + p.PublicKeyString() } func (p Participant) Equal(other wallet.Address) bool { From 962daa797b683d790fb13257ca7cded1ff1cf023 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Thu, 21 Dec 2023 09:22:45 +0100 Subject: [PATCH 23/51] client: package client deprecated because the demonstrator of payment channels has been moved to the perun-stellar-demo repository. --- client/channel.go | 117 --------------------- client/client.go | 146 -------------------------- client/handle.go | 105 ------------------- util/deploy.go | 59 ----------- util/util.go | 253 ---------------------------------------------- 5 files changed, 680 deletions(-) delete mode 100644 client/channel.go delete mode 100644 client/client.go delete mode 100644 client/handle.go delete mode 100644 util/deploy.go delete mode 100644 util/util.go diff --git a/client/channel.go b/client/channel.go deleted file mode 100644 index 11bcd8b..0000000 --- a/client/channel.go +++ /dev/null @@ -1,117 +0,0 @@ -package client - -import ( - "context" - "encoding/hex" - "fmt" - "log" - "math/big" - - "perun.network/go-perun/channel" - "perun.network/go-perun/client" - - "strconv" -) - -// PaymentChannel is a wrapper for a Perun channel for the payment use case. -type PaymentChannel struct { - ch *client.Channel - currency channel.Asset -} - -func (c *PaymentChannel) GetChannel() *client.Channel { - return c.ch -} -func (c *PaymentChannel) GetChannelParams() *channel.Params { - return c.ch.Params() -} - -func (c *PaymentChannel) GetChannelState() *channel.State { - return c.ch.State() -} - -// newPaymentChannel creates a new payment channel. -func newPaymentChannel(ch *client.Channel, currency channel.Asset) *PaymentChannel { - return &PaymentChannel{ - ch: ch, - currency: currency, - } -} - -// SendPayment sends a payment to the channel peer. -func (c PaymentChannel) SendPayment(amount int64) { - // Transfer the given amount from us to peer. - // Use UpdateBy to update the channel state. - err := c.ch.Update(context.TODO(), func(state *channel.State) { - icp := big.NewInt(amount) - actor := c.ch.Idx() - peer := 1 - actor - state.Allocation.TransferBalance(actor, peer, c.currency, icp) - }) - if err != nil { - panic(err) // We panic on error to keep the code simple. - } -} - -// Settle settles the payment channel and withdraws the funds. -func (c PaymentChannel) Settle() { - // If the channel is not finalized: Finalize the channel to enable fast settlement. - - if !c.ch.State().IsFinal { - err := c.ch.Update(context.TODO(), func(state *channel.State) { - state.IsFinal = true - }) - if err != nil { - panic(err) - } - } - - // Settle concludes the channel and withdraws the funds. - err := c.ch.Settle(context.TODO(), false) - if err != nil { - panic(err) - } - - // Close frees up channel resources. - c.ch.Close() -} - -func FormatState(c *PaymentChannel, state *channel.State) string { - id := c.ch.ID() - parties := c.ch.Params().Parts - - bigIntA := state.Allocation.Balance(0, c.currency) - bigFloatA := new(big.Float).SetInt(bigIntA) - balA, _ := bigFloatA.Float64() - balAStr := strconv.FormatFloat(balA, 'f', 4, 64) - - fstPartyPaymentAddr := parties[0].String() - sndPartyPaymentAddr := parties[1].String() - - bigIntB := state.Allocation.Balance(1, c.currency) - bigFloatB := new(big.Float).SetInt(bigIntB) - balB, _ := bigFloatB.Float64() - - balBStr := strconv.FormatFloat(balB, 'f', 4, 64) - if len(parties) != 2 { - log.Fatalf("invalid parties length: " + strconv.Itoa(len(parties))) - } - ret := fmt.Sprintf( - "Channel ID: [green]%s[white]\nBalances:\n %s: [green]%s[white] IC Token\n %s: [green]%s[white] IC Token\nFinal: [green]%t[white]\nVersion: [green]%d[white]", - hex.EncodeToString(id[:]), - fstPartyPaymentAddr, - balAStr, - sndPartyPaymentAddr, - balBStr, - state.IsFinal, - state.Version, - ) - return ret -} - -// func (p *PaymentClient) Settle() { -// if !p.HasOpenChannel() { -// return -// } -// p.Channel.Settle() -// } diff --git a/client/client.go b/client/client.go deleted file mode 100644 index 931b7a8..0000000 --- a/client/client.go +++ /dev/null @@ -1,146 +0,0 @@ -package client - -import ( - "context" - "errors" - "fmt" - "github.com/stellar/go/keypair" - "github.com/stellar/go/xdr" - - "math/big" - pchannel "perun.network/go-perun/channel" - "perun.network/go-perun/client" - "perun.network/go-perun/watcher/local" - "perun.network/go-perun/wire" - "perun.network/go-perun/wire/net/simple" - - "perun.network/perun-stellar-backend/channel" - "perun.network/perun-stellar-backend/channel/env" - "perun.network/perun-stellar-backend/channel/types" - "perun.network/perun-stellar-backend/wallet" -) - -type PaymentClient struct { - perunClient *client.Client - account *wallet.Account - currency pchannel.Asset - channels chan *PaymentChannel - Channel *PaymentChannel - wAddr wire.Address - balance *big.Int -} - -func SetupPaymentClient( - w *wallet.EphemeralWallet, // w is the wallet used to resolve addresses to accounts for channels. - acc *wallet.Account, - kp *keypair.Full, - tokenAddr xdr.ScAddress, - perunAddr xdr.ScAddress, - bus *wire.LocalBus, - -) (*PaymentClient, error) { - - // Connect to Perun pallet and get funder + adjudicator from it. - - perunConn := env.NewStellarClient(kp) - funder := channel.NewFunder(acc, kp, perunConn, perunAddr, tokenAddr) - adj := channel.NewAdjudicator(acc, kp, perunConn, perunAddr, tokenAddr) - - // Setup dispute watcher. - watcher, err := local.NewWatcher(adj) - if err != nil { - return nil, fmt.Errorf("intializing watcher: %w", err) - } - - // Setup Perun client. - wireAddr := simple.NewAddress(acc.Address().String()) - perunClient, err := client.New(wireAddr, bus, funder, adj, w, watcher) - if err != nil { - return nil, errors.New("creating client") - } - - assetContractID, err := types.NewStellarAssetFromScAddress(tokenAddr) - if err != nil { - panic(err) - } - - // Create client and start request handler. - c := &PaymentClient{ - perunClient: perunClient, - account: acc, - currency: assetContractID, - channels: make(chan *PaymentChannel, 1), - wAddr: wireAddr, - balance: big.NewInt(0), - } - - go perunClient.Handle(c, c) - return c, nil -} - -// startWatching starts the dispute watcher for the specified channel. -func (c *PaymentClient) startWatching(ch *client.Channel) { - go func() { - err := ch.Watch(c) - if err != nil { - fmt.Printf("Watcher returned with error: %v", err) - } - }() -} - -// OpenChannel opens a new channel with the specified peer and funding. -func (c *PaymentClient) OpenChannel(peer wire.Address, 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.WireAddress(), peer} - - // We create an initial allocation which defines the starting balances. - initBal := big.NewInt(int64(amount)) - - initAlloc := pchannel.NewAllocation(2, c.currency) - initAlloc.SetAssetBalances(c.currency, []pchannel.Bal{ - initBal, // Our initial balance. - initBal, // Peer's initial balance. - }) - - // 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.Address(), - 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) - c.Channel = newPaymentChannel(ch, c.currency) - //c.Channel.ch.OnUpdate(c.NotifyAllState) - //c.NotifyAllState(nil, ch.State()) - -} - -// AcceptedChannel returns the next accepted channel. -func (c *PaymentClient) AcceptedChannel() *PaymentChannel { - return <-c.channels -} - -func (p *PaymentClient) WireAddress() wire.Address { - return p.wAddr -} - -// Shutdown gracefully shuts down the client. -func (c *PaymentClient) Shutdown() { - c.perunClient.Close() -} diff --git a/client/handle.go b/client/handle.go deleted file mode 100644 index fdc4a57..0000000 --- a/client/handle.go +++ /dev/null @@ -1,105 +0,0 @@ -package client - -import ( - "context" - "fmt" - "log" - "time" - - "perun.network/go-perun/channel" - "perun.network/go-perun/client" -) - -// HandleProposal is the callback for incoming channel proposals. -func (c *PaymentClient) HandleProposal(p client.ChannelProposal, r *client.ProposalResponder) { - lcp, err := func() (*client.LedgerChannelProposalMsg, error) { - // Ensure that we got a ledger channel proposal. - lcp, ok := p.(*client.LedgerChannelProposalMsg) - if !ok { - return nil, fmt.Errorf("Invalid proposal type: %T\n", p) - } - - // Check that we have the correct number of participants. - if lcp.NumPeers() != 2 { - return nil, fmt.Errorf("Invalid number of participants: %d", lcp.NumPeers()) - } - - // Check that the channel has the expected assets and funding balances. - const assetIdx, clientIdx, peerIdx = 0, 0, 1 - if err := channel.AssertAssetsEqual(lcp.InitBals.Assets, []channel.Asset{c.currency}); err != nil { - return nil, fmt.Errorf("Invalid assets: %v\n", err) - } else if lcp.FundingAgreement[assetIdx][clientIdx].Cmp(lcp.FundingAgreement[assetIdx][peerIdx]) != 0 { - return nil, fmt.Errorf("Invalid funding balance") - } - return lcp, nil - }() - if err != nil { - errReject := r.Reject(context.TODO(), err.Error()) - if errReject != nil { - // Log the error or take other action as needed - fmt.Printf("Error rejecting proposal: %v\n", errReject) - } - } - - // Create a channel accept message and send it. - accept := lcp.Accept( - c.account.Address(), // The account we use in the channel. - client.WithRandomNonce(), // Our share of the channel nonce. - ) - ch, err := r.Accept(context.TODO(), accept) - if err != nil { - fmt.Printf("Error accepting channel proposal: %v\n", err) - return - } - - // Start the on-chain event watcher. It automatically handles disputes. - c.startWatching(ch) - - // Store channel. - c.channels <- newPaymentChannel(ch, c.currency) - //c.AcceptedChannel() - -} - -// HandleUpdate is the callback for incoming channel updates. -func (c *PaymentClient) HandleUpdate(cur *channel.State, next client.ChannelUpdate, r *client.UpdateResponder) { - // We accept every update that increases our balance. - err := func() error { - err := channel.AssertAssetsEqual(cur.Assets, next.State.Assets) - if err != nil { - return fmt.Errorf("Invalid assets: %v", err) - } - - receiverIdx := 1 - next.ActorIdx // This works because we are in a two-party channel. - curBal := cur.Allocation.Balance(receiverIdx, c.currency) - nextBal := next.State.Allocation.Balance(receiverIdx, c.currency) - if nextBal.Cmp(curBal) < 0 { - return fmt.Errorf("Invalid balance: %v", nextBal) - } - return nil - }() - if err != nil { - r.Reject(context.TODO(), err.Error()) //nolint:errcheck // It's OK if rejection fails. - } - - // Send the acceptance message. - err = r.Accept(context.TODO()) - if err != nil { - panic(err) - } -} - -// HandleAdjudicatorEvent is the callback for smart contract events. -func (c *PaymentClient) HandleAdjudicatorEvent(e channel.AdjudicatorEvent) { - log.Printf("Adjudicator event: type = %T, client = %v", e, c.account.Address()) -} - -func (c *PaymentClient) GetChannel() (*PaymentChannel, error) { - select { - case channel := <-c.channels: - c.channels <- channel // Put the channel back into the channels channel. - return channel, nil - case <-time.After(time.Second): // Set a timeout duration (e.g., 1 second). - return nil, fmt.Errorf("no channel available") - } -} diff --git a/util/deploy.go b/util/deploy.go deleted file mode 100644 index 15a3113..0000000 --- a/util/deploy.go +++ /dev/null @@ -1,59 +0,0 @@ -package util - -import ( - "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - "github.com/stellar/go/xdr" - "perun.network/perun-stellar-backend/channel" - - "perun.network/perun-stellar-backend/channel/env" -) - -func Deploy(kp *keypair.Full, contractPath string) (xdr.ScAddress, xdr.Hash) { - // Install contract - deployerClient := env.NewStellarClient(kp) //.GetHorizonClient() - hzClient := deployerClient.GetHorizonClient() - deployerAccReq := horizonclient.AccountRequest{AccountID: kp.Address()} - deployerAcc, err := hzClient.AccountDetail(deployerAccReq) - if err != nil { - panic(err) - } - - installContractOpInstall := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) - preFlightOp, minFeeInstall := env.PreflightHostFunctions(hzClient, &deployerAcc, *installContractOpInstall) - txParamsInstall := env.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeInstall), &preFlightOp) - txSignedInstall, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsInstall) - if err != nil { - panic(err) - } - _, err = hzClient.SubmitTransaction(txSignedInstall) - if err != nil { - panic(err) - } - - // Create the contract - createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", NETWORK_PASSPHRASE) - preFlightOpCreate, minFeeCreate := env.PreflightHostFunctions(hzClient, &deployerAcc, *createContractOp) - txParamsCreate := env.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeCreate), &preFlightOpCreate) - txSignedCreate, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsCreate) - if err != nil { - panic(err) - } - _, err = hzClient.SubmitTransaction(txSignedCreate) - if err != nil { - panic(err) - } - contractID := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId - contractHash := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash - contractIDAddress := xdr.ScAddress{ - Type: xdr.ScAddressTypeScAddressTypeContract, - ContractId: contractID, - } - - return contractIDAddress, contractHash -} - -// func MintAsset() { -// env.BuildMint - -// } diff --git a/util/util.go b/util/util.go deleted file mode 100644 index b099bf5..0000000 --- a/util/util.go +++ /dev/null @@ -1,253 +0,0 @@ -package util - -import ( - "crypto/rand" - "encoding/binary" - "errors" - "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/txnbuild" - "github.com/stellar/go/xdr" - "log" - mathrand "math/rand" - "perun.network/perun-stellar-backend/channel" - "perun.network/perun-stellar-backend/channel/env" - "perun.network/perun-stellar-backend/channel/types" - "perun.network/perun-stellar-backend/wallet" -) - -const NETWORK_PASSPHRASE = "Standalone Network ; February 2017" - -type StellarClient struct { - horizonClient *horizonclient.Client -} - -func (s *StellarClient) SubmitTx(tx *txnbuild.Transaction) error { - _, err := s.horizonClient.SubmitTransaction(tx) - if err != nil { - panic(err) - } - return err -} - -func CreateSignedTransaction(signers []*keypair.Full, txParams txnbuild.TransactionParams, -) (*txnbuild.Transaction, error) { - tx, err := txnbuild.NewTransaction(txParams) - if err != nil { - return nil, err - } - - for _, signer := range signers { - tx, err = tx.Sign(NETWORK_PASSPHRASE, signer) - if err != nil { - return nil, err - } - } - return tx, nil -} - -func DeployStandard(hzClient *env.StellarClient, kp *keypair.Full, hz horizon.Account, contractPath string) (xdr.ScAddress, xdr.Hash) { - // Install contract - cl := hzClient.GetHorizonClient() - - installContractOpInstall := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) - preFlightOp, minFeeInstall := env.PreflightHostFunctions(cl, &hz, *installContractOpInstall) - txParamsInstall := env.GetBaseTransactionParamsWithFee(&hz, int64(minFeeInstall), &preFlightOp) - txSignedInstall, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsInstall) - if err != nil { - panic(err) - } - _, err = cl.SubmitTransaction(txSignedInstall) - if err != nil { - panic(err) - } - - // Create the contract - createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", NETWORK_PASSPHRASE) - preFlightOpCreate, minFeeCreate := env.PreflightHostFunctions(cl, &hz, *createContractOp) - txParamsCreate := env.GetBaseTransactionParamsWithFee(&hz, int64(minFeeCreate), &preFlightOpCreate) - txSignedCreate, err := CreateSignedTransaction([]*keypair.Full{kp}, txParamsCreate) - if err != nil { - panic(err) - } - _, err = cl.SubmitTransaction(txSignedCreate) - if err != nil { - panic(err) - } - contractID := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId - contractHash := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash - contractIDAddress := xdr.ScAddress{ - Type: xdr.ScAddressTypeScAddressTypeContract, - ContractId: contractID, - } - return contractIDAddress, contractHash - -} - -func CreateFundStellarAccounts(pairs []*keypair.Full, count int, initialBalance string) error { - - masterClient := env.NewHorizonMasterClient() - masterHzClient := masterClient.GetMaster() - sourceKey := masterClient.GetSourceKey() - - hzClient := env.NewHorizonClient() - - ops := make([]txnbuild.Operation, count) - - accReq := horizonclient.AccountRequest{AccountID: sourceKey.Address()} - sourceAccount, err := masterHzClient.AccountDetail(accReq) - if err != nil { - panic(err) - } - - masterAccount := txnbuild.SimpleAccount{ - AccountID: sourceKey.Address(), - Sequence: sourceAccount.Sequence, - } - - // use masteraccount to generate new accounts - for i := 0; i < count; i++ { - pair := pairs[i] - - ops[i] = &txnbuild.CreateAccount{ - SourceAccount: masterAccount.AccountID, - Destination: pair.Address(), - Amount: initialBalance, - } - } - - txParams := env.GetBaseTransactionParamsWithFee(&masterAccount, txnbuild.MinBaseFee, ops...) - - txSigned, err := env.CreateSignedTransactionWithParams([]*keypair.Full{sourceKey}, txParams) - - if err != nil { - panic(err) - } - _, err = hzClient.SubmitTransaction(txSigned) - if err != nil { - panic(err) - } - - accounts := make([]txnbuild.Account, count) - for i, kp := range pairs { - request := horizonclient.AccountRequest{AccountID: kp.Address()} - account, err := hzClient.AccountDetail(request) - if err != nil { - panic(err) - } - - accounts[i] = &account - } - - for _, keys := range pairs { - log.Printf("Funded %s (%s) with %s XLM.\n", - keys.Seed(), keys.Address(), initialBalance) - } - - return nil -} - -func InitTokenContract(kp *keypair.Full, contractIDAddress xdr.ScAddress) error { - - stellarClient := env.NewStellarClient(kp) - adminScAddr, err := types.MakeAccountAddress(kp) - if err != nil { - panic(err) - } - - tokenParams := channel.NewTokenParams() - decimals := tokenParams.GetDecimals() - name := tokenParams.GetName() - symbol := tokenParams.GetSymbol() - - initArgs, err := channel.BuildInitTokenArgs(adminScAddr, decimals, name, symbol) - if err != nil { - panic(err) - } - _, err = stellarClient.InvokeAndProcessHostFunction("initialize", initArgs, contractIDAddress, kp) - if err != nil { - panic(err) - } - - return nil -} - -func MakeRandPerunWallet() (*wallet.EphemeralWallet, *wallet.Account, *keypair.Full) { - w := wallet.NewEphemeralWallet() - - var b [8]byte - _, err := rand.Read(b[:]) - if err != nil { - panic(err) - } - - seed := binary.LittleEndian.Uint64(b[:]) - - r := mathrand.New(mathrand.NewSource(int64(seed))) - - acc, kp, err := w.AddNewAccount(r) - if err != nil { - panic(err) - } - return w, acc, kp -} - -func NewAssetFromScAddress(contractAddr xdr.ScAddress) types.StellarAsset { - contractAsset, err := types.NewStellarAssetFromScAddress(contractAddr) - if err != nil { - panic(err) - } - return *contractAsset -} - -func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount uint64, recipientAddr xdr.ScAddress) error { //stellarCl *env.StellarClient, - stellarClient := env.NewStellarClient(kp) - - amountTo128Xdr := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(amount)} - - amountSc, err := xdr.NewScVal(xdr.ScValTypeScvI128, amountTo128Xdr) - if err != nil { - panic(err) - } - mintTokenArgs, err := env.BuildMintTokenArgs(recipientAddr, amountSc) - if err != nil { - panic(err) - } - _, err = stellarClient.InvokeAndProcessHostFunction("mint", mintTokenArgs, contractAddr, kp) - if err != nil { - panic(err) - } - return nil -} - -func GetTokenBalance(kp *keypair.Full, contractAddr xdr.ScAddress, balanceOf xdr.ScAddress) (uint64, error) { //xdr.TransactionMeta - stellarClient := env.NewStellarClient(kp) - - GetTokenBalanceArgs, err := env.BuildGetTokenBalanceArgs(balanceOf) - if err != nil { - panic(err) - } - txMeta, err := stellarClient.InvokeAndProcessHostFunction("balance", GetTokenBalanceArgs, contractAddr, kp) - if err != nil { - panic(err) - } - - bal := txMeta.V3.SorobanMeta.ReturnValue.I128 - - if bal.Hi != 0 { - return 0, errors.New("balance too large - cannot be mapped to uint64") - } else { - Uint64Bal := uint64(bal.Lo) - return Uint64Bal, nil - } - -} - -func MakeAccountAddress(kp keypair.KP) (xdr.ScAddress, error) { - accountId, err := xdr.AddressToAccountId(kp.Address()) - if err != nil { - return xdr.ScAddress{}, err - } - return xdr.NewScAddress(xdr.ScAddressTypeScAddressTypeAccount, accountId) -} From b7b7d01aeaaf2b08994ac16061c44f307733e91c Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Thu, 21 Dec 2023 09:23:32 +0100 Subject: [PATCH 24/51] main: Deprecate main.go demonstrator as it is moved to the repository perun-stellar-demo --- main.go | 69 --------------------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 main.go diff --git a/main.go b/main.go deleted file mode 100644 index fa238e3..0000000 --- a/main.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "fmt" - "github.com/stellar/go/keypair" - "log" - "perun.network/go-perun/wire" - "perun.network/perun-stellar-backend/client" - "perun.network/perun-stellar-backend/util" -) - -const PerunContractPath = "./testdata/perun_soroban_contract.wasm" -const StellarAssetContractPath = "./testdata/perun_soroban_token.wasm" - -func main() { - wAlice, accAlice, kpAlice := util.MakeRandPerunWallet() - wBob, accBob, kpBob := util.MakeRandPerunWallet() - _, _, kpDepToken := util.MakeRandPerunWallet() - _, _, kpDepPerun := util.MakeRandPerunWallet() - kps := []*keypair.Full{kpAlice, kpBob, kpDepToken, kpDepPerun} - - checkErr(util.CreateFundStellarAccounts(kps, len(kps), "1000000")) - - tokenAddr, _ := util.Deploy(kpDepToken, StellarAssetContractPath) - checkErr(util.InitTokenContract(kpDepToken, tokenAddr)) - - aliceAddr, err := util.MakeAccountAddress(kpAlice) - checkErr(err) - bobAddr, err := util.MakeAccountAddress(kpBob) - checkErr(err) - - checkErr(util.MintToken(kpDepToken, tokenAddr, 1000000, aliceAddr)) - checkErr(util.MintToken(kpDepToken, tokenAddr, 1000000, bobAddr)) - - perunAddr, _ := util.Deploy(kpDepPerun, PerunContractPath) - - bus := wire.NewLocalBus() - alicePerun, err := client.SetupPaymentClient(wAlice, accAlice, kpAlice, tokenAddr, perunAddr, bus) - checkErr(err) - bobPerun, err := client.SetupPaymentClient(wBob, accBob, kpBob, tokenAddr, perunAddr, bus) - checkErr(err) - - alicePerun.OpenChannel(bobPerun.WireAddress(), 1000) - aliceChannel, bobChannel := alicePerun.Channel, bobPerun.AcceptedChannel() - - aliceChannel.SendPayment(150) - bobChannel.SendPayment(220) - - aliceChannel.Settle() - bobChannel.Settle() - - alicePerun.Shutdown() - bobPerun.Shutdown() - - tokenBalAlice, err := util.GetTokenBalance(kpAlice, tokenAddr, aliceAddr) - fmt.Println("New Token Balance (Alice): ", tokenBalAlice) - - checkErr(err) - tokenBalBob, err := util.GetTokenBalance(kpBob, tokenAddr, bobAddr) - fmt.Println("New Token Balance (Bob): ", tokenBalBob) - checkErr(err) - log.Println("DONE") -} - -func checkErr(err error) { - if err != nil { - panic(err) - } -} From 6ed4799a79176ee167ad55779f957dd12328d207 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Thu, 4 Jan 2024 16:15:04 +0100 Subject: [PATCH 25/51] Add license, remove comments --- channel/adjudicator.go | 14 +++++++++++ channel/adjudicator_sub.go | 14 +++++++++++ channel/backend.go | 14 +++++++++++ channel/deploy.go | 14 +++++++++++ channel/env/client.go | 16 +++++++++++-- channel/env/transaction.go | 14 +++++++++++ channel/event.go | 14 +++++++++++ channel/funder.go | 14 +++++++++++ channel/funder_test.go | 15 ++++++++++++ channel/subscribe.go | 14 +++++++++++ channel/test/fund.go | 48 ++++++++++++++++++++++++------------- channel/test/init.go | 14 +++++++++++ channel/test/randomizer.go | 14 +++++++++++ channel/test/setup.go | 22 ++++++++++++----- channel/timeout.go | 4 ++-- channel/token.go | 26 ++++++++++---------- channel/types/asset.go | 14 +++++++++++ channel/types/asset_test.go | 14 +++++++++++ wallet/account.go | 14 +++++++++++ wallet/backend.go | 14 +++++++++++ wallet/test/init.go | 14 +++++++++++ wallet/test/randomizer.go | 14 +++++++++++ wallet/types/address.go | 14 +++++++++++ wallet/wallet.go | 14 +++++++++++ wallet/wallet_test.go | 14 +++++++++++ wire/balances.go | 14 +++++++++++ wire/balances_test.go | 14 +++++++++++ wire/channel.go | 14 +++++++++++ wire/channel_test.go | 14 +++++++++++ wire/control.go | 14 +++++++++++ wire/control_test.go | 14 +++++++++++ wire/invocation.go | 14 +++++++++++ wire/params.go | 14 +++++++++++ wire/params_test.go | 14 +++++++++++ wire/participant.go | 14 +++++++++++ wire/participant_test.go | 14 +++++++++++ wire/scmap.go | 14 +++++++++++ wire/scval/bool.go | 14 +++++++++++ wire/scval/scval.go | 14 +++++++++++ wire/state.go | 14 +++++++++++ wire/state_test.go | 14 +++++++++++ 41 files changed, 582 insertions(+), 39 deletions(-) create mode 100644 channel/funder_test.go diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 610d2e3..5f15d9d 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index 28720cb..4794934 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( diff --git a/channel/backend.go b/channel/backend.go index 59ec995..80b5551 100644 --- a/channel/backend.go +++ b/channel/backend.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( diff --git a/channel/deploy.go b/channel/deploy.go index 1cf6dcf..6ab8481 100644 --- a/channel/deploy.go +++ b/channel/deploy.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( diff --git a/channel/env/client.go b/channel/env/client.go index 4cd8ddd..46cbe6b 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -1,11 +1,23 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package env import ( "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" - // "github.com/stellar/go/txnbuild" - // "log" ) const HorizonURL = "http://localhost:8000" diff --git a/channel/env/transaction.go b/channel/env/transaction.go index 27f8f91..e537fec 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package env import ( diff --git a/channel/event.go b/channel/event.go index 49ece14..877b66d 100644 --- a/channel/event.go +++ b/channel/event.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( diff --git a/channel/funder.go b/channel/funder.go index 5a3356d..848843f 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( diff --git a/channel/funder_test.go b/channel/funder_test.go new file mode 100644 index 0000000..8a15574 --- /dev/null +++ b/channel/funder_test.go @@ -0,0 +1,15 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package channel_test diff --git a/channel/subscribe.go b/channel/subscribe.go index 94603d0..bec2e7b 100644 --- a/channel/subscribe.go +++ b/channel/subscribe.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( diff --git a/channel/test/fund.go b/channel/test/fund.go index aa21403..2907a24 100644 --- a/channel/test/fund.go +++ b/channel/test/fund.go @@ -1,23 +1,37 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package test import ( -// "context" -// pchannel "perun.network/go-perun/channel" -// "perun.network/perun-stellar-backend/channel" -// pkgerrors "polycry.pt/poly-go/errors" + "context" + pchannel "perun.network/go-perun/channel" + "perun.network/perun-stellar-backend/channel" + pkgerrors "polycry.pt/poly-go/errors" ) -// func FundAll(ctx context.Context, funders []*channel.Funder, reqs []*pchannel.FundingReq) error { -// g := pkgerrors.NewGatherer() -// for i := range funders { -// i := i -// g.Go(func() error { -// return funders[i].Fund(ctx, *reqs[i]) -// }) -// } +func FundAll(ctx context.Context, funders []*channel.Funder, reqs []*pchannel.FundingReq) error { + g := pkgerrors.NewGatherer() + for i := range funders { + i := i + g.Go(func() error { + return funders[i].Fund(ctx, *reqs[i]) + }) + } -// if g.WaitDoneOrFailedCtx(ctx) { -// return ctx.Err() -// } -// return g.Err() -// } + if g.WaitDoneOrFailedCtx(ctx) { + return ctx.Err() + } + return g.Err() +} diff --git a/channel/test/init.go b/channel/test/init.go index a39657d..7d99e2f 100644 --- a/channel/test/init.go +++ b/channel/test/init.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package test import "perun.network/go-perun/channel/test" diff --git a/channel/test/randomizer.go b/channel/test/randomizer.go index 92cb05f..685244f 100644 --- a/channel/test/randomizer.go +++ b/channel/test/randomizer.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package test import ( diff --git a/channel/test/setup.go b/channel/test/setup.go index 6200b82..70bc4c4 100644 --- a/channel/test/setup.go +++ b/channel/test/setup.go @@ -1,16 +1,26 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package test import ( "math/big" - "testing" - - //"perun.network/go-perun/backend/sim/wallet" pchannel "perun.network/go-perun/channel" - pwallet "perun.network/go-perun/wallet" - //pwtest "perun.network/go-perun/wallet/test" - ptest "perun.network/go-perun/channel/test" + pwallet "perun.network/go-perun/wallet" pkgtest "polycry.pt/poly-go/test" + "testing" ) func NewParamsState(t *testing.T) (*pchannel.Params, *pchannel.State) { diff --git a/channel/timeout.go b/channel/timeout.go index a23889b..7c35892 100644 --- a/channel/timeout.go +++ b/channel/timeout.go @@ -1,10 +1,10 @@ -// Copyright 2023 - See NOTICE file for copyright holders. +// Copyright 2023 PolyCrypt GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, diff --git a/channel/token.go b/channel/token.go index d5e8bf6..3deba43 100644 --- a/channel/token.go +++ b/channel/token.go @@ -1,19 +1,21 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package channel import ( - // "context" - // "errors" - // "github.com/stellar/go/keypair" - // "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/xdr" - // "github.com/stellar/protocols/horizon" - // "github.com/stellar/go/clients/horizonclient" - - // "perun.network/perun-stellar-backend/channel/env" - // "perun.network/perun-stellar-backend/channel/types" - - // "perun.network/perun-stellar-backend/wire" - "perun.network/perun-stellar-backend/wire/scval" ) diff --git a/channel/types/asset.go b/channel/types/asset.go index 362b4d5..7875650 100644 --- a/channel/types/asset.go +++ b/channel/types/asset.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package types import ( diff --git a/channel/types/asset_test.go b/channel/types/asset_test.go index 97c552f..bbc9e30 100644 --- a/channel/types/asset_test.go +++ b/channel/types/asset_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package types_test import ( diff --git a/wallet/account.go b/wallet/account.go index e373def..dea881c 100644 --- a/wallet/account.go +++ b/wallet/account.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wallet import ( diff --git a/wallet/backend.go b/wallet/backend.go index 4a6a040..e2cf822 100644 --- a/wallet/backend.go +++ b/wallet/backend.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wallet import ( diff --git a/wallet/test/init.go b/wallet/test/init.go index 4117711..2ad438b 100644 --- a/wallet/test/init.go +++ b/wallet/test/init.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package test import ( diff --git a/wallet/test/randomizer.go b/wallet/test/randomizer.go index 72d0f3a..8424803 100644 --- a/wallet/test/randomizer.go +++ b/wallet/test/randomizer.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package test import ( diff --git a/wallet/types/address.go b/wallet/types/address.go index 071f45f..515a000 100644 --- a/wallet/types/address.go +++ b/wallet/types/address.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package types import ( diff --git a/wallet/wallet.go b/wallet/wallet.go index 8e502ea..1d9640f 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wallet import ( diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index 8ee5525..2d920f8 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wallet_test import ( diff --git a/wire/balances.go b/wire/balances.go index 04b5055..4ae6c70 100644 --- a/wire/balances.go +++ b/wire/balances.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/balances_test.go b/wire/balances_test.go index 3e6f5ee..51c16cc 100644 --- a/wire/balances_test.go +++ b/wire/balances_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire_test import ( diff --git a/wire/channel.go b/wire/channel.go index a45b557..2c88095 100644 --- a/wire/channel.go +++ b/wire/channel.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/channel_test.go b/wire/channel_test.go index dd356ad..83d5240 100644 --- a/wire/channel_test.go +++ b/wire/channel_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire_test import ( diff --git a/wire/control.go b/wire/control.go index 7d352cb..bc723a0 100644 --- a/wire/control.go +++ b/wire/control.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/control_test.go b/wire/control_test.go index 149f085..b48f6a2 100644 --- a/wire/control_test.go +++ b/wire/control_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire_test import ( diff --git a/wire/invocation.go b/wire/invocation.go index 6e9dbc2..521a63b 100644 --- a/wire/invocation.go +++ b/wire/invocation.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/params.go b/wire/params.go index 98c30b5..bdd82dd 100644 --- a/wire/params.go +++ b/wire/params.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/params_test.go b/wire/params_test.go index 51ba24b..a8b6829 100644 --- a/wire/params_test.go +++ b/wire/params_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire_test import ( diff --git a/wire/participant.go b/wire/participant.go index b689c62..25cf0d0 100644 --- a/wire/participant.go +++ b/wire/participant.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/participant_test.go b/wire/participant_test.go index ef82384..d565952 100644 --- a/wire/participant_test.go +++ b/wire/participant_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire_test import ( diff --git a/wire/scmap.go b/wire/scmap.go index 93e6321..4965eb9 100644 --- a/wire/scmap.go +++ b/wire/scmap.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/scval/bool.go b/wire/scval/bool.go index cec5fc4..99ee45f 100644 --- a/wire/scval/bool.go +++ b/wire/scval/bool.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package scval import "github.com/stellar/go/xdr" diff --git a/wire/scval/scval.go b/wire/scval/scval.go index 11a0c65..9bae5b5 100644 --- a/wire/scval/scval.go +++ b/wire/scval/scval.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package scval import "github.com/stellar/go/xdr" diff --git a/wire/state.go b/wire/state.go index 70c4aa8..eeaa6f9 100644 --- a/wire/state.go +++ b/wire/state.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire import ( diff --git a/wire/state_test.go b/wire/state_test.go index 1c93888..b892492 100644 --- a/wire/state_test.go +++ b/wire/state_test.go @@ -1,3 +1,17 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package wire_test import ( From a9f90ca4c6f0d61dde90f912bcacb75e4e15f35d Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Wed, 17 Jan 2024 16:58:32 +0100 Subject: [PATCH 26/51] refactor: Remove wallet/test/init.go because the wallet Randomizer initialization is performed at other places already --- wallet/test/init.go | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 wallet/test/init.go diff --git a/wallet/test/init.go b/wallet/test/init.go deleted file mode 100644 index 2ad438b..0000000 --- a/wallet/test/init.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023 PolyCrypt GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test - -import ( - "perun.network/go-perun/wallet/test" - _ "perun.network/perun-stellar-backend/channel/test" -) - -func init() { - test.SetRandomizer(&Randomizer{}) -} From 56f760a0f7d2c2dffc4e1f0ea6b66eeadd9a722a Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 19 Jan 2024 17:33:02 +0100 Subject: [PATCH 27/51] refactor(channel/token): Move token.go into channel/test because it is only used for testing --- channel/test/token.go | 223 +++++++++++++++++++++++++++++++++++++++++ channel/token.go | 227 ------------------------------------------ 2 files changed, 223 insertions(+), 227 deletions(-) create mode 100644 channel/test/token.go delete mode 100644 channel/token.go diff --git a/channel/test/token.go b/channel/test/token.go new file mode 100644 index 0000000..eca821e --- /dev/null +++ b/channel/test/token.go @@ -0,0 +1,223 @@ +// Copyright 2023 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/require" + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/channel/types" + "perun.network/perun-stellar-backend/wire/scval" + "testing" +) + +const tokenDecimals = uint32(7) +const tokenName = "PerunToken" +const tokenSymbol = "PRN" + +type TokenParams struct { + decimals uint32 + name string + symbol string +} + +func NewTokenParams() *TokenParams { + return &TokenParams{ + decimals: tokenDecimals, + name: tokenName, + symbol: tokenSymbol, + } +} + +func (t *TokenParams) GetDecimals() uint32 { + return t.decimals +} + +func (t *TokenParams) GetName() string { + return t.name +} + +func (t *TokenParams) GetSymbol() string { + return t.symbol +} + +func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName string, tokenSymbol string) (xdr.ScVec, error) { + + adminScAddr, err := scval.WrapScAddress(adminAddr) + if err != nil { + panic(err) + } + + decim := xdr.Uint32(decimals) + scvaltype := xdr.ScValTypeScvU32 + decimSc, err := xdr.NewScVal(scvaltype, decim) + if err != nil { + panic(err) + } + + tokenNameScString := xdr.ScString(tokenName) + tokenNameXdr := scval.MustWrapScString(tokenNameScString) + + tokenSymbolString := xdr.ScString(tokenSymbol) + tokenSymbolXdr := scval.MustWrapScString(tokenSymbolString) + + initTokenArgs := xdr.ScVec{ + adminScAddr, + decimSc, + tokenNameXdr, + tokenSymbolXdr, + } + + return initTokenArgs, nil +} + +func InitTokenContract(kp *keypair.Full, contractIDAddress xdr.ScAddress) error { + + stellarClient := env.NewStellarClient(kp) + adminScAddr, err := types.MakeAccountAddress(kp) + if err != nil { + panic(err) + } + + tokenParams := NewTokenParams() + decimals := tokenParams.GetDecimals() + name := tokenParams.GetName() + symbol := tokenParams.GetSymbol() + + initArgs, err := BuildInitTokenArgs(adminScAddr, decimals, name, symbol) + if err != nil { + panic(err) + } + _, err = stellarClient.InvokeAndProcessHostFunction("initialize", initArgs, contractIDAddress, kp) + if err != nil { + panic(err) + } + + return nil +} + +func GetTokenName(kp *keypair.Full, contractAddress xdr.ScAddress) error { + + stellarClient := env.NewStellarClient(kp) + TokenNameArgs := xdr.ScVec{} + + _, err := stellarClient.InvokeAndProcessHostFunction("name", TokenNameArgs, contractAddress, kp) + if err != nil { + panic(err) + } + + return nil +} + +func BuildGetTokenBalanceArgs(balanceOf xdr.ScAddress) (xdr.ScVec, error) { + + recScAddr, err := scval.WrapScAddress(balanceOf) + if err != nil { + panic(err) + } + + GetTokenBalanceArgs := xdr.ScVec{ + recScAddr, + } + + return GetTokenBalanceArgs, nil +} + +func BuildTransferTokenArgs(from xdr.ScAddress, to xdr.ScAddress, amount xdr.Int128Parts) (xdr.ScVec, error) { + + fromScAddr, err := scval.WrapScAddress(from) + if err != nil { + panic(err) + } + + toScAddr, err := scval.WrapScAddress(to) + if err != nil { + panic(err) + } + + amountSc, err := scval.WrapInt128Parts(amount) + if err != nil { + panic(err) + } + + GetTokenBalanceArgs := xdr.ScVec{ + fromScAddr, + toScAddr, + amountSc, + } + + return GetTokenBalanceArgs, nil +} + +func Deploy(t *testing.T, kp *keypair.Full, contractPath string) (xdr.ScAddress, xdr.Hash) { + deployerClient := env.NewStellarClient(kp) + hzClient := deployerClient.GetHorizonClient() + deployerAccReq := horizonclient.AccountRequest{AccountID: kp.Address()} + deployerAcc, err := hzClient.AccountDetail(deployerAccReq) + + require.NoError(t, err) + + installContractOpInstall := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) + preFlightOp, minFeeInstall := env.PreflightHostFunctions(hzClient, &deployerAcc, *installContractOpInstall) + txParamsInstall := env.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeInstall), &preFlightOp) + txSignedInstall, err := env.CreateSignedTransactionWithParams([]*keypair.Full{kp}, txParamsInstall) + require.NoError(t, err) + + _, err = hzClient.SubmitTransaction(txSignedInstall) + + require.NoError(t, err) + + createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", env.NETWORK_PASSPHRASE) + preFlightOpCreate, minFeeCreate := env.PreflightHostFunctions(hzClient, &deployerAcc, *createContractOp) + txParamsCreate := env.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeCreate), &preFlightOpCreate) + txSignedCreate, err := env.CreateSignedTransactionWithParams([]*keypair.Full{kp}, txParamsCreate) + + require.NoError(t, err) + + _, err = hzClient.SubmitTransaction(txSignedCreate) + require.NoError(t, err) + + contractID := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + contractHash := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash + contractIDAddress := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: contractID, + } + + return contractIDAddress, contractHash +} + +func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount uint64, recipientAddr xdr.ScAddress) error { + stellarClient := env.NewStellarClient(kp) + + amountTo128Xdr := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(amount)} + + amountSc, err := xdr.NewScVal(xdr.ScValTypeScvI128, amountTo128Xdr) + if err != nil { + panic(err) + } + mintTokenArgs, err := env.BuildMintTokenArgs(recipientAddr, amountSc) + if err != nil { + panic(err) + } + _, err = stellarClient.InvokeAndProcessHostFunction("mint", mintTokenArgs, contractAddr, kp) + if err != nil { + panic(err) + } + return nil +} diff --git a/channel/token.go b/channel/token.go deleted file mode 100644 index 3deba43..0000000 --- a/channel/token.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2023 PolyCrypt GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package channel - -import ( - "github.com/stellar/go/xdr" - "perun.network/perun-stellar-backend/wire/scval" -) - -const tokenDecimals = uint32(7) -const tokenName = "PerunToken" -const tokenSymbol = "PRN" - -type TokenParams struct { - decimals uint32 - name string - symbol string -} - -func NewTokenParams() *TokenParams { - return &TokenParams{ - decimals: tokenDecimals, - name: tokenName, - symbol: tokenSymbol, - } -} - -func (t *TokenParams) GetDecimals() uint32 { - return t.decimals -} - -func (t *TokenParams) GetName() string { - return t.name -} - -func (t *TokenParams) GetSymbol() string { - return t.symbol -} - -func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName string, tokenSymbol string) (xdr.ScVec, error) { - - adminScAddr, err := scval.WrapScAddress(adminAddr) - if err != nil { - panic(err) - } - - decim := xdr.Uint32(decimals) - scvaltype := xdr.ScValTypeScvU32 - decimSc, err := xdr.NewScVal(scvaltype, decim) - if err != nil { - panic(err) - } - - tokenNameScString := xdr.ScString(tokenName) - tokenNameXdr := scval.MustWrapScString(tokenNameScString) - - tokenSymbolString := xdr.ScString(tokenSymbol) - tokenSymbolXdr := scval.MustWrapScString(tokenSymbolString) - - initTokenArgs := xdr.ScVec{ - adminScAddr, - decimSc, - tokenNameXdr, - tokenSymbolXdr, - } - - return initTokenArgs, nil -} - -// func InitTokenContract(hzClient *env.StellarClient, hzAccount horizon.Account, kpAdmin *keypair.Full, contractAddress xdr.ScAddress) error { - -// tokenParams := NewTokenParams() -// adminScAddr, err := types.MakeAccountAddress(kpAdmin) - -// if err != nil { -// panic(err) -// } -// initTokenArgs, err := wire.BuildInitTokenArgs(adminScAddr, tokenParams.decimals, tokenParams.name, tokenParams.symbol) - -// if err != nil { -// return errors.New("error while building fund tx") -// } -// auth := []xdr.SorobanAuthorizationEntry{} - -// _, err = hzClient.GetHorizonClient().InvokeAndProcessHostFunction(hzAccount, "initialize", initTokenArgs, contractAddress, kpAdmin, auth) -// if err != nil { -// return errors.New("error while invoking and processing host function for InitTokenContract") -// } - -// return nil -// } - -func BuildTokenNameArgs() (xdr.ScVec, error) { - - return xdr.ScVec{}, nil -} - -func BuildGetTokenBalanceArgs(balanceOf xdr.ScAddress) (xdr.ScVec, error) { - - recScAddr, err := scval.WrapScAddress(balanceOf) - if err != nil { - panic(err) - } - - GetTokenBalanceArgs := xdr.ScVec{ - recScAddr, - } - - return GetTokenBalanceArgs, nil -} - -func BuildTransferTokenArgs(from xdr.ScAddress, to xdr.ScAddress, amount xdr.Int128Parts) (xdr.ScVec, error) { - - fromScAddr, err := scval.WrapScAddress(from) - if err != nil { - panic(err) - } - - toScAddr, err := scval.WrapScAddress(to) - if err != nil { - panic(err) - } - - amountSc, err := scval.WrapInt128Parts(amount) - if err != nil { - panic(err) - } - - GetTokenBalanceArgs := xdr.ScVec{ - fromScAddr, - toScAddr, - amountSc, - } - - return GetTokenBalanceArgs, nil -} - -// func GetTokenName(ctx context.Context, stellarClient *env.StellarClient, contractAddress xdr.ScAddress) error { - -// hzAcc := stellarClient.GetHorizonAcc() -// kp := stellarClient.GetAccount() -// // generate tx to open the channel -// TokenNameArgs, err := BuildTokenNameArgs() -// if err != nil { -// return errors.New("error while building fund tx") -// } -// auth := []xdr.SorobanAuthorizationEntry{} - -// _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "name", TokenNameArgs, contractAddress, kp, auth) -// if err != nil { -// return errors.New("error while invoking and processing host function for GetTokenName") -// } - -// return nil -// } - -// func MintToken(ctx context.Context, stellarClient *env.StellarClient, mintTo xdr.ScAddress, mintAmount int64, contractAddress xdr.ScAddress) error { - -// hzAcc := stellarClient.GetHorizonAcc() -// kp := stellarClient.GetAccount() -// TokenNameArgs, err := BuildMintTokenArgs(mintTo, mintAmount) -// if err != nil { -// return errors.New("error while building fund tx") -// } -// auth := []xdr.SorobanAuthorizationEntry{} -// _, err = stellarClient.InvokeAndProcessHostFunction(hzAcc, "mint", TokenNameArgs, contractAddress, kp, auth) -// if err != nil { -// return errors.New("error while invoking and processing host function for GetTokenName") -// } - -// return nil -// } - -// func GetTokenBalance(ctx context.Context, stellarClient *env.StellarClient, balanceOf xdr.ScAddress, contractAddress xdr.ScAddress) error { - -// hzAcc := stellarClient.GetHorizonAcc() -// kp := stellarClient.GetAccount() -// // generate tx to open the channel -// getBalanceArgs, err := BuildGetTokenBalanceArgs(balanceOf) -// if err != nil { -// return errors.New("error while building fund tx") -// } -// auth := []xdr.SorobanAuthorizationEntry{} -// txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "balance", getBalanceArgs, contractAddress, kp, auth) -// if err != nil { -// return errors.New("error while invoking and processing host function for GetTokenName") -// } - -// _ = txMeta.V3.SorobanMeta.ReturnValue - -// // rVal := reVal.MustI128() - -// return nil -// } - -// func TransferToken(ctx context.Context, stellarClient *env.StellarClient, from xdr.ScAddress, to xdr.ScAddress, amount128 xdr.Int128Parts, contractAddress xdr.ScAddress) error { - -// hzAcc := stellarClient.GetHorizonAcc() -// kp := stellarClient.GetAccount() -// transferArgs, err := BuildTransferTokenArgs(from, to, amount128) -// if err != nil { -// return errors.New("error while building fund tx") -// } -// auth := []xdr.SorobanAuthorizationEntry{} -// txMeta, err := stellarClient.InvokeAndProcessHostFunction(hzAcc, "transfer", transferArgs, contractAddress, kp, auth) -// if err != nil { -// return errors.New("error while invoking and processing host function for GetTokenName") -// } - -// _ = txMeta.V3.SorobanMeta.ReturnValue - -// // rVal := reVal.MustI128() - -// return nil -// } From 198ebd7010359b8dfbe4e0136c444e9e1eafe45c Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 19 Jan 2024 17:35:05 +0100 Subject: [PATCH 28/51] refactor(channel/test): Add Wallet Randomizer into init.go for testing --- channel/test/init.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/channel/test/init.go b/channel/test/init.go index 7d99e2f..692fb4f 100644 --- a/channel/test/init.go +++ b/channel/test/init.go @@ -14,8 +14,14 @@ package test -import "perun.network/go-perun/channel/test" +import ( + pchtest "perun.network/go-perun/channel/test" + pwtest "perun.network/go-perun/wallet/test" + wtest "perun.network/perun-stellar-backend/wallet/test" +) func init() { - test.SetRandomizer(&Randomizer{}) + pchtest.SetRandomizer(&Randomizer{}) + walletRdz := wtest.Randomizer{} + pwtest.SetRandomizer(&walletRdz) } From e3424f385ad1bf8bf68f34bd216d7375a98b7b2c Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 19 Jan 2024 19:09:52 +0100 Subject: [PATCH 29/51] test(channel/test): Add Setup struct and the required auxiliary functions to perform tests --- channel/test/setup.go | 228 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 214 insertions(+), 14 deletions(-) diff --git a/channel/test/setup.go b/channel/test/setup.go index 70bc4c4..9174b38 100644 --- a/channel/test/setup.go +++ b/channel/test/setup.go @@ -15,39 +15,232 @@ package test import ( + "context" + "crypto/rand" + "encoding/binary" + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/require" + "log" "math/big" + mathrand "math/rand" pchannel "perun.network/go-perun/channel" ptest "perun.network/go-perun/channel/test" pwallet "perun.network/go-perun/wallet" + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/env" + "perun.network/perun-stellar-backend/channel/types" + "perun.network/perun-stellar-backend/wallet" pkgtest "polycry.pt/poly-go/test" "testing" + "time" ) -func NewParamsState(t *testing.T) (*pchannel.Params, *pchannel.State) { +const ( + PerunContractPath = "../testdata/perun_soroban_contract.wasm" + StellarAssetContractPath = "../testdata/perun_soroban_token.wasm" + initLumensBalance = "10000000" + initTokenBalance = uint64(2000000) + DefaultTestTimeout = 200 +) - rng := pkgtest.Prng(t) +type Setup struct { + t *testing.T + accs []*wallet.Account + stellarClients []*env.StellarClient + Rng *mathrand.Rand + funders []*channel.Funder + adjs []*channel.Adjudicator + assetID pchannel.Asset +} - numParts := 2 +func (s *Setup) GetStellarClients() []*env.StellarClient { + return s.stellarClients +} - return ptest.NewRandomParamsAndState(rng, ptest.WithNumLocked(0).Append( - ptest.WithVersion(0), - ptest.WithNumParts(numParts), - ptest.WithIsFinal(false), - ptest.WithLedgerChannel(true), - ptest.WithVirtualChannel(false), - ptest.WithNumAssets(1), - ptest.WithoutApp(), - ptest.WithBalancesInRange(big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000)), big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000))), - )) +func (s *Setup) GetFunders() []*channel.Funder { + return s.funders +} + +func (s *Setup) GetTokenAsset() pchannel.Asset { + return s.assetID +} + +func (s *Setup) GetAccounts() []*wallet.Account { + return s.accs +} + +func NewTestSetup(t *testing.T) *Setup { + + accs, kpsToFund := MakeRandPerunAccs(4) + require.NoError(t, CreateFundStellarAccounts(kpsToFund, initLumensBalance)) + + depTokenKp := kpsToFund[2] + depPerunKp := kpsToFund[3] + + tokenAddress, _ := Deploy(t, depTokenKp, StellarAssetContractPath) + perunAddress, _ := Deploy(t, depPerunKp, PerunContractPath) + + require.NoError(t, InitTokenContract(depTokenKp, tokenAddress)) + + setupAccountsAndContracts(t, depTokenKp, kpsToFund[:2], tokenAddress, initTokenBalance) + + assetContractID, err := types.NewStellarAssetFromScAddress(tokenAddress) + require.NoError(t, err) + + stellarClients := NewStellarClients(kpsToFund) + + aliceClient := stellarClients[0] + bobClient := stellarClients[1] + channelAccs := []*wallet.Account{accs[0], accs[1]} + channelClients := []*env.StellarClient{aliceClient, bobClient} + + funders, adjs := createFundersAndAdjudicators(channelAccs, kpsToFund, stellarClients, perunAddress, tokenAddress) + + setup := Setup{ + t: t, + accs: channelAccs, + stellarClients: channelClients, + funders: funders, + adjs: adjs, + assetID: assetContractID, + } + + return &setup +} + +func setupAccountsAndContracts(t *testing.T, deployerKp *keypair.Full, kps []*keypair.Full, tokenAddress xdr.ScAddress, tokenBalance uint64) { + for _, kp := range kps { + addr, err := types.MakeAccountAddress(kp) + require.NoError(t, err) + require.NoError(t, MintToken(deployerKp, tokenAddress, tokenBalance, addr)) + } +} + +func createFundersAndAdjudicators(accs []*wallet.Account, kps []*keypair.Full, clients []*env.StellarClient, perunAddress, tokenAddress xdr.ScAddress) ([]*channel.Funder, []*channel.Adjudicator) { + funders := make([]*channel.Funder, len(accs)) + adjs := make([]*channel.Adjudicator, len(accs)) + for i, acc := range accs { + funders[i] = channel.NewFunder(acc, kps[i], clients[i], perunAddress, tokenAddress) + adjs[i] = channel.NewAdjudicator(acc, kps[i], clients[i], perunAddress, tokenAddress) + } + return funders, adjs +} + +func NewStellarClients(kps []*keypair.Full) []*env.StellarClient { + clients := make([]*env.StellarClient, len(kps)) + for i, kp := range kps { + clients[i] = env.NewStellarClient(kp) + } + return clients } -func NewParamsWithAddressState(t *testing.T, partsAddr []pwallet.Address) (*pchannel.Params, *pchannel.State) { +func MakeRandPerunAccs(count int) ([]*wallet.Account, []*keypair.Full) { + accs := make([]*wallet.Account, count) + kps := make([]*keypair.Full, count) + + for i := 0; i < count; i++ { + acc, kp := MakeRandPerunAcc() + accs[i] = acc + kps[i] = kp + } + return accs, kps +} + +func MakeRandPerunAcc() (*wallet.Account, *keypair.Full) { + w := wallet.NewEphemeralWallet() + + var b [8]byte + _, err := rand.Read(b[:]) + if err != nil { + panic(err) + } + + seed := binary.LittleEndian.Uint64(b[:]) + + r := mathrand.New(mathrand.NewSource(int64(seed))) + + acc, kp, err := w.AddNewAccount(r) + if err != nil { + panic(err) + } + return acc, kp +} + +func CreateFundStellarAccounts(pairs []*keypair.Full, initialBalance string) error { + + numKps := len(pairs) + + masterClient := env.NewHorizonMasterClient() + masterHzClient := masterClient.GetMaster() + sourceKey := masterClient.GetSourceKey() + + hzClient := env.NewHorizonClient() + + ops := make([]txnbuild.Operation, numKps) + + accReq := horizonclient.AccountRequest{AccountID: sourceKey.Address()} + sourceAccount, err := masterHzClient.AccountDetail(accReq) + if err != nil { + panic(err) + } + + masterAccount := txnbuild.SimpleAccount{ + AccountID: sourceKey.Address(), + Sequence: sourceAccount.Sequence, + } + + for i := 0; i < numKps; i++ { + pair := pairs[i] + + ops[i] = &txnbuild.CreateAccount{ + SourceAccount: masterAccount.AccountID, + Destination: pair.Address(), + Amount: initialBalance, + } + } + + txParams := env.GetBaseTransactionParamsWithFee(&masterAccount, txnbuild.MinBaseFee, ops...) + + txSigned, err := env.CreateSignedTransactionWithParams([]*keypair.Full{sourceKey}, txParams) + + if err != nil { + panic(err) + } + _, err = hzClient.SubmitTransaction(txSigned) + if err != nil { + panic(err) + } + + accounts := make([]txnbuild.Account, numKps) + for i, kp := range pairs { + request := horizonclient.AccountRequest{AccountID: kp.Address()} + account, err := hzClient.AccountDetail(request) + if err != nil { + panic(err) + } + + accounts[i] = &account + } + + for _, keys := range pairs { + log.Printf("Funded %s (%s) with %s XLM.\n", + keys.Seed(), keys.Address(), initialBalance) + } + + return nil +} + +func NewParamsWithAddressStateWithAsset(t *testing.T, partsAddr []pwallet.Address, asset pchannel.Asset) (*pchannel.Params, *pchannel.State) { rng := pkgtest.Prng(t) numParts := 2 return ptest.NewRandomParamsAndState(rng, ptest.WithNumLocked(0).Append( + ptest.WithAssets(asset), ptest.WithVersion(0), ptest.WithNumParts(numParts), ptest.WithParts(partsAddr...), @@ -59,3 +252,10 @@ func NewParamsWithAddressState(t *testing.T, partsAddr []pwallet.Address) (*pcha ptest.WithBalancesInRange(big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000)), big.NewInt(0).Mul(big.NewInt(1), big.NewInt(100_000))), )) } + +func (s *Setup) NewCtx() context.Context { + timeout := time.Duration(float64(DefaultTestTimeout) * float64(time.Second)) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + s.t.Cleanup(cancel) + return ctx +} From 96ccb68f9b821a8130f48a8a8fd80c5beb0f6fa9 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 19 Jan 2024 19:11:30 +0100 Subject: [PATCH 30/51] test(channel): Add Funding Test --- channel/funder_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/channel/funder_test.go b/channel/funder_test.go index 8a15574..ad8a321 100644 --- a/channel/funder_test.go +++ b/channel/funder_test.go @@ -13,3 +13,31 @@ // limitations under the License. package channel_test + +import ( + "github.com/stretchr/testify/require" + pchannel "perun.network/go-perun/channel" + pwallet "perun.network/go-perun/wallet" + chtest "perun.network/perun-stellar-backend/channel/test" + "testing" +) + +func TestFunding(t *testing.T) { + setup := chtest.NewTestSetup(t) + stellarAsset := setup.GetTokenAsset() + accs := setup.GetAccounts() + addrAlice := accs[0].Address() + addrBob := accs[1].Address() + addrList := []pwallet.Address{addrAlice, addrBob} + perunParams, perunState := chtest.NewParamsWithAddressStateWithAsset(t, addrList, stellarAsset) + + freqAlice := pchannel.NewFundingReq(perunParams, perunState, 0, perunState.Balances) + freqBob := pchannel.NewFundingReq(perunParams, perunState, 1, perunState.Balances) + + freqs := []*pchannel.FundingReq{freqAlice, freqBob} + + funders := setup.GetFunders() + ctx := setup.NewCtx() + err := chtest.FundAll(ctx, funders, freqs) + require.NoError(t, err) +} From 453719db92e25c782a3e37ba8234d9f0ac05cfeb Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Mon, 22 Jan 2024 18:45:30 +0100 Subject: [PATCH 31/51] docs: Add doc.go file, documenting the package contents --- channel/doc.go | 18 ++++++++++++++++++ channel/env/doc.go | 16 ++++++++++++++++ channel/types/doc.go | 16 ++++++++++++++++ wallet/doc.go | 16 ++++++++++++++++ wire/doc.go | 17 +++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 channel/doc.go create mode 100644 channel/env/doc.go create mode 100644 channel/types/doc.go create mode 100644 wallet/doc.go create mode 100644 wire/doc.go diff --git a/channel/doc.go b/channel/doc.go new file mode 100644 index 0000000..b7c939f --- /dev/null +++ b/channel/doc.go @@ -0,0 +1,18 @@ +// Copyright 2024 - See NOTICE file for copyright holders. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package channel contains all relevant components to generate, utilize and conclude payment channels on the Stellar blockchain. +// These main components consist of the Funder and Adjudicator interfaces that govern the interaction between the channel users. +// Additionally, the AdjEventSub interface processes the emitted events from the Soroban smart contracts, using the interfaces from go-perun. +package channel diff --git a/channel/env/doc.go b/channel/env/doc.go new file mode 100644 index 0000000..97b57fd --- /dev/null +++ b/channel/env/doc.go @@ -0,0 +1,16 @@ +// Copyright 2024 - See NOTICE file for copyright holders. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package env provides the functionality to connect to the Stellar blockchain and broadcast transactions to the network and decode its response. +package env diff --git a/channel/types/doc.go b/channel/types/doc.go new file mode 100644 index 0000000..9d847ae --- /dev/null +++ b/channel/types/doc.go @@ -0,0 +1,16 @@ +// Copyright 2024 - See NOTICE file for copyright holders. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package types defines the stellar asset that is used in payment channels. +package types diff --git a/wallet/doc.go b/wallet/doc.go new file mode 100644 index 0000000..cdfbc6d --- /dev/null +++ b/wallet/doc.go @@ -0,0 +1,16 @@ +// Copyright 2024 - See NOTICE file for copyright holders. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package wallet provides the necessary components to use Stellar accounts for Perun payment channels. +package wallet diff --git a/wire/doc.go b/wire/doc.go new file mode 100644 index 0000000..ece981c --- /dev/null +++ b/wire/doc.go @@ -0,0 +1,17 @@ +// Copyright 2024 - See NOTICE file for copyright holders. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package wire enables communication between go-perun and the Stellar blockchain. +// It defines the encoding of types required by go-perun and ensures the compatibility to the types used in Soroban smart contracts. +package wire From 0ea664932d1e5df0b2fb68e03cfe7bf9a93cf969 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Mon, 22 Jan 2024 18:46:29 +0100 Subject: [PATCH 32/51] docs(README): Include additional information in README --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5cef364..c658950 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,15 @@ # [Perun](https://perun.network/) Stellar backend -This repository contains the [Stellar](https://stellar.org/) backend for the [go-perun](https://github.com/perun-network/go-perun) channel library. It provides the necessary components to run our secure peer-to-peer Perun Payment Channels on the Stellar blockchain, using a [Soroban](https://soroban.stellar.org/) smart contract implementation. The payment channel implementation connects our Perun state machine with the [Perun contract](https://github.com/perun-network/perun-soroban-contract), which implements the Perun Payment Channel logic on the Stellar blockchain. This project is financed through the Stellar Community Fund grants program. +This repository contains the [Stellar](https://stellar.org/) backend for the [go-perun](https://github.com/perun-network/go-perun) channel library. It provides the necessary components to run our secure peer-to-peer Perun Payment Channels on the Stellar blockchain, using a [Soroban](https://soroban.stellar.org/) smart contract implementation. The payment channel implementation connects our Perun state machine with the [Perun contract](https://github.com/perun-network/perun-soroban-contract), which implements the Perun Payment Channel logic on the Stellar blockchain. To connect to the Stellar blockchain, we use the Horizon client service of the Stellar Go SDK, which is located [here](https://github.com/stellar/go). + +This project is financed through the Stellar Community Fund grants program. In the following sections, we will describe how to run our Payment Channels on a local instance of the Stellar blockchain. ## [Setup](#setup) -1. We use the Horizon client service of the Stellar Go SDK, which is located [here](https://github.com/stellar/go). The first step is cloning this repository: +1. Clone this repository: ``` git clone https://github.com/perun-network/perun-stellar-backend @@ -19,7 +21,7 @@ cd perun-stellar-backend ``` -2. To run a local Stellar blockchain with Soroban smart contract support, you initialize the docker images defined in the ```quickstart.sh``` script: +2. To run a local Stellar blockchain with Soroban smart contract support, you initialize the docker images defined in the ```quickstart.sh``` script. Docker needs to be installed to perform this step: ```sh @@ -29,7 +31,7 @@ cd perun-stellar-backend Note that this backend is customized to run on a local Stellar blockchain (standalone), but can be easily adapted to run on a public testnet. -3. Running the payment channels: +3. Running the payment channel tests: To make sure that your setup is correct, you can run the payment channel tests after cloning this repository: @@ -38,12 +40,10 @@ To make sure that your setup is correct, you can run the payment channel tests a go test ./... ``` -To run the simple payment channel demo, you can run the following command: +# Payment Channel Demo -```sh -go run main.go -``` +A demonstrator of Perun payment channels on the Stellar blockchain can be found [here](https://github.com/perun-network/perun-stellar-demo). -## Copyright +# Copyright -Copyright 2023 PolyCrypt GmbH. Use of the source code is governed by the Apache 2.0 license that can be found in the [LICENSE file](LICENSE). \ No newline at end of file +Copyright 2024 PolyCrypt GmbH. Use of the source code is governed by the Apache 2.0 license that can be found in the [LICENSE file](LICENSE). \ No newline at end of file From 8a694f64d53bdcc653d18a731660d234b040eb49 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Mon, 22 Jan 2024 18:50:57 +0100 Subject: [PATCH 33/51] * test(setup.go): Add GetAdjudicators method to retrieve the users' Adjudicator struct. * test(channel): Add adjudicator_test.go, containing a Happy channel test. It describes a standard channel lifetime process --- channel/adjudicator_test.go | 98 +++++++++++++++++++++++++++++++++++++ channel/test/setup.go | 4 ++ 2 files changed, 102 insertions(+) create mode 100644 channel/adjudicator_test.go diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go new file mode 100644 index 0000000..6dc08b7 --- /dev/null +++ b/channel/adjudicator_test.go @@ -0,0 +1,98 @@ +// Copyright 2024 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package channel_test + +import ( + "github.com/stretchr/testify/require" + pchannel "perun.network/go-perun/channel" + pwallet "perun.network/go-perun/wallet" + "perun.network/perun-stellar-backend/channel" + chtest "perun.network/perun-stellar-backend/channel/test" + "testing" +) + +func TestHappyChannel(t *testing.T) { + setup := chtest.NewTestSetup(t) + stellarAsset := setup.GetTokenAsset() + accs := setup.GetAccounts() + addrAlice := accs[0].Address() + addrBob := accs[1].Address() + addrList := []pwallet.Address{addrAlice, addrBob} + perunParams, perunState := chtest.NewParamsWithAddressStateWithAsset(t, addrList, stellarAsset) + + freqAlice := pchannel.NewFundingReq(perunParams, perunState, 0, perunState.Balances) + freqBob := pchannel.NewFundingReq(perunParams, perunState, 1, perunState.Balances) + + freqs := []*pchannel.FundingReq{freqAlice, freqBob} + + funders := setup.GetFunders() + ctx := setup.NewCtx() + err := chtest.FundAll(ctx, funders, freqs) + require.NoError(t, err) + + // funding complete + + // Withdrawal + { + adjAlice := setup.GetAdjudicators()[0] + adjBob := setup.GetAdjudicators()[1] + + ctxAliceWithdraw := setup.NewCtx() + ctxBobWithdraw := setup.NewCtx() + + adjState := perunState + next := adjState.Clone() + next.Version++ + next.IsFinal = true + encodedState, err := channel.EncodeState(next) + require.NoError(t, err) + signAlice, err := accs[0].SignData(encodedState) + require.NoError(t, err) + signBob, err := accs[1].SignData(encodedState) + require.NoError(t, err) + sigs := []pwallet.Sig{signAlice, signBob} + tx := pchannel.Transaction{State: next, Sigs: sigs} + + reqAlice := pchannel.AdjudicatorReq{ + Params: perunParams, + Tx: tx, + Acc: accs[0], + Idx: pchannel.Index(0), + Secondary: false} + + reqBob := pchannel.AdjudicatorReq{ + Params: perunParams, + Tx: tx, + Acc: accs[1], + Idx: pchannel.Index(1), + Secondary: false} + + require.NoError(t, adjAlice.Withdraw(ctxAliceWithdraw, reqAlice, nil)) + stellarChanAlice, err := adjAlice.GetChannelState(ctx, next) + + require.True(t, stellarChanAlice.Control.WithdrawnA) + + require.NoError(t, err) + require.NoError(t, adjBob.Withdraw(ctx, reqBob, nil)) + + stellarChanBob, err := adjBob.GetChannelState(ctxBobWithdraw, next) + + require.NoError(t, err) + + require.True(t, stellarChanBob.Control.WithdrawnB) + + } + +} diff --git a/channel/test/setup.go b/channel/test/setup.go index 9174b38..8a19517 100644 --- a/channel/test/setup.go +++ b/channel/test/setup.go @@ -64,6 +64,10 @@ func (s *Setup) GetFunders() []*channel.Funder { return s.funders } +func (s *Setup) GetAdjudicators() []*channel.Adjudicator { + return s.adjs +} + func (s *Setup) GetTokenAsset() pchannel.Asset { return s.assetID } From f4537e115f48ef4ce743f304bbaf55196d88c3f5 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Mon, 22 Jan 2024 19:00:06 +0100 Subject: [PATCH 34/51] style(channel): Remove unneeded functions, comments and logs. --- channel/adjudicator.go | 35 ++++++----------------------------- channel/event.go | 10 ---------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 5f15d9d..eb8f808 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -81,7 +81,10 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, log.Println("Withdraw called") err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs) + fmt.Println("Close called: err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs)") + if err != nil { + fmt.Println("chanControl, err := a.GetChannelState(ctx, req.Tx.State)") chanControl, err := a.GetChannelState(ctx, req.Tx.State) if err != nil { @@ -89,6 +92,7 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, } if chanControl.Control.Closed { + fmt.Println("chanControl.Control.Closed in withdraw") return a.withdraw(ctx, req) } @@ -96,6 +100,8 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, if err != nil { return err } + fmt.Println("before a.withdraw(ctx, req)") + return a.withdraw(ctx, req) } else { @@ -117,34 +123,6 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, return nil } -func (a *Adjudicator) waitForClosed(ctx context.Context, evsub *AdjEventSub, cid pchannel.ID) error { - a.log.Log().Tracef("Waiting for the channel closing event") - -loop: - for { - - select { - case event := <-evsub.Events(): - _, ok := event.(*CloseEvent) - - if !ok { - continue loop - } - - evsub.Close() - return nil - - case <-ctx.Done(): - return ctx.Err() - case err := <-evsub.PanicErr(): - return err - default: - continue loop - } - - } -} - func (a *Adjudicator) GetChannelState(ctx context.Context, state *pchannel.State) (wire.Channel, error) { contractAddress := a.GetPerunID() @@ -258,7 +236,6 @@ func (a *Adjudicator) Register(ctx context.Context, req pchannel.AdjudicatorReq, func (a *Adjudicator) Dispute(ctx context.Context, state *pchannel.State, sigs []pwallet.Sig) error { contractAddress := a.GetPerunID() kp := a.kpFull - // hzAcc := a.stellarClient.GetHorizonAcc() closeTxArgs, err := BuildDisputeTxArgs(*state, sigs) if err != nil { return errors.New("error while building fund tx") diff --git a/channel/event.go b/channel/event.go index 877b66d..20f4530 100644 --- a/channel/event.go +++ b/channel/event.go @@ -18,7 +18,6 @@ import ( "errors" "fmt" "github.com/stellar/go/xdr" - "log" pchannel "perun.network/go-perun/channel" "perun.network/perun-stellar-backend/wire" "time" @@ -61,8 +60,6 @@ var ( type controlsState map[string]bool type ( - - // PerunEvent is a Perun event. PerunEvent interface { ID() pchannel.ID Timeout() pchannel.Timeout @@ -261,7 +258,6 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { } evs = append(evs, &openEvent) - log.Println("OpenEvent: ", openEvent) case EventTypeFundChannel: fundEventchanStellar, _, err := GetChannelBoolFromEvents(ev.Body.V0.Data) @@ -273,7 +269,6 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { Channel: fundEventchanStellar, } evs = append(evs, &fundEvent) - log.Println("FundEvent: ", fundEvent) case EventTypeClosed: closedEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) if err != nil { @@ -284,7 +279,6 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { Channel: closedEventchanStellar, } evs = append(evs, &closeEvent) - log.Println("CloseEvent: ", closeEvent) case EventTypeWithdrawn: withdrawnEventchanStellar, err := GetChannelFromEvents(ev.Body.V0.Data) if err != nil { @@ -367,7 +361,6 @@ func (e *CloseEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint copy(cid[:], chanID[:]) e.IDV = cid - //e.VersionV = version e.Timestamp = timestamp e.Channel = chanState return nil @@ -380,7 +373,6 @@ func (e *FundEvent) EventDataFromChannel(chanState wire.Channel, timestamp uint6 copy(cid[:], chanID[:]) e.IDV = cid - //e.VersionV = version e.Timestamp = timestamp e.Channel = chanState return nil @@ -393,7 +385,6 @@ func (e *WithdrawnEvent) EventDataFromChannel(chanState wire.Channel, timestamp copy(cid[:], chanID[:]) e.IDV = cid - //e.VersionV = version e.Timestamp = timestamp e.Channel = chanState return nil @@ -406,7 +397,6 @@ func (e *DisputedEvent) EventDataFromChannel(chanState wire.Channel, timestamp u copy(cid[:], chanID[:]) e.IDV = cid - //e.VersionV = version e.Timestamp = timestamp e.Channel = chanState return nil From 0c7ae1e13a12dcb130b6f00042f1a6cc9de7d68e Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Mon, 22 Jan 2024 19:01:01 +0100 Subject: [PATCH 35/51] style(wire, channel/env): Remove comments --- channel/env/client.go | 1 - wire/balances.go | 1 - 2 files changed, 2 deletions(-) diff --git a/channel/env/client.go b/channel/env/client.go index 46cbe6b..b007aa7 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -31,7 +31,6 @@ type HorizonMasterClient struct { type StellarClient struct { hzClient *horizonclient.Client kp *keypair.Full - // clientKey *keypair.Full } func NewHorizonClient() *horizonclient.Client { diff --git a/wire/balances.go b/wire/balances.go index 4ae6c70..ef70d86 100644 --- a/wire/balances.go +++ b/wire/balances.go @@ -145,7 +145,6 @@ func (b *Balances) UnmarshalBinary(data []byte) error { } func MakeBalances(alloc channel.Allocation) (Balances, error) { - // TODO: Move all these checks into a compatibility layer if err := alloc.Valid(); err != nil { return Balances{}, err } From 2d738cefb65de488981f225acac7d089b5ba042e Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 13 Feb 2024 10:23:29 +0100 Subject: [PATCH 36/51] style(channel): Remove unnecessary print statements and logs --- channel/adjudicator.go | 4 ---- channel/deploy.go | 3 --- 2 files changed, 7 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index eb8f808..60dbd18 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -81,10 +81,8 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, log.Println("Withdraw called") err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs) - fmt.Println("Close called: err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs)") if err != nil { - fmt.Println("chanControl, err := a.GetChannelState(ctx, req.Tx.State)") chanControl, err := a.GetChannelState(ctx, req.Tx.State) if err != nil { @@ -92,7 +90,6 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, } if chanControl.Control.Closed { - fmt.Println("chanControl.Control.Closed in withdraw") return a.withdraw(ctx, req) } @@ -100,7 +97,6 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, if err != nil { return err } - fmt.Println("before a.withdraw(ctx, req)") return a.withdraw(ctx, req) diff --git a/channel/deploy.go b/channel/deploy.go index 6ab8481..cfba7aa 100644 --- a/channel/deploy.go +++ b/channel/deploy.go @@ -16,10 +16,8 @@ package channel import ( "crypto/sha256" - "encoding/hex" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" - "log" "os" ) @@ -53,7 +51,6 @@ func AssembleCreateContractOp(sourceAccount string, wasmFileName string, contrac } salt := sha256.Sum256([]byte(contractSalt)) - log.Printf("Salt hash: %v", hex.EncodeToString(salt[:])) saltParameter := xdr.Uint256(salt) accountId := xdr.MustAddress(sourceAccount) From 8461c0ac3b243a42e4ae5ae29517246e8272a774 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 13 Feb 2024 10:49:30 +0100 Subject: [PATCH 37/51] refactor(wire): Remove unused invocation.go file --- wire/invocation.go | 50 ---------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 wire/invocation.go diff --git a/wire/invocation.go b/wire/invocation.go deleted file mode 100644 index 521a63b..0000000 --- a/wire/invocation.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2023 PolyCrypt GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package wire - -import ( - "github.com/stellar/go/xdr" - "perun.network/perun-stellar-backend/wire/scval" -) - -func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName string, tokenSymbol string) (xdr.ScVec, error) { - - adminScAddr, err := scval.WrapScAddress(adminAddr) - if err != nil { - panic(err) - } - - decim := xdr.Uint32(decimals) - scvaltype := xdr.ScValTypeScvU32 - decimSc, err := xdr.NewScVal(scvaltype, decim) - if err != nil { - panic(err) - } - - tokenNameScString := xdr.ScString(tokenName) - tokenNameXdr := scval.MustWrapScString(tokenNameScString) - - tokenSymbolString := xdr.ScString(tokenSymbol) - tokenSymbolXdr := scval.MustWrapScString(tokenSymbolString) - - initTokenArgs := xdr.ScVec{ - adminScAddr, - decimSc, - tokenNameXdr, - tokenSymbolXdr, - } - - return initTokenArgs, nil -} From 74e3e6f0b6d1155332b5a2f713aada1c956681d7 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 13 Feb 2024 11:57:16 +0100 Subject: [PATCH 38/51] fix(wire): Import Randomizer object from channel/test instead of wallet/test to avoid code duplication or double initialization --- wire/params_test.go | 2 +- wire/state_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/wire/params_test.go b/wire/params_test.go index a8b6829..bb2987d 100644 --- a/wire/params_test.go +++ b/wire/params_test.go @@ -21,7 +21,7 @@ import ( ptest "perun.network/go-perun/channel/test" schannel "perun.network/perun-stellar-backend/channel" - _ "perun.network/perun-stellar-backend/wallet/test" + _ "perun.network/perun-stellar-backend/channel/test" "perun.network/perun-stellar-backend/wire" pkgtest "polycry.pt/poly-go/test" diff --git a/wire/state_test.go b/wire/state_test.go index b892492..633676d 100644 --- a/wire/state_test.go +++ b/wire/state_test.go @@ -20,6 +20,7 @@ import ( "math/big" "perun.network/go-perun/channel" ptest "perun.network/go-perun/channel/test" + _ "perun.network/perun-stellar-backend/channel/test" "perun.network/perun-stellar-backend/wire" polytest "polycry.pt/poly-go/test" "testing" From 50ca47c45dca912b4c0974cec6bafccf38530735 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 13 Feb 2024 16:17:16 +0100 Subject: [PATCH 39/51] feat(README, channel): Add separation between unit tests and integration tests, allowing to choose to run unit tests only. Update README accordingly. --- README.md | 20 ++++++++++++++------ channel/adjudicator_test.go | 3 +++ channel/funder_test.go | 3 +++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c658950..6632287 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,34 @@ git clone https://github.com/perun-network/perun-stellar-backend cd perun-stellar-backend ``` +2. Running the payment channel unit tests: -2. To run a local Stellar blockchain with Soroban smart contract support, you initialize the docker images defined in the ```quickstart.sh``` script. Docker needs to be installed to perform this step: +To run the unit tests only, you can use the following command: ```sh -./quickstart.sh standalone +go test ./... ``` -Note that this backend is customized to run on a local Stellar blockchain (standalone), but can be easily adapted to run on a public testnet. +3. Running the payment channel tests, including the integration tests: +The integration tests require running a local Stellar blockchain, a Horizon client and a Soroban RPC server. The binaries are packages in a docker image, which are initialized using the ```quickstart.sh``` script. Docker needs to be installed to perform this step: -3. Running the payment channel tests: +```sh +./quickstart.sh standalone -To make sure that your setup is correct, you can run the payment channel tests after cloning this repository: +``` + +The initialization takes a few seconds. Afterwards, you can run the tests using the following command: ```sh -go test ./... +go test -tags=integration ./... ``` +Note that this backend is customized to run on a local Stellar blockchain (standalone), but can be easily adapted to run on a public blockchain. + + # Payment Channel Demo A demonstrator of Perun payment channels on the Stellar blockchain can be found [here](https://github.com/perun-network/perun-stellar-demo). diff --git a/channel/adjudicator_test.go b/channel/adjudicator_test.go index 6dc08b7..93d2a20 100644 --- a/channel/adjudicator_test.go +++ b/channel/adjudicator_test.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build integration +// +build integration + package channel_test import ( diff --git a/channel/funder_test.go b/channel/funder_test.go index ad8a321..ef2c031 100644 --- a/channel/funder_test.go +++ b/channel/funder_test.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build integration +// +build integration + package channel_test import ( From 52e28defad88b9f1b2dae730b9fc09b079c367e2 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Tue, 13 Feb 2024 19:19:28 +0100 Subject: [PATCH 40/51] fix: Provide Dockerfile and build.sh script to download and build the Stellar binaries docker image. Update README to document docker usage. --- Dockerfile | 18 ++++++++++++++++++ README.md | 10 +++++++++- testdata/docker/build.sh | 7 +++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100755 Dockerfile create mode 100755 testdata/docker/build.sh diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..cadb8e4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# Based on Preview 7 +# https://soroban.stellar.org/docs/reference/releases + +FROM ubuntu:20.04 + +RUN apt update && apt install -y curl + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rust_install.sh +RUN sh rust_install.sh -y +RUN echo $PATH +ENV PATH="$PATH:/root/.cargo/bin" +RUN rustup target add wasm32-unknown-unknown + +RUN apt install -y build-essential +# WORKDIR / +RUN mkdir /workspace +WORKDIR /workspace +ENV IS_USING_DOCKER=true diff --git a/README.md b/README.md index 6632287..1d5a848 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,15 @@ go test ./... 3. Running the payment channel tests, including the integration tests: -The integration tests require running a local Stellar blockchain, a Horizon client and a Soroban RPC server. The binaries are packages in a docker image, which are initialized using the ```quickstart.sh``` script. Docker needs to be installed to perform this step: +The integration tests require running a local Stellar blockchain, a Horizon client and a Soroban RPC server. The binaries are packaged in a docker image. + +To install the docker image, you need to download and build it first, using the build.sh script in testdata/docker: + +```sh +./testdata/docker/build.sh +``` + +Now you can run the docker image with ```sh ./quickstart.sh standalone diff --git a/testdata/docker/build.sh b/testdata/docker/build.sh new file mode 100755 index 0000000..8f9f02d --- /dev/null +++ b/testdata/docker/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Build image and tag it with image name and version +docker build . \ + --tag soroban-preview:10 \ + --force-rm \ + --rm From 6dc4eabcec877307aa5f5430960a5103791c41bc Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Wed, 14 Feb 2024 12:04:04 +0100 Subject: [PATCH 41/51] chore(channel/subscribe.go): Rename Events() method to getEvents() to unexpose --- channel/subscribe.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/channel/subscribe.go b/channel/subscribe.go index bec2e7b..ffd5ec9 100644 --- a/channel/subscribe.go +++ b/channel/subscribe.go @@ -25,12 +25,12 @@ func (s *AdjEventSub) Next() pchannel.AdjudicatorEvent { return nil } - if s.Events() == nil { + if s.getEvents() == nil { return nil } select { - case event := <-s.Events(): + case event := <-s.getEvents(): if event == nil { return nil } @@ -76,7 +76,7 @@ func (s *AdjEventSub) Close() error { return nil } -func (s *AdjEventSub) Events() <-chan AdjEvent { +func (s *AdjEventSub) getEvents() <-chan AdjEvent { return s.events } From 57ccb5f8fb85a88f2fd19cfb1e755049a6f5d572 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Wed, 14 Feb 2024 13:26:34 +0100 Subject: [PATCH 42/51] chore(AdjEventSub): Remove unused Ev field --- channel/adjudicator_sub.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index 4794934..e296dd3 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -49,7 +49,6 @@ type AdjEventSub struct { perunID xdr.ScAddress assetID xdr.ScAddress events chan AdjEvent - Ev []AdjEvent err error panicErr chan error cancel context.CancelFunc @@ -72,7 +71,6 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. perunID: perunID, assetID: assetID, events: make(chan AdjEvent, DefaultBufferSize), - Ev: make([]AdjEvent, 0), panicErr: make(chan error, 1), pollInterval: DefaultSubscriptionPollingInterval, closer: new(pkgsync.Closer), From fae93c2386a935d146b515bc950e4ac32aaa3d96 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Wed, 14 Feb 2024 14:07:28 +0100 Subject: [PATCH 43/51] * chore(channel): Return errors instead of panic where possible * chorse(channel/env): Return errors instead of panic where possible --- channel/adjudicator.go | 4 ++-- channel/adjudicator_sub.go | 6 +++--- channel/env/client.go | 12 ++++++------ channel/env/transaction.go | 27 +++++++++++++++------------ channel/event.go | 4 ++-- channel/funder.go | 7 +++++-- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 60dbd18..8c728d9 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -72,7 +72,7 @@ func (a *Adjudicator) Subscribe(ctx context.Context, cid pchannel.ID) (pchannel. c := a.stellarClient perunID := a.GetPerunID() assetID := a.GetAssetID() - return NewAdjudicatorSub(ctx, cid, c, perunID, assetID), nil + return NewAdjudicatorSub(ctx, cid, c, perunID, assetID) } func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, smap pchannel.StateMap) error { @@ -156,7 +156,7 @@ func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVe } else if partyIdx == 1 { withdrawIdx = scval.MustWrapBool(true) } else { - panic("partyIdx must be 0 or 1") + return xdr.ScVec{}, errors.New("invalid party index") } var chanid xdr.ScBytes copy(chanid, chanIDStellar) diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index e296dd3..aab6cfb 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -57,10 +57,10 @@ type AdjEventSub struct { log log.Embedding } -func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *AdjEventSub { +func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) (*AdjEventSub, error) { getChanArgs, err := env.BuildGetChannelTxArgs(cid) if err != nil { - panic(err) + return nil, err } sub := &AdjEventSub{ @@ -79,7 +79,7 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. ctx, sub.cancel = context.WithCancel(ctx) go sub.run(ctx) - return sub + return sub, nil } diff --git a/channel/env/client.go b/channel/env/client.go index b007aa7..2c8d6fb 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -68,20 +68,20 @@ func (c *StellarClient) GetHorizonClient() *horizonclient.Client { return c.hzClient } -func (h *HorizonMasterClient) GetAccount(kp *keypair.Full) horizon.Account { +func (h *HorizonMasterClient) GetAccount(kp *keypair.Full) (horizon.Account, error) { accountReq := horizonclient.AccountRequest{AccountID: kp.Address()} hzAccount, err := h.master.AccountDetail(accountReq) if err != nil { - panic(err) + return hzAccount, err } - return hzAccount + return hzAccount, nil } -func (s *StellarClient) GetHorizonAccount(kp *keypair.Full) horizon.Account { +func (s *StellarClient) GetHorizonAccount(kp *keypair.Full) (horizon.Account, error) { accountReq := horizonclient.AccountRequest{AccountID: kp.Address()} hzAccount, err := s.hzClient.AccountDetail(accountReq) if err != nil { - panic(err) + return hzAccount, err } - return hzAccount + return hzAccount, nil } diff --git a/channel/env/transaction.go b/channel/env/transaction.go index e537fec..05b7641 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -159,35 +159,35 @@ func CreateSignedTransactionWithParams(signers []*keypair.Full, txParams txnbuil return tx, nil } -func BuildOpenTxArgs(params *pchannel.Params, state *pchannel.State) xdr.ScVec { +func BuildOpenTxArgs(params *pchannel.Params, state *pchannel.State) (xdr.ScVec, error) { paramsStellar, err := wire.MakeParams(*params) if err != nil { - panic(err) + return xdr.ScVec{}, err } stateStellar, err := wire.MakeState(*state) if err != nil { - panic(err) + return xdr.ScVec{}, err } paramsXdr, err := paramsStellar.ToScVal() if err != nil { - panic(err) + return xdr.ScVec{}, err } stateXdr, err := stateStellar.ToScVal() if err != nil { - panic(err) + return xdr.ScVec{}, err } openArgs := xdr.ScVec{ paramsXdr, stateXdr, } - return openArgs + return openArgs, nil } func BuildMintTokenArgs(mintTo xdr.ScAddress, amount xdr.ScVal) (xdr.ScVec, error) { mintToSc, err := scval.WrapScAddress(mintTo) if err != nil { - panic(err) + return xdr.ScVec{}, err } MintTokenArgs := xdr.ScVec{ @@ -202,7 +202,7 @@ func BuildGetTokenBalanceArgs(balanceOf xdr.ScAddress) (xdr.ScVec, error) { balanceOfSc, err := scval.WrapScAddress(balanceOf) if err != nil { - panic(err) + return xdr.ScVec{}, err } GetTokenBalanceArgs := xdr.ScVec{ @@ -241,7 +241,7 @@ func BuildGetChannelTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { copy(chanid[:], chanIDStellar) channelID, err := scval.WrapScBytes(chanIDStellar) if err != nil { - panic(err) + return xdr.ScVec{}, err } getChannelArgs := xdr.ScVec{ @@ -270,7 +270,10 @@ func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xd sharedMtx.Lock() defer sharedMtx.Unlock() fnameXdr := xdr.ScSymbol(fname) - hzAcc := s.GetHorizonAccount(kp) + hzAcc, err := s.GetHorizonAccount(kp) + if err != nil { + return xdr.TransactionMeta{}, err + } hzClient := s.GetHorizonClient() invokeHostFunctionOp := BuildContractCallOp(hzAcc, fnameXdr, callTxArgs, contractAddr) @@ -279,11 +282,11 @@ func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xd txParams := GetBaseTransactionParamsWithFee(&hzAcc, minFee, &preFlightOp) txSigned, err := CreateSignedTransactionWithParams([]*keypair.Full{kp}, txParams) if err != nil { - panic(err) + return xdr.TransactionMeta{}, err } tx, err := hzClient.SubmitTransaction(txSigned) if err != nil { - panic(err) + return xdr.TransactionMeta{}, err } // Decode transaction metadata txMeta, err := DecodeTxMeta(tx) diff --git a/channel/event.go b/channel/event.go index 20f4530..d91bc32 100644 --- a/channel/event.go +++ b/channel/event.go @@ -212,7 +212,7 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { topics := ev.Body.V0.Topics if len(topics) < 2 { - panic(ErrNotStellarPerunContract) + return []PerunEvent{}, ErrNotStellarPerunContract } perunString, ok := topics[0].GetSym() @@ -221,7 +221,7 @@ func DecodeEventsPerun(txMeta xdr.TransactionMeta) ([]PerunEvent, error) { } if perunString != AssertPerunSymbol { - panic(ErrNotStellarPerunContract) + return []PerunEvent{}, ErrNotStellarPerunContract } if !ok { return []PerunEvent{}, ErrNotStellarPerunContract diff --git a/channel/funder.go b/channel/funder.go index 848843f..e84236b 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -131,7 +131,10 @@ func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state perunAddress := f.GetPerunID() kp := f.kpFull - openTxArgs := env.BuildOpenTxArgs(params, state) + openTxArgs, err := env.BuildOpenTxArgs(params, state) + if err != nil { + return errors.New("error while building open tx") + } txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("open", openTxArgs, perunAddress, kp) if err != nil { return errors.New("error while invoking and processing host function: open") @@ -166,7 +169,7 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state sameContractTokenID := tokenIDAddrFromBals.Equals(tokenAddress) if !sameContractTokenID { - panic("tokenIDAddrFromBals not equal to tokenContractAddress") + return errors.New("tokenIDAddrFromBals not equal to tokenContractAddress") } txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("fund", fundTxArgs, perunAddress, kp) From 5b1ea78c107a4f6ae9f79566a32a651aaf4b690e Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Wed, 14 Feb 2024 16:07:03 +0100 Subject: [PATCH 44/51] refactor(adjudicator): Streamline error handling for Withdraw function --- channel/adjudicator.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 8c728d9..c9b680f 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -80,21 +80,15 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, if req.Tx.State.IsFinal { log.Println("Withdraw called") - err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs) - - if err != nil { - - chanControl, err := a.GetChannelState(ctx, req.Tx.State) - if err != nil { - return err + if err := a.Close(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs); err != nil { + chanControl, errChanState := a.GetChannelState(ctx, req.Tx.State) + if errChanState != nil { + return errChanState } if chanControl.Control.Closed { return a.withdraw(ctx, req) } - - } - if err != nil { return err } @@ -103,12 +97,11 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, } else { err := a.ForceClose(ctx, req.Tx.ID, req.Tx.State, req.Tx.Sigs, req.Params) log.Println("ForceClose called") + if errors.Is(err, ErrChannelAlreadyClosed) { + return a.withdraw(ctx, req) + } if err != nil { - if err == ErrChannelAlreadyClosed { - return a.withdraw(ctx, req) - } else { - return err - } + return err } err = a.withdraw(ctx, req) From eb1425948a958cbf82024760b4e2b112ed374a06 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Wed, 14 Feb 2024 16:45:13 +0100 Subject: [PATCH 45/51] refactor(channel): Rename panicErr to subErrors, remove PanicErr() method and add conditions to close AdjEventSub subscription --- channel/adjudicator_sub.go | 17 ++++++++--------- channel/subscribe.go | 4 ---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index aab6cfb..b954863 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -49,8 +49,8 @@ type AdjEventSub struct { perunID xdr.ScAddress assetID xdr.ScAddress events chan AdjEvent + subErrors chan error err error - panicErr chan error cancel context.CancelFunc closer *pkgsync.Closer pollInterval time.Duration @@ -71,7 +71,7 @@ func NewAdjudicatorSub(ctx context.Context, cid pchannel.ID, stellarClient *env. perunID: perunID, assetID: assetID, events: make(chan AdjEvent, DefaultBufferSize), - panicErr: make(chan error, 1), + subErrors: make(chan error, 1), pollInterval: DefaultSubscriptionPollingInterval, closer: new(pkgsync.Closer), log: log.MakeEmbedding(log.Default()), @@ -87,7 +87,7 @@ func (s *AdjEventSub) run(ctx context.Context) { s.log.Log().Info("Listening for channel state changes") chanControl, err := s.getChanControl() if err != nil { - s.panicErr <- err + s.subErrors <- err } s.chanControl = chanControl finish := func(err error) { @@ -98,6 +98,9 @@ polling: for { s.log.Log().Debug("AdjudicatorSub is listening for Adjudicator Events") select { + case err := <-s.subErrors: + finish(err) + return case <-ctx.Done(): finish(nil) return @@ -107,12 +110,11 @@ polling: if err != nil { - s.panicErr <- err + s.subErrors <- err } - // decode channel state difference to events adjEvent, err := DifferencesInControls(s.chanControl, newChanControl) if err != nil { - s.panicErr <- err + s.subErrors <- err } if adjEvent == nil { @@ -122,9 +124,6 @@ polling: } else { s.log.Log().Debug("Event detected, evaluating events...") - - // Store the event - s.log.Log().Debugf("Found event: %v", adjEvent) s.events <- adjEvent return diff --git a/channel/subscribe.go b/channel/subscribe.go index ffd5ec9..44e44d0 100644 --- a/channel/subscribe.go +++ b/channel/subscribe.go @@ -83,7 +83,3 @@ func (s *AdjEventSub) getEvents() <-chan AdjEvent { func (s *AdjEventSub) Err() error { return s.err } - -func (s *AdjEventSub) PanicErr() <-chan error { - return s.panicErr -} From 9c03ce499e52009d9915257d14920244c046a587 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Thu, 15 Feb 2024 18:46:55 +0100 Subject: [PATCH 46/51] fix(funder): Simplify the Fund method, using fundParty instead of fundPartyA and fundPartyB. Include safety check to assert that PartyA and PartyB have funded before Fund returns successfully. --- channel/funder.go | 86 ++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/channel/funder.go b/channel/funder.go index e84236b..4ccf9cb 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -62,68 +62,69 @@ func (f *Funder) GetAssetID() xdr.ScAddress { func (f *Funder) Fund(ctx context.Context, req pchannel.FundingReq) error { log.Println("Fund called") - switch req.Idx { - case 0: - return f.fundPartyA(ctx, req) - case 1: - return f.fundPartyB(ctx, req) - default: - return errors.New("invalid index") + + if req.Idx != 0 && req.Idx != 1 { + return errors.New("req.Idx must be 0 or 1") } -} -func (f *Funder) fundPartyA(ctx context.Context, req pchannel.FundingReq) error { + if req.Idx == pchannel.Index(0) { + err := f.openChannel(ctx, req) + if err != nil { + return err + } + } - err := f.OpenChannel(ctx, req.Params, req.State) - if err != nil { + return f.fundParty(ctx, req) +} +func (f *Funder) fundParty(ctx context.Context, req pchannel.FundingReq) error { - return errors.New("error while opening channel in party A") - } - err = f.FundChannel(ctx, req.Params, req.State, false) - if err != nil { - return err - } + party := getPartyByIndex(req.Idx) + + log.Printf("%s: Funding channel...\n", party) -polling: for i := 0; i < f.maxIters; i++ { select { case <-ctx.Done(): return f.AbortChannel(ctx, req.Params, req.State) case <-time.After(f.pollingInterval): + + log.Printf("%s: Polling for opened channel...\n", party) chanState, err := f.GetChannelState(ctx, req.Params, req.State) if err != nil { - continue polling + log.Printf("%s: Error while polling for opened channel: %v\n", party, err) + continue } + + log.Printf("%s: Found opened channel!\n", party) if chanState.Control.FundedA && chanState.Control.FundedB { return nil } + if req.Idx == pchannel.Index(0) && !chanState.Control.FundedA { + err := f.FundChannel(ctx, req.Params, req.State, false) + if err != nil { + return err + } + continue + } + if req.Idx == pchannel.Index(1) && !chanState.Control.FundedB { + err := f.FundChannel(ctx, req.Params, req.State, true) + if err != nil { + return err + } + continue + } } } return f.AbortChannel(ctx, req.Params, req.State) } -func (f *Funder) fundPartyB(ctx context.Context, req pchannel.FundingReq) error { - -polling: - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(f.pollingInterval): - log.Println("Party B: Polling for opened channel...") - chanState, err := f.GetChannelState(ctx, req.Params, req.State) - if err != nil { - log.Println("Party B: Error while polling for opened channel:", err) - continue polling - } - log.Println("Party B: Found opened channel!") - if chanState.Control.FundedA && chanState.Control.FundedB { - return nil - } - return f.FundChannel(ctx, req.Params, req.State, true) - } +func (f *Funder) openChannel(ctx context.Context, req pchannel.FundingReq) error { + err := f.OpenChannel(ctx, req.Params, req.State) + if err != nil { + return errors.New("error while opening channel in party A") } + return nil } func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { @@ -232,3 +233,10 @@ func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, s } return getChan, nil } + +func getPartyByIndex(funderIdx pchannel.Index) string { + if funderIdx == 1 { + return "Party B" + } + return "Party A" +} From edb6ec705c01ca2b7418558c5221ad4ab73a981f Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 16 Feb 2024 08:09:21 +0100 Subject: [PATCH 47/51] refactor(channel/env): Remove HorizonMasterClient, as this type was identical to StellarClient --- channel/env/client.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/channel/env/client.go b/channel/env/client.go index 2c8d6fb..6cccedb 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -23,11 +23,6 @@ import ( const HorizonURL = "http://localhost:8000" const NETWORK_PASSPHRASE = "Standalone Network ; February 2017" -type HorizonMasterClient struct { - master *horizonclient.Client - sourceKey *keypair.Full -} - type StellarClient struct { hzClient *horizonclient.Client kp *keypair.Full @@ -48,29 +43,29 @@ func (s *StellarClient) GetKeyPair() *keypair.Full { return s.kp } -func NewHorizonMasterClient() *HorizonMasterClient { +func NewHorizonMasterClient() *StellarClient { sourceKey := keypair.Root(NETWORK_PASSPHRASE) - return &HorizonMasterClient{ - master: &horizonclient.Client{HorizonURL: HorizonURL}, - sourceKey: sourceKey, + return &StellarClient{ + hzClient: &horizonclient.Client{HorizonURL: HorizonURL}, + kp: sourceKey, } } -func (m *HorizonMasterClient) GetMaster() *horizonclient.Client { - return m.master +func (m *StellarClient) GetMaster() *horizonclient.Client { + return m.hzClient } -func (m *HorizonMasterClient) GetSourceKey() *keypair.Full { - return m.sourceKey +func (m *StellarClient) GetSourceKey() *keypair.Full { + return m.kp } func (c *StellarClient) GetHorizonClient() *horizonclient.Client { return c.hzClient } -func (h *HorizonMasterClient) GetAccount(kp *keypair.Full) (horizon.Account, error) { +func (h *StellarClient) GetAccount(kp *keypair.Full) (horizon.Account, error) { accountReq := horizonclient.AccountRequest{AccountID: kp.Address()} - hzAccount, err := h.master.AccountDetail(accountReq) + hzAccount, err := h.hzClient.AccountDetail(accountReq) if err != nil { return hzAccount, err } From 279af0e89b86545d82a260cd6c99f071038e24cc Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 16 Feb 2024 08:19:19 +0100 Subject: [PATCH 48/51] refactor(channel/env, channel/client): Remove GetMaster() and GetSourceKey() methods, as they were identical to GetHorizonClient() and GetKeyPair() --- channel/env/client.go | 12 ++++++------ channel/test/setup.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/channel/env/client.go b/channel/env/client.go index 6cccedb..5eb7c46 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -51,13 +51,13 @@ func NewHorizonMasterClient() *StellarClient { } } -func (m *StellarClient) GetMaster() *horizonclient.Client { - return m.hzClient -} +// func (m *StellarClient) GetMaster() *horizonclient.Client { +// return m.hzClient +// } -func (m *StellarClient) GetSourceKey() *keypair.Full { - return m.kp -} +// func (m *StellarClient) GetSourceKey() *keypair.Full { +// return m.kp +// } func (c *StellarClient) GetHorizonClient() *horizonclient.Client { return c.hzClient diff --git a/channel/test/setup.go b/channel/test/setup.go index 8a19517..091d142 100644 --- a/channel/test/setup.go +++ b/channel/test/setup.go @@ -178,8 +178,8 @@ func CreateFundStellarAccounts(pairs []*keypair.Full, initialBalance string) err numKps := len(pairs) masterClient := env.NewHorizonMasterClient() - masterHzClient := masterClient.GetMaster() - sourceKey := masterClient.GetSourceKey() + masterHzClient := masterClient.GetHorizonClient() + sourceKey := masterClient.GetKeyPair() hzClient := env.NewHorizonClient() From 2b115f9fa545442a9a7025866c7d7698d1a8bce0 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 16 Feb 2024 09:30:34 +0100 Subject: [PATCH 49/51] refactor(channel, channel/env, channel/test): Remove necessity for keypair exposure in Funder, Adjudicator and the associated methods. Keypair is now used in channel/env by Stellarclient.keyHolder to sign transactions. --- channel/adjudicator.go | 23 +++++---------- channel/adjudicator_sub.go | 5 +--- channel/env/client.go | 60 ++++++++++++++++++++++++-------------- channel/env/transaction.go | 6 ++-- channel/funder.go | 18 ++++-------- channel/test/setup.go | 14 ++++----- channel/test/token.go | 6 ++-- 7 files changed, 64 insertions(+), 68 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index c9b680f..90d6e75 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" - "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" "perun.network/go-perun/log" @@ -38,7 +37,6 @@ type Adjudicator struct { log log.Embedding stellarClient *env.StellarClient acc *wallet.Account - kpFull *keypair.Full assetID xdr.ScAddress perunID xdr.ScAddress maxIters int @@ -47,11 +45,10 @@ type Adjudicator struct { // NewAdjudicator returns a new Adjudicator. -func NewAdjudicator(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *Adjudicator { +func NewAdjudicator(acc *wallet.Account, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *Adjudicator { return &Adjudicator{ stellarClient: stellarClient, acc: acc, - kpFull: kp, perunID: perunID, assetID: assetID, maxIters: MaxIterationsUntilAbort, @@ -115,15 +112,13 @@ func (a *Adjudicator) Withdraw(ctx context.Context, req pchannel.AdjudicatorReq, func (a *Adjudicator) GetChannelState(ctx context.Context, state *pchannel.State) (wire.Channel, error) { contractAddress := a.GetPerunID() - kp := a.kpFull chanId := state.ID - // generate tx to open the channel getchTxArgs, err := env.BuildGetChannelTxArgs(chanId) if err != nil { return wire.Channel{}, errors.New("error while building get_channel tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } @@ -169,13 +164,11 @@ func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVe func (a *Adjudicator) withdraw(ctx context.Context, req pchannel.AdjudicatorReq) error { perunAddress := a.GetPerunID() - kp := a.kpFull - withdrawTxArgs, err := a.BuildWithdrawTxArgs(req) if err != nil { return errors.New("error while building fund tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("withdraw", withdrawTxArgs, perunAddress, kp) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("withdraw", withdrawTxArgs, perunAddress) if err != nil { return errors.New("error while invoking and processing host function: withdraw") } @@ -192,12 +185,11 @@ func (a *Adjudicator) Close(ctx context.Context, id pchannel.ID, state *pchannel log.Println("Close called") contractAddress := a.GetPerunID() - kp := a.kpFull closeTxArgs, err := BuildCloseTxArgs(*state, sigs) if err != nil { return errors.New("error while building fund tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("close", closeTxArgs, contractAddress, kp) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("close", closeTxArgs, contractAddress) if err != nil { return errors.New("error while invoking and processing host function: close") } @@ -224,12 +216,11 @@ func (a *Adjudicator) Register(ctx context.Context, req pchannel.AdjudicatorReq, func (a *Adjudicator) Dispute(ctx context.Context, state *pchannel.State, sigs []pwallet.Sig) error { contractAddress := a.GetPerunID() - kp := a.kpFull closeTxArgs, err := BuildDisputeTxArgs(*state, sigs) if err != nil { return errors.New("error while building fund tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("dispute", closeTxArgs, contractAddress, kp) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("dispute", closeTxArgs, contractAddress) if err != nil { return errors.New("error while invoking and processing host function: dispute") } @@ -243,12 +234,12 @@ func (a *Adjudicator) Dispute(ctx context.Context, state *pchannel.State, sigs [ func (a *Adjudicator) ForceClose(ctx context.Context, id pchannel.ID, state *pchannel.State, sigs []pwallet.Sig, params *pchannel.Params) error { log.Println("ForceClose called") contractAddress := a.GetPerunID() - kp := a.kpFull + // kp := a.kpFull forceCloseTxArgs, err := env.BuildForceCloseTxArgs(id) if err != nil { return errors.New("error while building fund tx") } - txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("force_close", forceCloseTxArgs, contractAddress, kp) + txMeta, err := a.stellarClient.InvokeAndProcessHostFunction("force_close", forceCloseTxArgs, contractAddress) if err != nil { return errors.New("error while invoking and processing host function") } diff --git a/channel/adjudicator_sub.go b/channel/adjudicator_sub.go index b954863..42f739c 100644 --- a/channel/adjudicator_sub.go +++ b/channel/adjudicator_sub.go @@ -134,8 +134,7 @@ polling: func (s *AdjEventSub) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) { contractAddress := s.perunID - kp := s.stellarClient.GetKeyPair() - txMeta, err := s.stellarClient.InvokeAndProcessHostFunction("get_channel", chanArgs, contractAddress, kp) + txMeta, err := s.stellarClient.InvokeAndProcessHostFunction("get_channel", chanArgs, contractAddress) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } @@ -152,8 +151,6 @@ func (s *AdjEventSub) GetChannelState(chanArgs xdr.ScVec) (wire.Channel, error) } func (s *AdjEventSub) getChanControl() (wire.Control, error) { - // query channel state - getChanArgs, err := env.BuildGetChannelTxArgs(s.cid) if err != nil { return wire.Control{}, err diff --git a/channel/env/client.go b/channel/env/client.go index 5eb7c46..d24a11c 100644 --- a/channel/env/client.go +++ b/channel/env/client.go @@ -18,53 +18,50 @@ import ( "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/txnbuild" ) const HorizonURL = "http://localhost:8000" const NETWORK_PASSPHRASE = "Standalone Network ; February 2017" type StellarClient struct { - hzClient *horizonclient.Client - kp *keypair.Full + hzClient *horizonclient.Client + keyHolder keyHolder +} + +type keyHolder struct { + kp *keypair.Full } func NewHorizonClient() *horizonclient.Client { return &horizonclient.Client{HorizonURL: HorizonURL} } +func newKeyHolder(kp *keypair.Full) keyHolder { + return keyHolder{kp} +} + func NewStellarClient(kp *keypair.Full) *StellarClient { return &StellarClient{ - hzClient: NewHorizonClient(), - kp: kp, + hzClient: NewHorizonClient(), + keyHolder: newKeyHolder(kp), } } -func (s *StellarClient) GetKeyPair() *keypair.Full { - return s.kp -} - func NewHorizonMasterClient() *StellarClient { sourceKey := keypair.Root(NETWORK_PASSPHRASE) return &StellarClient{ - hzClient: &horizonclient.Client{HorizonURL: HorizonURL}, - kp: sourceKey, + hzClient: &horizonclient.Client{HorizonURL: HorizonURL}, + keyHolder: newKeyHolder(sourceKey), } } -// func (m *StellarClient) GetMaster() *horizonclient.Client { -// return m.hzClient -// } - -// func (m *StellarClient) GetSourceKey() *keypair.Full { -// return m.kp -// } - func (c *StellarClient) GetHorizonClient() *horizonclient.Client { return c.hzClient } -func (h *StellarClient) GetAccount(kp *keypair.Full) (horizon.Account, error) { - accountReq := horizonclient.AccountRequest{AccountID: kp.Address()} +func (h *StellarClient) GetAccount() (horizon.Account, error) { + accountReq := horizonclient.AccountRequest{AccountID: h.GetAddress()} hzAccount, err := h.hzClient.AccountDetail(accountReq) if err != nil { return hzAccount, err @@ -72,11 +69,30 @@ func (h *StellarClient) GetAccount(kp *keypair.Full) (horizon.Account, error) { return hzAccount, nil } -func (s *StellarClient) GetHorizonAccount(kp *keypair.Full) (horizon.Account, error) { - accountReq := horizonclient.AccountRequest{AccountID: kp.Address()} +func (s *StellarClient) GetAddress() string { + return s.keyHolder.kp.Address() +} + +func (s *StellarClient) GetHorizonAccount() (horizon.Account, error) { + accountReq := horizonclient.AccountRequest{AccountID: s.GetAddress()} hzAccount, err := s.hzClient.AccountDetail(accountReq) if err != nil { return hzAccount, err } return hzAccount, nil } + +func (s *StellarClient) CreateSignedTxFromParams(txParams txnbuild.TransactionParams) (*txnbuild.Transaction, error) { + + txUnsigned, err := txnbuild.NewTransaction(txParams) + if err != nil { + return nil, err + } + + tx, err := txUnsigned.Sign(NETWORK_PASSPHRASE, s.keyHolder.kp) + if err != nil { + return nil, err + } + + return tx, nil +} diff --git a/channel/env/transaction.go b/channel/env/transaction.go index 05b7641..211a502 100644 --- a/channel/env/transaction.go +++ b/channel/env/transaction.go @@ -266,11 +266,11 @@ func BuildForceCloseTxArgs(chanID pchannel.ID) (xdr.ScVec, error) { return getChannelArgs, nil } -func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress, kp *keypair.Full) (xdr.TransactionMeta, error) { +func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xdr.ScVec, contractAddr xdr.ScAddress) (xdr.TransactionMeta, error) { sharedMtx.Lock() defer sharedMtx.Unlock() fnameXdr := xdr.ScSymbol(fname) - hzAcc, err := s.GetHorizonAccount(kp) + hzAcc, err := s.GetHorizonAccount() if err != nil { return xdr.TransactionMeta{}, err } @@ -280,7 +280,7 @@ func (s *StellarClient) InvokeAndProcessHostFunction(fname string, callTxArgs xd preFlightOp, minFee := PreflightHostFunctions(hzClient, &hzAcc, *invokeHostFunctionOp) txParams := GetBaseTransactionParamsWithFee(&hzAcc, minFee, &preFlightOp) - txSigned, err := CreateSignedTransactionWithParams([]*keypair.Full{kp}, txParams) + txSigned, err := s.CreateSignedTxFromParams(txParams) if err != nil { return xdr.TransactionMeta{}, err } diff --git a/channel/funder.go b/channel/funder.go index 4ccf9cb..aaa5c15 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -17,7 +17,6 @@ package channel import ( "context" "errors" - "github.com/stellar/go/keypair" "github.com/stellar/go/xdr" "log" pchannel "perun.network/go-perun/channel" @@ -33,18 +32,16 @@ const DefaultPollingInterval = time.Duration(6) * time.Second type Funder struct { stellarClient *env.StellarClient acc *wallet.Account - kpFull *keypair.Full perunID xdr.ScAddress assetID xdr.ScAddress maxIters int pollingInterval time.Duration } -func NewFunder(acc *wallet.Account, kp *keypair.Full, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *Funder { +func NewFunder(acc *wallet.Account, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *Funder { return &Funder{ stellarClient: stellarClient, acc: acc, - kpFull: kp, perunID: perunID, assetID: assetID, maxIters: MaxIterationsUntilAbort, @@ -130,13 +127,11 @@ func (f *Funder) openChannel(ctx context.Context, req pchannel.FundingReq) error func (f *Funder) OpenChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { perunAddress := f.GetPerunID() - kp := f.kpFull - openTxArgs, err := env.BuildOpenTxArgs(params, state) if err != nil { return errors.New("error while building open tx") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("open", openTxArgs, perunAddress, kp) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("open", openTxArgs, perunAddress) if err != nil { return errors.New("error while invoking and processing host function: open") } @@ -154,7 +149,6 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state perunAddress := f.GetPerunID() tokenAddress := f.GetAssetID() - kp := f.kpFull chanId := state.ID fundTxArgs, err := env.BuildFundTxArgs(chanId, funderIdx) @@ -173,7 +167,7 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state return errors.New("tokenIDAddrFromBals not equal to tokenContractAddress") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("fund", fundTxArgs, perunAddress, kp) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("fund", fundTxArgs, perunAddress) if err != nil { return errors.New("error while invoking and processing host function: fund") } @@ -189,14 +183,13 @@ func (f *Funder) FundChannel(ctx context.Context, params *pchannel.Params, state func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, state *pchannel.State) error { contractAddress := f.GetPerunID() - kp := f.kpFull chanId := state.ID openTxArgs, err := env.BuildGetChannelTxArgs(chanId) if err != nil { return errors.New("error while building get_channel tx") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("abort_funding", openTxArgs, contractAddress, kp) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("abort_funding", openTxArgs, contractAddress) if err != nil { return errors.New("error while invoking and processing host function: abort_funding") } @@ -212,14 +205,13 @@ func (f *Funder) AbortChannel(ctx context.Context, params *pchannel.Params, stat func (f *Funder) GetChannelState(ctx context.Context, params *pchannel.Params, state *pchannel.State) (wire.Channel, error) { contractAddress := f.GetPerunID() - kp := f.kpFull chanId := state.ID getchTxArgs, err := env.BuildGetChannelTxArgs(chanId) if err != nil { return wire.Channel{}, errors.New("error while building get_channel tx") } - txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress, kp) + txMeta, err := f.stellarClient.InvokeAndProcessHostFunction("get_channel", getchTxArgs, contractAddress) if err != nil { return wire.Channel{}, errors.New("error while processing and submitting get_channel tx") } diff --git a/channel/test/setup.go b/channel/test/setup.go index 091d142..125e19f 100644 --- a/channel/test/setup.go +++ b/channel/test/setup.go @@ -101,7 +101,7 @@ func NewTestSetup(t *testing.T) *Setup { channelAccs := []*wallet.Account{accs[0], accs[1]} channelClients := []*env.StellarClient{aliceClient, bobClient} - funders, adjs := createFundersAndAdjudicators(channelAccs, kpsToFund, stellarClients, perunAddress, tokenAddress) + funders, adjs := createFundersAndAdjudicators(channelAccs, stellarClients, perunAddress, tokenAddress) setup := Setup{ t: t, @@ -123,12 +123,12 @@ func setupAccountsAndContracts(t *testing.T, deployerKp *keypair.Full, kps []*ke } } -func createFundersAndAdjudicators(accs []*wallet.Account, kps []*keypair.Full, clients []*env.StellarClient, perunAddress, tokenAddress xdr.ScAddress) ([]*channel.Funder, []*channel.Adjudicator) { +func createFundersAndAdjudicators(accs []*wallet.Account, clients []*env.StellarClient, perunAddress, tokenAddress xdr.ScAddress) ([]*channel.Funder, []*channel.Adjudicator) { funders := make([]*channel.Funder, len(accs)) adjs := make([]*channel.Adjudicator, len(accs)) for i, acc := range accs { - funders[i] = channel.NewFunder(acc, kps[i], clients[i], perunAddress, tokenAddress) - adjs[i] = channel.NewAdjudicator(acc, kps[i], clients[i], perunAddress, tokenAddress) + funders[i] = channel.NewFunder(acc, clients[i], perunAddress, tokenAddress) + adjs[i] = channel.NewAdjudicator(acc, clients[i], perunAddress, tokenAddress) } return funders, adjs } @@ -179,20 +179,20 @@ func CreateFundStellarAccounts(pairs []*keypair.Full, initialBalance string) err masterClient := env.NewHorizonMasterClient() masterHzClient := masterClient.GetHorizonClient() - sourceKey := masterClient.GetKeyPair() + sourceKey := keypair.Root(env.NETWORK_PASSPHRASE) hzClient := env.NewHorizonClient() ops := make([]txnbuild.Operation, numKps) - accReq := horizonclient.AccountRequest{AccountID: sourceKey.Address()} + accReq := horizonclient.AccountRequest{AccountID: masterClient.GetAddress()} sourceAccount, err := masterHzClient.AccountDetail(accReq) if err != nil { panic(err) } masterAccount := txnbuild.SimpleAccount{ - AccountID: sourceKey.Address(), + AccountID: masterClient.GetAddress(), Sequence: sourceAccount.Sequence, } diff --git a/channel/test/token.go b/channel/test/token.go index eca821e..1c8a18b 100644 --- a/channel/test/token.go +++ b/channel/test/token.go @@ -103,7 +103,7 @@ func InitTokenContract(kp *keypair.Full, contractIDAddress xdr.ScAddress) error if err != nil { panic(err) } - _, err = stellarClient.InvokeAndProcessHostFunction("initialize", initArgs, contractIDAddress, kp) + _, err = stellarClient.InvokeAndProcessHostFunction("initialize", initArgs, contractIDAddress) if err != nil { panic(err) } @@ -116,7 +116,7 @@ func GetTokenName(kp *keypair.Full, contractAddress xdr.ScAddress) error { stellarClient := env.NewStellarClient(kp) TokenNameArgs := xdr.ScVec{} - _, err := stellarClient.InvokeAndProcessHostFunction("name", TokenNameArgs, contractAddress, kp) + _, err := stellarClient.InvokeAndProcessHostFunction("name", TokenNameArgs, contractAddress) if err != nil { panic(err) } @@ -215,7 +215,7 @@ func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount uint64, reci if err != nil { panic(err) } - _, err = stellarClient.InvokeAndProcessHostFunction("mint", mintTokenArgs, contractAddr, kp) + _, err = stellarClient.InvokeAndProcessHostFunction("mint", mintTokenArgs, contractAddr) if err != nil { panic(err) } From 25f6c780bb7b1802d008abe210cf8a828c6e1012 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 16 Feb 2024 09:56:31 +0100 Subject: [PATCH 50/51] refactor(channel/funder): Remove unused acc field in Funder struct --- channel/funder.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/channel/funder.go b/channel/funder.go index aaa5c15..92bddfb 100644 --- a/channel/funder.go +++ b/channel/funder.go @@ -31,7 +31,6 @@ const DefaultPollingInterval = time.Duration(6) * time.Second type Funder struct { stellarClient *env.StellarClient - acc *wallet.Account perunID xdr.ScAddress assetID xdr.ScAddress maxIters int @@ -41,7 +40,6 @@ type Funder struct { func NewFunder(acc *wallet.Account, stellarClient *env.StellarClient, perunID xdr.ScAddress, assetID xdr.ScAddress) *Funder { return &Funder{ stellarClient: stellarClient, - acc: acc, perunID: perunID, assetID: assetID, maxIters: MaxIterationsUntilAbort, From ae05dd2244ab0814e0eebb424fcd48d711e088f3 Mon Sep 17 00:00:00 2001 From: Ilja von Hoessle Date: Fri, 16 Feb 2024 10:59:11 +0100 Subject: [PATCH 51/51] style(wallet, channel): Fix error capitalization --- channel/adjudicator.go | 3 +-- wallet/backend.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/channel/adjudicator.go b/channel/adjudicator.go index 90d6e75..f48aca1 100644 --- a/channel/adjudicator.go +++ b/channel/adjudicator.go @@ -31,7 +31,7 @@ import ( "time" ) -var ErrChannelAlreadyClosed = errors.New("Channel is already closed") +var ErrChannelAlreadyClosed = errors.New("channel is already closed") type Adjudicator struct { log log.Embedding @@ -135,7 +135,6 @@ func (a *Adjudicator) GetChannelState(ctx context.Context, state *pchannel.State func (a *Adjudicator) BuildWithdrawTxArgs(req pchannel.AdjudicatorReq) (xdr.ScVec, error) { - // build withdrawalargs chanIDStellar := req.Tx.ID[:] partyIdx := req.Idx var withdrawIdx xdr.ScVal diff --git a/wallet/backend.go b/wallet/backend.go index e2cf822..477b1e7 100644 --- a/wallet/backend.go +++ b/wallet/backend.go @@ -49,7 +49,7 @@ func (b backend) DecodeSig(reader io.Reader) (wallet.Sig, error) { func (b backend) VerifySignature(msg []byte, sig wallet.Sig, a wallet.Address) (bool, error) { p, ok := a.(*types.Participant) if !ok { - return false, errors.New("Participant has invalid type") + return false, errors.New("participant has invalid type") } if len(sig) != ed25519.SignatureSize { return false, errors.New("invalid signature size")