[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-cashless2ecash] branch master updated: code: add simulation clien
From: |
gnunet |
Subject: |
[taler-cashless2ecash] branch master updated: code: add simulation client and attestor |
Date: |
Mon, 01 Apr 2024 21:54:33 +0200 |
This is an automated email from the git hooks/post-receive script.
joel-haeberli pushed a commit to branch master
in repository cashless2ecash.
The following commit(s) were added to refs/heads/master by this push:
new 5e8a195 code: add simulation client and attestor
5e8a195 is described below
commit 5e8a195e45ea5dbdd5b4c4a3b329f47f851ac901
Author: Joel-Haeberli <haebu@rubigen.ch>
AuthorDate: Mon Apr 1 21:54:14 2024 +0200
code: add simulation client and attestor
---
c2ec/attestor.go | 2 +-
c2ec/bank-integration.go | 29 +++++++-
c2ec/c2ec | Bin 0 -> 14393824 bytes
c2ec/c2ec-config.yaml | 5 +-
c2ec/config.go | 2 +
c2ec/db.go | 10 +--
c2ec/db/0000-c2ec_test.sql | 17 +++++
c2ec/db/0000-c2ec_test_rollback.sql | 17 +++++
c2ec/http-util.go | 15 +++-
c2ec/main.go | 77 +++++++++++++++++++--
c2ec/postgres.go | 17 +++--
.../{wallee-attestor.go => simulation-attestor.go} | 44 ++++++------
c2ec/simulation-client.go | 43 ++++++++++++
c2ec/wallee-attestor.go | 28 ++++----
c2ec/wire-gateway.go | 2 +
15 files changed, 247 insertions(+), 61 deletions(-)
diff --git a/c2ec/attestor.go b/c2ec/attestor.go
index 11eb75f..70edb0c 100644
--- a/c2ec/attestor.go
+++ b/c2ec/attestor.go
@@ -26,7 +26,7 @@ type Attestor[T any] interface {
// be retrieved through the supplied error channel.
Listen(ctx context.Context, notificationChannel chan *T, errs chan
error) error
// Attests a single withdrawal.
- Attest(withdrawalId int, providerTransactionId string, errs chan error)
error
+ Attest(withdrawalId int, providerTransactionId string, errs chan error)
}
// Sets up and runs an attestor in the background. This must be called at
startup.
diff --git a/c2ec/bank-integration.go b/c2ec/bank-integration.go
index 15c8f66..9b5305a 100644
--- a/c2ec/bank-integration.go
+++ b/c2ec/bank-integration.go
@@ -269,6 +269,18 @@ func handlePaymentNotification(res http.ResponseWriter,
req *http.Request) {
paymentNotification.Amount,
paymentNotification.Amount,
)
+ if err != nil {
+ err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
+ TypeUri: TALER_URI_PROBLEM_PREFIX +
"/C2EC_PAYMENT_NOTIFICATION_FAILED",
+ Title: "payment notification failed",
+ Detail: "the payment notification failed during the
processing of the message: " + err.Error(),
+ Instance: req.RequestURI,
+ })
+ if err != nil {
+ res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+ }
+ return
+ }
res.WriteHeader(HTTP_NO_CONTENT)
}
@@ -286,11 +298,24 @@ func getWithdrawalOrWriteError(wopid string, res
http.ResponseWriter, reqUri str
withdrawal, err := DB.GetWithdrawalByWopid(wopid)
if err != nil {
- // TODO : What, if just no entry has been found -> 404?
err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR,
&RFC9457Problem{
TypeUri: TALER_URI_PROBLEM_PREFIX +
"/C2EC_WITHDRAWAL_STATUS_DB_FAILURE",
Title: "database failure",
- Detail: "the registration of the withdrawal failed
due to db failure (error:" + err.Error() + ")",
+ Detail: "db failure while requesting withdrawal
(error:" + err.Error() + ")",
+ Instance: reqUri,
+ })
+ if err != nil {
+ res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+ }
+ return
+ }
+
+ if withdrawal == nil {
+ // not found -> 404
+ err := WriteProblem(res, HTTP_NOT_FOUND, &RFC9457Problem{
+ TypeUri: TALER_URI_PROBLEM_PREFIX +
"/C2EC_WITHDRAWAL_NOT_FOUND",
+ Title: "Not Found",
+ Detail: "No withdrawal with wopid=" + wopid + " could
been found.",
Instance: reqUri,
})
if err != nil {
diff --git a/c2ec/c2ec b/c2ec/c2ec
new file mode 100755
index 0000000..b190a72
Binary files /dev/null and b/c2ec/c2ec differ
diff --git a/c2ec/c2ec-config.yaml b/c2ec/c2ec-config.yaml
index 1d5fae2..3032884 100644
--- a/c2ec/c2ec-config.yaml
+++ b/c2ec/c2ec-config.yaml
@@ -1,8 +1,10 @@
c2ec:
+ prod: false
host: "localhost"
port: 8081
- unix-domain-socket: true
+ unix-domain-socket: false
unix-socket-path: "c2ec.sock"
+ fail-on-missing-attestors: false # forced if prod=true
db:
host: "localhost"
port: 5432
@@ -11,3 +13,4 @@ db:
database: "postgres"
providers:
- "Wallee"
+ - "Simulation"
diff --git a/c2ec/config.go b/c2ec/config.go
index 2f3b9cc..d4a3028 100644
--- a/c2ec/config.go
+++ b/c2ec/config.go
@@ -13,10 +13,12 @@ type C2ECConfig struct {
}
type C2ECServerConfig struct {
+ IsProd bool `yaml:"prod"`
Host string `yaml:"host"`
Port int `yaml:"port"`
UseUnixDomainSocket bool `yaml:"unix-domain-socket"`
UnixSocketPath string `yaml:"unix-socket-path"`
+ StrictAttestors bool `yaml:"fail-on-missing-attestors"`
}
type C2ECDatabseConfig struct {
diff --git a/c2ec/db.go b/c2ec/db.go
index 80a33a4..12fe2e4 100644
--- a/c2ec/db.go
+++ b/c2ec/db.go
@@ -4,20 +4,20 @@ import (
"context"
)
-const PROVIDER_TABLE_NAME = "provider"
+const PROVIDER_TABLE_NAME = "c2ec.provider"
const PROVIDER_FIELD_NAME_ID = "terminal_id"
const PROVIDER_FIELD_NAME_NAME = "name"
const PROVIDER_FIELD_NAME_BACKEND_URL = "backend_base_url"
const PROVIDER_FIELD_NAME_BACKEND_CREDENTIALS = "backend_credentials"
-const TERMINAL_TABLE_NAME = "terminal"
+const TERMINAL_TABLE_NAME = "c2ec.terminal"
const TERMINAL_FIELD_NAME_ID = "terminal_id"
const TERMINAL_FIELD_NAME_ACCESS_TOKEN = "access_token"
const TERMINAL_FIELD_NAME_ACTIVE = "active"
const TERMINAL_FIELD_NAME_DESCRIPTION = "description"
const TERMINAL_FIELD_NAME_PROVIDER_ID = "provider_id"
-const WITHDRAWAL_TABLE_NAME = "withdrawal"
+const WITHDRAWAL_TABLE_NAME = "c2ec.withdrawal"
const WITHDRAWAL_FIELD_NAME_ID = "withdrawal_id"
const WITHDRAWAL_FIELD_NAME_WOPID = "wopid"
const WITHDRAWAL_FIELD_NAME_RESPUBKEY = "reserve_pub_key"
@@ -110,8 +110,8 @@ type C2ECDatabase interface {
completionProof []byte,
) error
- // Get a provider entry by its identifier
- GetTerminalProviderById(id int) (*Provider, error)
+ // Get a provider entry by its name
+ GetTerminalProviderByName(name string) (*Provider, error)
// Get a terminal entry by its identifier
GetTerminalById(id int) (*Terminal, error)
diff --git a/c2ec/db/0000-c2ec_test.sql b/c2ec/db/0000-c2ec_test.sql
new file mode 100644
index 0000000..ee85912
--- /dev/null
+++ b/c2ec/db/0000-c2ec_test.sql
@@ -0,0 +1,17 @@
+BEGIN;
+
+SET search_path TO c2ec;
+
+DROP TABLE IF EXISTS p_id;
+
+INSERT INTO provider (name, backend_base_url, backend_credentials)
+ VALUES ('Simulation', 'will be simulated', 'no creds');
+
+SELECT provider_id INTO p_id FROM provider WHERE name = 'Simulation';
+
+INSERT INTO terminal (access_token, description, provider_id)
+ VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'this is a simulated
terminal', (SELECT * FROM p_id));
+
+COMMIT;
+
+SELECT * FROM provider;
diff --git a/c2ec/db/0000-c2ec_test_rollback.sql
b/c2ec/db/0000-c2ec_test_rollback.sql
new file mode 100644
index 0000000..0d9ee48
--- /dev/null
+++ b/c2ec/db/0000-c2ec_test_rollback.sql
@@ -0,0 +1,17 @@
+BEGIN;
+
+SET search_path TO c2ec;
+
+DROP TABLE IF EXISTS p_r_id;
+DROP TABLE IF EXISTS t_r_id;
+
+SELECT provider_id INTO p_r_id FROM provider WHERE name = 'Simulation';
+SELECT terminal_id INTO t_r_id FROM terminal WHERE provider_id = (SELECT *
FROM p_r_id);
+
+DELETE FROM withdrawal WHERE terminal_id = (SELECT * FROM t_r_id);
+DELETE FROM terminal WHERE provider_id = (SELECT * FROM p_r_id);
+DELETE FROM provider WHERE provider_id = (SELECT * FROM p_r_id);
+
+COMMIT;
+
+SELECT * FROM provider;
\ No newline at end of file
diff --git a/c2ec/http-util.go b/c2ec/http-util.go
index 9f833d2..4cd8158 100644
--- a/c2ec/http-util.go
+++ b/c2ec/http-util.go
@@ -40,8 +40,8 @@ func WriteProblem(res http.ResponseWriter, status int,
problem *RFC9457Problem)
return err
}
- res.Write(problm)
res.WriteHeader(status)
+ res.Write(problm)
return nil
}
@@ -73,6 +73,19 @@ func AcceptOptionalParamOrWriteResponse[T any](
return nil, false
}
+ if ptr == nil {
+ err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR,
&RFC9457Problem{
+ TypeUri: TALER_URI_PROBLEM_PREFIX +
"/C2EC_INVALID_REQUEST_QUERY_PARAMETER",
+ Title: "invalid request query parameter",
+ Detail: "the withdrawal status request parameter '" +
name + "' resulted in a nil pointer)",
+ Instance: req.RequestURI,
+ })
+ if err != nil {
+ res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+ }
+ return nil, false
+ }
+
obj := *ptr
assertedObj, ok := any(obj).(T)
if !ok {
diff --git a/c2ec/main.go b/c2ec/main.go
index 3cf98c6..9eaf45e 100644
--- a/c2ec/main.go
+++ b/c2ec/main.go
@@ -1,6 +1,8 @@
package main
import (
+ "context"
+ "errors"
"fmt"
"net"
http "net/http"
@@ -26,9 +28,10 @@ var DB C2ECDatabase
// The startup follows these steps:
// 1. load configuration or panic
// 2. setup database or panic
-// 3. setup routes for the bank-integration-api
-// 4. setup routes for the wire-gateway-api
-// 5. listen for incoming requests (as specified in config)
+// 3. setup attestors
+// 4. setup routes for the bank-integration-api
+// 5. setup routes for the wire-gateway-api
+// 6. listen for incoming requests (as specified in config)
func main() {
cfgPath := DEFAULT_C2EC_CONFIG_PATH
@@ -40,11 +43,16 @@ func main() {
panic("unable to load config: " + err.Error())
}
- DB, err = setupDatabase(cfg.Database)
+ DB, err = setupDatabase(&cfg.Database)
if err != nil {
panic("unable initialize datatbase: " + err.Error())
}
+ err = setupAttestors(cfg)
+ if err != nil {
+ panic("unable initialize attestors: " + err.Error())
+ }
+
router := http.NewServeMux()
setupBankIntegrationRoutes(router)
@@ -83,9 +91,59 @@ func main() {
}
}
-func setupDatabase(cfg C2ECDatabseConfig) (C2ECDatabase, error) {
+func setupDatabase(cfg *C2ECDatabseConfig) (C2ECDatabase, error) {
- return NewC2ECPostgres(&cfg)
+ return NewC2ECPostgres(cfg)
+}
+
+func setupAttestors(cfg *C2ECConfig) error {
+
+ if DB == nil {
+ return errors.New("setup database first")
+ }
+
+ for _, providerName := range cfg.Providers {
+
+ p, err := DB.GetTerminalProviderByName(providerName)
+ if err != nil {
+ return err
+ }
+
+ if p == nil {
+ if cfg.Server.IsProd || cfg.Server.StrictAttestors {
+ panic("no provider entry for " + providerName)
+ } else {
+ fmt.Println("non-strict attestor
initialization. skipping", providerName)
+ continue
+ }
+ }
+
+ if !cfg.Server.IsProd {
+ // Prevent simulation provider to be loaded in
productive environments.
+ if p.Name == "Simulation" {
+ attestor := new(SimulationAttestor)
+ errs, err := RunAttestor(context.Background(),
attestor, p, &cfg.Database)
+ if err != nil {
+ return err
+ }
+ go EndlessChannelResultPrinter("Simulation
Attestor Error:", errs)
+ }
+ }
+
+ if p.Name == "Wallee" {
+ attestor := new(WalleeAttestor)
+ errs, err := RunAttestor(context.Background(),
attestor, p, &cfg.Database)
+ if err != nil {
+ return err
+ } else {
+ go EndlessChannelResultPrinter("Wallee Attestor
Error:", errs)
+ }
+ }
+
+ // For new added provider, add the respective if-clause
+ }
+
+ return nil
}
func setupBankIntegrationRoutes(router *http.ServeMux) {
@@ -143,3 +201,10 @@ func setupWireGatewayRoutes(router *http.ServeMux) {
adminAddIncoming,
)
}
+
+func EndlessChannelResultPrinter[T any](prefix string, c chan T) {
+
+ out := <-c
+ fmt.Println(prefix, out)
+ EndlessChannelResultPrinter(prefix, c)
+}
diff --git a/c2ec/postgres.go b/c2ec/postgres.go
index 10ee97c..acd703e 100644
--- a/c2ec/postgres.go
+++ b/c2ec/postgres.go
@@ -43,8 +43,8 @@ const PS_FINALISE_PAYMENT = "UPDATE " + WITHDRAWAL_TABLE_NAME
+ " SET (" +
const PS_GET_WITHDRAWAL_BY_WOPID = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
" WHERE " + WITHDRAWAL_FIELD_NAME_WOPID + "=$1"
-const PS_GET_PROVIDER_BY_ID = "SELECT * FROM " + PROVIDER_TABLE_NAME +
- " WHERE " + PROVIDER_FIELD_NAME_ID + "=$1"
+const PS_GET_PROVIDER_BY_NAME = "SELECT * FROM " + PROVIDER_TABLE_NAME +
+ " WHERE " + PROVIDER_FIELD_NAME_NAME + "=$1"
const PS_GET_TERMINAL_BY_ID = "SELECT * FROM " + TERMINAL_TABLE_NAME +
" WHERE " + TERMINAL_FIELD_NAME_ID + "=$1"
@@ -134,6 +134,9 @@ func (db *C2ECPostgres) GetWithdrawalByWopid(wopid string)
(*Withdrawal, error)
return nil, err
}
+ if len(withdrawals) < 1 {
+ return nil, nil
+ }
return withdrawals[0], nil
}
}
@@ -206,12 +209,12 @@ func (db *C2ECPostgres) FinaliseWithdrawal(
return nil
}
-func (db *C2ECPostgres) GetTerminalProviderById(id int) (*Provider, error) {
+func (db *C2ECPostgres) GetTerminalProviderByName(name string) (*Provider,
error) {
if row, err := db.pool.Query(
db.ctx,
- PS_GET_PROVIDER_BY_ID,
- id,
+ PS_GET_PROVIDER_BY_NAME,
+ name,
); err != nil {
if row != nil {
row.Close()
@@ -226,6 +229,10 @@ func (db *C2ECPostgres) GetTerminalProviderById(id int)
(*Provider, error) {
return nil, err
}
+ if len(provider) < 1 {
+ return nil, nil
+ }
+
return provider[0], nil
}
}
diff --git a/c2ec/wallee-attestor.go b/c2ec/simulation-attestor.go
similarity index 73%
copy from c2ec/wallee-attestor.go
copy to c2ec/simulation-attestor.go
index 80c96d5..30be17e 100644
--- a/c2ec/wallee-attestor.go
+++ b/c2ec/simulation-attestor.go
@@ -11,13 +11,13 @@ import (
"github.com/jackc/pgxlisten"
)
-type WalleeAttestor struct {
+type SimulationAttestor struct {
listener *pgxlisten.Listener
provider *Provider
providerClient ProviderClient[WalleeClient]
}
-func (wa *WalleeAttestor) Setup(p *Provider, cfg *C2ECDatabseConfig) (chan
*pgconn.Notification, error) {
+func (wa *SimulationAttestor) Setup(p *Provider, cfg *C2ECDatabseConfig) (chan
*pgconn.Notification, error) {
connectionString := PostgresConnectionString(cfg)
@@ -28,7 +28,7 @@ func (wa *WalleeAttestor) Setup(p *Provider, cfg
*C2ECDatabseConfig) (chan *pgco
wa.provider = p
- wa.providerClient = new(WalleeClient)
+ wa.providerClient = new(SimulationClient)
err = wa.providerClient.SetupClient(wa.provider)
if err != nil {
panic(err.Error())
@@ -39,7 +39,7 @@ func (wa *WalleeAttestor) Setup(p *Provider, cfg
*C2ECDatabseConfig) (chan *pgco
return notificationChannel, nil
}
-func (wa *WalleeAttestor) Listen(
+func (wa *SimulationAttestor) Listen(
ctx context.Context,
notificationChannel chan *pgconn.Notification,
errs chan error,
@@ -49,21 +49,6 @@ func (wa *WalleeAttestor) Listen(
return errors.New("attestor needs to be setup first")
}
- // we must listen for notifications async
- go func() {
- for {
- select {
- case notification := <-notificationChannel:
- // the dispatching can be done asynchronously
- go wa.dispatch(notification, errs)
- case <-ctx.Done():
- close(notificationChannel)
- close(errs)
- return
- }
- }
- }()
-
go func() {
err := wa.listener.Listen(ctx)
if err != nil {
@@ -73,10 +58,21 @@ func (wa *WalleeAttestor) Listen(
close(errs)
}()
- return nil
+ // Listen is started async. We can therefore block here and must
+ // not run the retrieval logic in own goroutine
+ for {
+ select {
+ case notification := <-notificationChannel:
+ // the dispatching can be done asynchronously
+ go wa.dispatch(notification, errs)
+ case <-ctx.Done():
+ close(notificationChannel)
+ close(errs)
+ }
+ }
}
-func (wa *WalleeAttestor) Attest(withdrawalId int, providerTransactionId
string, errs chan error) {
+func (wa *SimulationAttestor) Attest(withdrawalId int, providerTransactionId
string, errs chan error) {
transaction, err :=
wa.providerClient.GetTransaction(providerTransactionId)
if err != nil {
@@ -102,7 +98,7 @@ func (wa *WalleeAttestor) Attest(withdrawalId int,
providerTransactionId string,
}
}
-func (wa *WalleeAttestor) dispatch(notification *pgconn.Notification, errs
chan error) {
+func (wa *SimulationAttestor) dispatch(notification *pgconn.Notification, errs
chan error) {
// The payload is formatted like:
"{PROVIDER_NAME}|{WITHDRAWAL_ID}|{PROVIDER_TRANSACTION_ID}"
// the validation is strict. This means, that the dispatcher emits an
error
@@ -114,8 +110,8 @@ func (wa *WalleeAttestor) dispatch(notification
*pgconn.Notification, errs chan
}
provider := payload[0]
- if provider != "Wallee" {
- // the Wallee attestor can only handle wallee transactions
+ if provider != "Simulation" {
+ // the Simulation attestor can only handle wallee transactions
return
}
withdrawalRowId, err := strconv.Atoi(payload[1])
diff --git a/c2ec/simulation-client.go b/c2ec/simulation-client.go
new file mode 100644
index 0000000..e7ae253
--- /dev/null
+++ b/c2ec/simulation-client.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "fmt"
+)
+
+type SimulationTransaction struct {
+ ProviderTransaction
+
+ allow bool
+}
+
+type SimulationClient struct {
+ ProviderClient[SimulationTransaction]
+
+ // toggle this to simulate failed transactions.
+ AllowNextWithdrawal bool
+}
+
+func (st *SimulationTransaction) AllowWithdrawal() bool {
+
+ return st.allow
+}
+
+func (*SimulationClient) SetupClient(p *Provider) error {
+
+ fmt.Println("setting up simulation client. probably not what you want
in production")
+ return nil
+}
+
+func (sc *SimulationClient) GetTransaction(transactionId string)
(ProviderTransaction, error) {
+
+ fmt.Println("getting transaction from simulation provider")
+ st := new(SimulationTransaction)
+ st.allow = sc.AllowNextWithdrawal
+ return st, nil
+}
+
+func (*SimulationClient) Refund(transactionId string) error {
+
+ fmt.Println("refund triggered for simulation provider with transaction
id: ", transactionId)
+ return nil
+}
diff --git a/c2ec/wallee-attestor.go b/c2ec/wallee-attestor.go
index 80c96d5..b65ed21 100644
--- a/c2ec/wallee-attestor.go
+++ b/c2ec/wallee-attestor.go
@@ -49,21 +49,6 @@ func (wa *WalleeAttestor) Listen(
return errors.New("attestor needs to be setup first")
}
- // we must listen for notifications async
- go func() {
- for {
- select {
- case notification := <-notificationChannel:
- // the dispatching can be done asynchronously
- go wa.dispatch(notification, errs)
- case <-ctx.Done():
- close(notificationChannel)
- close(errs)
- return
- }
- }
- }()
-
go func() {
err := wa.listener.Listen(ctx)
if err != nil {
@@ -73,7 +58,18 @@ func (wa *WalleeAttestor) Listen(
close(errs)
}()
- return nil
+ // Listen is started async. We can therefore block here and must
+ // not run the retrieval logic in own goroutine
+ for {
+ select {
+ case notification := <-notificationChannel:
+ // the dispatching can be done asynchronously
+ go wa.dispatch(notification, errs)
+ case <-ctx.Done():
+ close(notificationChannel)
+ close(errs)
+ }
+ }
}
func (wa *WalleeAttestor) Attest(withdrawalId int, providerTransactionId
string, errs chan error) {
diff --git a/c2ec/wire-gateway.go b/c2ec/wire-gateway.go
index 329c9b8..e308a28 100644
--- a/c2ec/wire-gateway.go
+++ b/c2ec/wire-gateway.go
@@ -103,6 +103,7 @@ func historyIncoming(res http.ResponseWriter, req
*http.Request) {
// This method is currently dead and implemented for API conformance
func historyOutgoing(res http.ResponseWriter, req *http.Request) {
+ // not implemented, because not used
res.WriteHeader(HTTP_BAD_REQUEST)
}
@@ -125,5 +126,6 @@ type AddIncomingResponse struct {
// This method is currently dead and implemented for API conformance
func adminAddIncoming(res http.ResponseWriter, req *http.Request) {
+ // not implemented, because not used
res.WriteHeader(HTTP_BAD_REQUEST)
}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-cashless2ecash] branch master updated: code: add simulation client and attestor,
gnunet <=