-
Notifications
You must be signed in to change notification settings - Fork 50
/
state_service.go
158 lines (133 loc) · 6.16 KB
/
state_service.go
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
154
155
156
157
158
//go:generate protoc -I . ./state_service.proto --go-grpc_out=. --go_out=.
package escrow
import (
"bytes"
"errors"
"fmt"
"math/big"
"github.com/singnet/snet-daemon/v5/authutils"
"github.com/singnet/snet-daemon/v5/blockchain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"go.uber.org/zap"
"golang.org/x/net/context"
)
// PaymentChannelStateService is an implementation of PaymentChannelStateServiceServer gRPC interface
type PaymentChannelStateService struct {
channelService PaymentChannelService
paymentStorage *PaymentStorage
mpeAddress func() (address common.Address)
compareWithLatestBlockNumber func(*big.Int) error
}
func (service *PaymentChannelStateService) mustEmbedUnimplementedPaymentChannelStateServiceServer() {
//TODO implement me
panic("implement me")
}
type BlockChainDisabledStateService struct {
}
func (service *BlockChainDisabledStateService) mustEmbedUnimplementedPaymentChannelStateServiceServer() {
//TODO implement me
panic("implement me")
}
func (service *BlockChainDisabledStateService) GetChannelState(context context.Context, request *ChannelStateRequest) (reply *ChannelStateReply, err error) {
return &ChannelStateReply{}, nil
}
// verifies whether storage channel nonce is equal to blockchain nonce or not
func (service *PaymentChannelStateService) StorageNonceMatchesWithBlockchainNonce(storageChannel *PaymentChannelData) (equal bool, err error) {
h := service.channelService
blockchainChannel, ok, err := h.PaymentChannelFromBlockChain(&PaymentChannelKey{ID: storageChannel.ChannelID})
if err != nil {
return false, errors.New("channel error:" + err.Error())
}
if !ok {
return false, errors.New("unable to read channel details from blockchain.")
}
return storageChannel.Nonce.Cmp(blockchainChannel.Nonce) == 0, nil
}
// NewPaymentChannelStateService returns new instance of PaymentChannelStateService
func NewPaymentChannelStateService(channelService PaymentChannelService, paymentStorage *PaymentStorage, metaData *blockchain.ServiceMetadata) *PaymentChannelStateService {
return &PaymentChannelStateService{
channelService: channelService,
paymentStorage: paymentStorage,
mpeAddress: func() common.Address { return metaData.GetMpeAddress() },
compareWithLatestBlockNumber: authutils.CompareWithLatestBlockNumber,
}
}
// GetChannelState returns the latest state of the channel which id is passed
// in request. To authenticate sender request should also contain correct
// signature of the channel id.
/*Simple case current_nonce == blockchain_nonce
unspent_amount = blockchain_value - current_signed_amount
Complex case current_nonce != blockchain_nonce
Taking into account our assumptions, we know that current_nonce = blockchain_nonce + 1.
unspent_amount = blockchain_value - oldnonce_signed_amount - current_signed_amount
It should be noted that in this case the server could send us smaller old nonce_signed_amount (not the actually last one which was used for channelClaim).
In this case, the server can only make us believe that we have more money in the channel then we actually have.
That means that one possible attack via unspent_amount is to make us believe that we have less tokens than we truly have,
and therefore reject future calls (or force us to call channelAddFunds).*/
func (service *PaymentChannelStateService) GetChannelState(context context.Context, request *ChannelStateRequest) (reply *ChannelStateReply, err error) {
zap.L().Debug("GetChannelState called",
zap.Any("context", context),
zap.Any("request", request))
channelID := bytesToBigInt(request.GetChannelId())
// signature verification
message := bytes.Join([][]byte{
[]byte("__get_channel_state"),
service.mpeAddress().Bytes(),
bigIntToBytes(channelID),
math.U256Bytes(big.NewInt(int64(request.CurrentBlock))),
}, nil)
signature := request.GetSignature()
sender, err := authutils.GetSignerAddressFromMessage(message, signature)
if err != nil {
return nil, errors.New("incorrect signature")
}
channel, ok, err := service.channelService.PaymentChannel(&PaymentChannelKey{ID: channelID})
if err != nil {
return nil, errors.New("channel error:" + err.Error())
}
if !ok {
return nil, fmt.Errorf("channel is not found, channelId: %v", channelID)
}
if channel.Signer != *sender && *sender != channel.Sender && *sender != channel.Recipient {
return nil, errors.New("only channel signer/sender/receiver can get latest channel state")
}
if err := service.compareWithLatestBlockNumber(big.NewInt(int64(request.CurrentBlock))); err != nil {
return nil, err
}
// check if nonce matches with blockchain or not
nonceEqual, err := service.StorageNonceMatchesWithBlockchainNonce(channel)
if err != nil {
zap.L().Info("payment data not available in payment storage.", zap.Error(err))
return nil, err
} else if !nonceEqual {
// check for payments in the payment storage with current nonce - 1, this will happen cli has issues in claiming process
paymentID := PaymentID(channel.ChannelID, (&big.Int{}).Sub(channel.Nonce, big.NewInt(1)))
payment, ok, err := service.paymentStorage.Get(paymentID)
if err != nil {
zap.L().Error("Error trying unable to extract old payment from storage", zap.Error(err))
return nil, err
}
if !ok {
zap.L().Error("old payment is not found in storage, nevertheless local channel nonce is not equal to the blockchain one", zap.Any("ChannelID", channelID))
return nil, errors.New("channel has different nonce in local storage and blockchain and old payment is not found in storage")
}
return &ChannelStateReply{
CurrentNonce: bigIntToBytes(channel.Nonce),
CurrentSignedAmount: bigIntToBytes(channel.AuthorizedAmount),
CurrentSignature: channel.Signature,
OldNonceSignedAmount: bigIntToBytes(payment.Amount),
OldNonceSignature: payment.Signature,
}, nil
}
if channel.Signature == nil {
return &ChannelStateReply{
CurrentNonce: bigIntToBytes(channel.Nonce),
}, nil
}
return &ChannelStateReply{
CurrentNonce: bigIntToBytes(channel.Nonce),
CurrentSignedAmount: bigIntToBytes(channel.AuthorizedAmount),
CurrentSignature: channel.Signature,
}, nil
}