summaryrefslogtreecommitdiff
path: root/c2ec/attestor.go
blob: fb2bda205caeb99661a29c0fd6df0db462e4318d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package main

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
)

const PAYMENT_NOTIFICATION_CHANNEL_BUFFER_SIZE = 10
const PS_PAYMENT_NOTIFICATION_CHANNEL = "payment_notification"

// Sets up and runs an attestor in the background. This must be called at startup.
func RunAttestor(
	ctx context.Context,
	errs chan error,
) {

	go RunListener(
		ctx,
		PS_PAYMENT_NOTIFICATION_CHANNEL,
		attestationCallback,
		make(chan *Notification, PAYMENT_NOTIFICATION_CHANNEL_BUFFER_SIZE),
		errs,
	)
}

func attestationCallback(notification *Notification, errs chan error) {

	LogInfo("attestor", fmt.Sprintf("retrieved information on channel=%s with payload=%s", notification.Channel, notification.Payload))

	// The payload is formatted like: "{PROVIDER_NAME}|{WITHDRAWAL_ID}|{PROVIDER_TRANSACTION_ID}"
	// the validation is strict. This means, that the dispatcher emits an error
	// and returns, if a property is malformed.
	payload := strings.Split(notification.Payload, "|")
	if len(payload) != 3 {
		errs <- errors.New("malformed notification payload: " + notification.Payload)
		return
	}

	providerName := payload[0]
	if providerName == "" {
		errs <- errors.New("the provider of the payment is not specified")
		return
	}
	withdrawalRowId, err := strconv.Atoi(payload[1])
	if err != nil {
		errs <- errors.New("malformed withdrawal_id: " + err.Error())
		return
	}
	providerTransactionId := payload[2]

	client := PROVIDER_CLIENTS[providerName]
	if client == nil {
		errs <- errors.New("no provider client registered for provider " + providerName)
	}

	transaction, err := client.GetTransaction(providerTransactionId)
	if err != nil {
		LogError("attestor", err)
		prepareRetryOrAbort(withdrawalRowId, errs)
		return
	}

	finaliseOrSetRetry(
		transaction,
		withdrawalRowId,
		errs,
	)
}

func finaliseOrSetRetry(
	transaction ProviderTransaction,
	withdrawalRowId int,
	errs chan error,
) {

	if transaction == nil {
		err := errors.New("transaction was nil. will set retry or abort")
		LogError("attestor", err)
		errs <- err
		prepareRetryOrAbort(withdrawalRowId, errs)
		return
	}

	completionProof := transaction.Bytes()
	if len(completionProof) > 0 {
		// only allow finalization operation, when the completion
		// proof of the transaction could be retrieved
		if transaction.AllowWithdrawal() {

			err := DB.FinaliseWithdrawal(withdrawalRowId, CONFIRMED, completionProof)
			if err != nil {
				LogError("attestor", err)
				prepareRetryOrAbort(withdrawalRowId, errs)
			}
		} else {
			// when the received transaction is not allowed, we first check if the
			// transaction is in a final state which will not allow the withdrawal
			// and therefore the operation can be aborted, without further retries.
			if transaction.AbortWithdrawal() {
				err := DB.FinaliseWithdrawal(withdrawalRowId, ABORTED, completionProof)
				if err != nil {
					LogError("attestor", err)
					prepareRetryOrAbort(withdrawalRowId, errs)
					return
				}
			}
			prepareRetryOrAbort(withdrawalRowId, errs)
		}
		return
	}
	// when the transaction proof was not present (empty proof), retry.
	prepareRetryOrAbort(withdrawalRowId, errs)
}

// Checks wether the maximal amount of retries was already
// reached and the withdrawal operation shall be aborted or
// triggers the next retry by setting the last_retry_ts field
// which will trigger the stored procedure triggering the retry
// process. The retry counter of the retries is handled by the
// retrier logic and shall not be set here!
func prepareRetryOrAbort(
	withdrawalRowId int,
	errs chan error,
) {

	withdrawal, err := DB.GetWithdrawalById(withdrawalRowId)
	if err != nil {
		LogError("attestor", err)
		errs <- err
		return
	}

	if withdrawal.RetryCounter >= CONFIG.Server.MaxRetries {

		LogInfo("attestor", fmt.Sprintf("max retries for withdrawal with id=%d was reached. withdrawal is aborted.", withdrawal.WithdrawalId))
		err := DB.FinaliseWithdrawal(withdrawalRowId, ABORTED, make([]byte, 0))
		if err != nil {
			LogError("attestor", err)
		}
	} else {

		lastRetryTs := time.Now().Unix()
		err := DB.SetLastRetry(withdrawalRowId, lastRetryTs)
		if err != nil {
			LogError("attestor", err)
		}
	}

}