From 1338796029478c146349b8b7b19f0dcbdba6ee12 Mon Sep 17 00:00:00 2001 From: Patrick Deziel <42919891+pdeziel@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:45:33 -0500 Subject: [PATCH] Improve rVASP error handling (#102) --- lib/python/rvaspy/rvaspy/api_pb2.py | 30 ++-- pkg/rvasp/async.go | 12 +- pkg/rvasp/db/db.go | 20 ++- pkg/rvasp/pb/v1/api.pb.go | 66 ++++----- pkg/rvasp/rvasp.go | 207 +++++++++++++--------------- pkg/rvasp/testdata/ca.gz | Bin 0 -> 3959 bytes pkg/rvasp/trisa.go | 166 ++++++++++++++-------- pkg/rvasp/trisa_test.go | 39 +++--- proto/rvasp/v1/api.proto | 3 +- 9 files changed, 304 insertions(+), 239 deletions(-) create mode 100644 pkg/rvasp/testdata/ca.gz diff --git a/lib/python/rvaspy/rvaspy/api_pb2.py b/lib/python/rvaspy/rvaspy/api_pb2.py index eec2931..27d40e1 100644 --- a/lib/python/rvaspy/rvaspy/api_pb2.py +++ b/lib/python/rvaspy/rvaspy/api_pb2.py @@ -20,7 +20,7 @@ syntax='proto3', serialized_options=b'Z2github.com/trisacrypto/testnet/pkg/rvasp/pb/v1;api', create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\tapi.proto\x12\x08rvasp.v1\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"B\n\x07\x41\x63\x63ount\x12\x16\n\x0ewallet_address\x18\x01 \x01(\t\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x10\n\x08provider\x18\x03 \x01(\t\"\xce\x01\n\x0bTransaction\x12%\n\noriginator\x18\x01 \x01(\x0b\x32\x11.rvasp.v1.Account\x12&\n\x0b\x62\x65neficiary\x18\x02 \x01(\x0b\x32\x11.rvasp.v1.Account\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x02\x12\x11\n\ttimestamp\x18\x04 \x01(\t\x12\x10\n\x08\x65nvelope\x18\x05 \x01(\t\x12\x10\n\x08identity\x18\x06 \x01(\t\x12)\n\x05state\x18\x07 \x01(\x0e\x32\x1a.rvasp.v1.TransactionState\"\xad\x01\n\x0fTransferRequest\x12\x0f\n\x07\x61\x63\x63ount\x18\x01 \x01(\t\x12\x13\n\x0b\x62\x65neficiary\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x02\x12\x18\n\x10originating_vasp\x18\x04 \x01(\t\x12\x18\n\x10\x62\x65neficiary_vasp\x18\x05 \x01(\t\x12\x19\n\x11\x63heck_beneficiary\x18\x06 \x01(\x08\x12\x15\n\rexternal_demo\x18\x07 \x01(\x08\"[\n\rTransferReply\x12\x1e\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x0f.rvasp.v1.Error\x12*\n\x0btransaction\x18\x02 \x01(\x0b\x32\x15.rvasp.v1.Transaction\"Z\n\x0e\x41\x63\x63ountRequest\x12\x0f\n\x07\x61\x63\x63ount\x18\x01 \x01(\t\x12\x17\n\x0fno_transactions\x18\x02 \x01(\x08\x12\x0c\n\x04page\x18\x03 \x01(\r\x12\x10\n\x08per_page\x18\x04 \x01(\r\"\xc5\x01\n\x0c\x41\x63\x63ountReply\x12\x1e\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x0f.rvasp.v1.Error\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12\x16\n\x0ewallet_address\x18\x04 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\x05 \x01(\x02\x12\x11\n\tcompleted\x18\x06 \x01(\x04\x12\x0f\n\x07pending\x18\x07 \x01(\x04\x12+\n\x0ctransactions\x18\x08 \x03(\x0b\x32\x15.rvasp.v1.Transaction\"\xa9\x01\n\x07\x43ommand\x12\x1b\n\x04type\x18\x01 \x01(\x0e\x32\r.rvasp.v1.RPC\x12\n\n\x02id\x18\x02 \x01(\x04\x12\x0e\n\x06\x63lient\x18\x03 \x01(\t\x12-\n\x08transfer\x18\x0b \x01(\x0b\x32\x19.rvasp.v1.TransferRequestH\x00\x12+\n\x07\x61\x63\x63ount\x18\x0c \x01(\x0b\x32\x18.rvasp.v1.AccountRequestH\x00\x42\t\n\x07request\"\xe3\x01\n\x07Message\x12\x1b\n\x04type\x18\x01 \x01(\x0e\x32\r.rvasp.v1.RPC\x12\n\n\x02id\x18\x02 \x01(\x04\x12\x0e\n\x06update\x18\x03 \x01(\t\x12\x11\n\ttimestamp\x18\x04 \x01(\t\x12+\n\x08\x63\x61tegory\x18\x05 \x01(\x0e\x32\x19.rvasp.v1.MessageCategory\x12+\n\x08transfer\x18\x0b \x01(\x0b\x32\x17.rvasp.v1.TransferReplyH\x00\x12)\n\x07\x61\x63\x63ount\x18\x0c \x01(\x0b\x32\x16.rvasp.v1.AccountReplyH\x00\x42\x07\n\x05reply*\x8f\x01\n\x10TransactionState\x12\x0b\n\x07INVALID\x10\x00\x12\x0c\n\x08\x41WAITING\x10\x01\x12\x10\n\x0cPENDING_SENT\x10\x02\x12\x18\n\x14PENDING_ACKNOWLEDGED\x10\x03\x12\x0c\n\x08\x41\x43\x43\x45PTED\x10\x04\x12\n\n\x06\x46\x41ILED\x10\x05\x12\x0b\n\x07\x45XPIRED\x10\x06\x12\r\n\tCOMPLETED\x10\x07*+\n\x03RPC\x12\t\n\x05NORPC\x10\x00\x12\x0c\n\x08TRANSFER\x10\x01\x12\x0b\n\x07\x41\x43\x43OUNT\x10\x02*S\n\x0fMessageCategory\x12\n\n\x06LEDGER\x10\x00\x12\x0b\n\x07TRISADS\x10\x01\x12\x0c\n\x08TRISAP2P\x10\x02\x12\x0e\n\nBLOCKCHAIN\x10\x03\x12\t\n\x05\x45RROR\x10\x04\x32\x44\n\tTRISADemo\x12\x37\n\x0bLiveUpdates\x12\x11.rvasp.v1.Command\x1a\x11.rvasp.v1.Message(\x01\x30\x01\x32\x95\x01\n\x10TRISAIntegration\x12>\n\x08Transfer\x12\x19.rvasp.v1.TransferRequest\x1a\x17.rvasp.v1.TransferReply\x12\x41\n\rAccountStatus\x12\x18.rvasp.v1.AccountRequest\x1a\x16.rvasp.v1.AccountReplyB4Z2github.com/trisacrypto/testnet/pkg/rvasp/pb/v1;apib\x06proto3' + serialized_pb=b'\n\tapi.proto\x12\x08rvasp.v1\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"B\n\x07\x41\x63\x63ount\x12\x16\n\x0ewallet_address\x18\x01 \x01(\t\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x10\n\x08provider\x18\x03 \x01(\t\"\xce\x01\n\x0bTransaction\x12%\n\noriginator\x18\x01 \x01(\x0b\x32\x11.rvasp.v1.Account\x12&\n\x0b\x62\x65neficiary\x18\x02 \x01(\x0b\x32\x11.rvasp.v1.Account\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x02\x12\x11\n\ttimestamp\x18\x04 \x01(\t\x12\x10\n\x08\x65nvelope\x18\x05 \x01(\t\x12\x10\n\x08identity\x18\x06 \x01(\t\x12)\n\x05state\x18\x07 \x01(\x0e\x32\x1a.rvasp.v1.TransactionState\"\xad\x01\n\x0fTransferRequest\x12\x0f\n\x07\x61\x63\x63ount\x18\x01 \x01(\t\x12\x13\n\x0b\x62\x65neficiary\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x02\x12\x18\n\x10originating_vasp\x18\x04 \x01(\t\x12\x18\n\x10\x62\x65neficiary_vasp\x18\x05 \x01(\t\x12\x19\n\x11\x63heck_beneficiary\x18\x06 \x01(\x08\x12\x15\n\rexternal_demo\x18\x07 \x01(\x08\"[\n\rTransferReply\x12\x1e\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x0f.rvasp.v1.Error\x12*\n\x0btransaction\x18\x02 \x01(\x0b\x32\x15.rvasp.v1.Transaction\"Z\n\x0e\x41\x63\x63ountRequest\x12\x0f\n\x07\x61\x63\x63ount\x18\x01 \x01(\t\x12\x17\n\x0fno_transactions\x18\x02 \x01(\x08\x12\x0c\n\x04page\x18\x03 \x01(\r\x12\x10\n\x08per_page\x18\x04 \x01(\r\"\xc5\x01\n\x0c\x41\x63\x63ountReply\x12\x1e\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x0f.rvasp.v1.Error\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12\x16\n\x0ewallet_address\x18\x04 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\x05 \x01(\x02\x12\x11\n\tcompleted\x18\x06 \x01(\x04\x12\x0f\n\x07pending\x18\x07 \x01(\x04\x12+\n\x0ctransactions\x18\x08 \x03(\x0b\x32\x15.rvasp.v1.Transaction\"\xa9\x01\n\x07\x43ommand\x12\x1b\n\x04type\x18\x01 \x01(\x0e\x32\r.rvasp.v1.RPC\x12\n\n\x02id\x18\x02 \x01(\x04\x12\x0e\n\x06\x63lient\x18\x03 \x01(\t\x12-\n\x08transfer\x18\x0b \x01(\x0b\x32\x19.rvasp.v1.TransferRequestH\x00\x12+\n\x07\x61\x63\x63ount\x18\x0c \x01(\x0b\x32\x18.rvasp.v1.AccountRequestH\x00\x42\t\n\x07request\"\xe3\x01\n\x07Message\x12\x1b\n\x04type\x18\x01 \x01(\x0e\x32\r.rvasp.v1.RPC\x12\n\n\x02id\x18\x02 \x01(\x04\x12\x0e\n\x06update\x18\x03 \x01(\t\x12\x11\n\ttimestamp\x18\x04 \x01(\t\x12+\n\x08\x63\x61tegory\x18\x05 \x01(\x0e\x32\x19.rvasp.v1.MessageCategory\x12+\n\x08transfer\x18\x0b \x01(\x0b\x32\x17.rvasp.v1.TransferReplyH\x00\x12)\n\x07\x61\x63\x63ount\x18\x0c \x01(\x0b\x32\x16.rvasp.v1.AccountReplyH\x00\x42\x07\n\x05reply*\x9d\x01\n\x10TransactionState\x12\x0b\n\x07INVALID\x10\x00\x12\x0c\n\x08\x41WAITING\x10\x01\x12\x10\n\x0cPENDING_SENT\x10\x02\x12\x18\n\x14PENDING_ACKNOWLEDGED\x10\x03\x12\x0c\n\x08\x41\x43\x43\x45PTED\x10\x04\x12\n\n\x06\x46\x41ILED\x10\x05\x12\x0b\n\x07\x45XPIRED\x10\x06\x12\x0c\n\x08REJECTED\x10\x07\x12\r\n\tCOMPLETED\x10\x08*+\n\x03RPC\x12\t\n\x05NORPC\x10\x00\x12\x0c\n\x08TRANSFER\x10\x01\x12\x0b\n\x07\x41\x43\x43OUNT\x10\x02*S\n\x0fMessageCategory\x12\n\n\x06LEDGER\x10\x00\x12\x0b\n\x07TRISADS\x10\x01\x12\x0c\n\x08TRISAP2P\x10\x02\x12\x0e\n\nBLOCKCHAIN\x10\x03\x12\t\n\x05\x45RROR\x10\x04\x32\x44\n\tTRISADemo\x12\x37\n\x0bLiveUpdates\x12\x11.rvasp.v1.Command\x1a\x11.rvasp.v1.Message(\x01\x30\x01\x32\x95\x01\n\x10TRISAIntegration\x12>\n\x08Transfer\x12\x19.rvasp.v1.TransferRequest\x1a\x17.rvasp.v1.TransferReply\x12\x41\n\rAccountStatus\x12\x18.rvasp.v1.AccountRequest\x1a\x16.rvasp.v1.AccountReplyB4Z2github.com/trisacrypto/testnet/pkg/rvasp/pb/v1;apib\x06proto3' ) _TRANSACTIONSTATE = _descriptor.EnumDescriptor( @@ -66,7 +66,12 @@ type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='COMPLETED', index=7, number=7, + name='REJECTED', index=7, number=7, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='COMPLETED', index=8, number=8, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), @@ -74,7 +79,7 @@ containing_type=None, serialized_options=None, serialized_start=1304, - serialized_end=1447, + serialized_end=1461, ) _sym_db.RegisterEnumDescriptor(_TRANSACTIONSTATE) @@ -104,8 +109,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1449, - serialized_end=1492, + serialized_start=1463, + serialized_end=1506, ) _sym_db.RegisterEnumDescriptor(_RPC) @@ -145,8 +150,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1494, - serialized_end=1577, + serialized_start=1508, + serialized_end=1591, ) _sym_db.RegisterEnumDescriptor(_MESSAGECATEGORY) @@ -158,7 +163,8 @@ ACCEPTED = 4 FAILED = 5 EXPIRED = 6 -COMPLETED = 7 +REJECTED = 7 +COMPLETED = 8 NORPC = 0 TRANSFER = 1 ACCOUNT = 2 @@ -832,8 +838,8 @@ index=0, serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_start=1579, - serialized_end=1647, + serialized_start=1593, + serialized_end=1661, methods=[ _descriptor.MethodDescriptor( name='LiveUpdates', @@ -858,8 +864,8 @@ index=1, serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_start=1650, - serialized_end=1799, + serialized_start=1664, + serialized_end=1813, methods=[ _descriptor.MethodDescriptor( name='Transfer', diff --git a/pkg/rvasp/async.go b/pkg/rvasp/async.go index cf37b77..e3b0159 100644 --- a/pkg/rvasp/async.go +++ b/pkg/rvasp/async.go @@ -69,7 +69,9 @@ func (s *TRISA) handleAsync(ctx context.Context) { now := time.Now() txloop: - for _, tx := range transactions { + for _, transaction := range transactions { + tx := &transaction + // Verify pending transaction is old enough if now.Before(tx.NotBefore) { continue @@ -78,7 +80,7 @@ txloop: // Verify pending transaction has not expired if now.After(tx.NotAfter) { log.Info().Uint("id", tx.ID).Time("not_after", tx.NotAfter).Msg("transaction expired") - tx.State = pb.TransactionState_EXPIRED + tx.SetState(pb.TransactionState_EXPIRED) if err = s.parent.db.Save(&tx).Error; err != nil { log.Error().Err(err).Uint("id", tx.ID).Msg("could not save expired transaction") } @@ -86,13 +88,13 @@ txloop: } // Acknowledge the transaction with the originator - if err = s.acknowledgeTransaction(&tx); err != nil { + if err = s.acknowledgeTransaction(tx); err != nil { log.Warn().Err(err).Uint("id", tx.ID).Msg("could not acknowledge transaction") - tx.State = pb.TransactionState_FAILED + tx.SetState(pb.TransactionState_FAILED) } // Save the updated transaction in the database - if err = s.parent.db.Save(&tx).Error; err != nil { + if err = s.parent.db.Save(tx).Error; err != nil { log.Error().Err(err).Uint("id", tx.ID).Msg("could not save completed transaction") } } diff --git a/pkg/rvasp/db/db.go b/pkg/rvasp/db/db.go index 60fcb90..669a13e 100644 --- a/pkg/rvasp/db/db.go +++ b/pkg/rvasp/db/db.go @@ -111,13 +111,13 @@ func (d *DB) LookupWallet(address string) *gorm.DB { // MakeTransaction returns a new Transaction from the originator and beneficiary // wallet addresses. Note: this does not store the transaction in the database to allow // the caller to modify the transaction fields before storage. -func (d *DB) MakeTransaction(originator string, beneficiary string) (Transaction, error) { +func (d *DB) MakeTransaction(originator string, beneficiary string) (*Transaction, error) { var originatorIdentity, beneficiaryIdentity Identity // Fetch originator identity record if err := d.LookupIdentity(originator).FirstOrInit(&originatorIdentity, Identity{}).Error; err != nil { log.Error().Err(err).Msg("could not lookup originator identity") - return Transaction{}, status.Errorf(codes.FailedPrecondition, "could not lookup originator identity: %s", err) + return nil, status.Errorf(codes.FailedPrecondition, "could not lookup originator identity: %s", err) } // If originator identity does not exist then create it @@ -127,14 +127,14 @@ func (d *DB) MakeTransaction(originator string, beneficiary string) (Transaction if err := d.Create(&originatorIdentity).Error; err != nil { log.Error().Err(err).Msg("could not save originator identity") - return Transaction{}, status.Errorf(codes.FailedPrecondition, "could not save originator identity: %s", err) + return nil, status.Errorf(codes.FailedPrecondition, "could not save originator identity: %s", err) } } // Fetch beneficiary identity record if err := d.LookupIdentity(beneficiary).FirstOrInit(&beneficiaryIdentity, Identity{}).Error; err != nil { log.Error().Err(err).Msg("could not lookup beneficiary identity") - return Transaction{}, status.Errorf(codes.FailedPrecondition, "could not lookup beneficiary identity: %s", err) + return nil, status.Errorf(codes.FailedPrecondition, "could not lookup beneficiary identity: %s", err) } // If the beneficiary identity does not exist then create it @@ -144,15 +144,16 @@ func (d *DB) MakeTransaction(originator string, beneficiary string) (Transaction if err := d.Create(&beneficiaryIdentity).Error; err != nil { log.Error().Err(err).Msg("could not save beneficiary identity") - return Transaction{}, status.Errorf(codes.FailedPrecondition, "could not save beneficiary identity: %s", err) + return nil, status.Errorf(codes.FailedPrecondition, "could not save beneficiary identity: %s", err) } } - return Transaction{ + return &Transaction{ Envelope: uuid.New().String(), Originator: originatorIdentity, Beneficiary: beneficiaryIdentity, State: pb.TransactionState_AWAITING, + StateString: pb.TransactionState_AWAITING.String(), Timestamp: time.Now(), Vasp: d.vasp, }, nil @@ -260,6 +261,7 @@ type Transaction struct { Amount decimal.Decimal `gorm:"type:decimal(15,8)"` Debit bool `gorm:"not null"` State pb.TransactionState `gorm:"not null;default:0"` + StateString string `gorm:"column:state_string;not null"` Timestamp time.Time `gorm:"not null"` NotBefore time.Time `gorm:"not null"` NotAfter time.Time `gorm:"not null"` @@ -274,6 +276,12 @@ func (Transaction) TableName() string { return "transactions" } +// Set the transaction state to a new value +func (t *Transaction) SetState(state pb.TransactionState) { + t.State = state + t.StateString = state.String() +} + // Identity holds raw data for an originator or a beneficiary that was sent as // part of the transaction process. This should not be stored in the wallet since the // wallet is a representation of the local VASPs knowledge about customers and bercause diff --git a/pkg/rvasp/pb/v1/api.pb.go b/pkg/rvasp/pb/v1/api.pb.go index c6fb010..1b3fd4f 100644 --- a/pkg/rvasp/pb/v1/api.pb.go +++ b/pkg/rvasp/pb/v1/api.pb.go @@ -31,7 +31,8 @@ const ( TransactionState_ACCEPTED TransactionState = 4 // (Async) Originator has received the transaction acknowledgement from the beneficiary TransactionState_FAILED TransactionState = 5 // The transaction has failed TransactionState_EXPIRED TransactionState = 6 // The asynchronous transaction has expired before completion - TransactionState_COMPLETED TransactionState = 7 // The transaction is completed + TransactionState_REJECTED TransactionState = 7 // The transaction has been rejected + TransactionState_COMPLETED TransactionState = 8 // The transaction is completed ) // Enum value maps for TransactionState. @@ -44,7 +45,8 @@ var ( 4: "ACCEPTED", 5: "FAILED", 6: "EXPIRED", - 7: "COMPLETED", + 7: "REJECTED", + 8: "COMPLETED", } TransactionState_value = map[string]int32{ "INVALID": 0, @@ -54,7 +56,8 @@ var ( "ACCEPTED": 4, "FAILED": 5, "EXPIRED": 6, - "COMPLETED": 7, + "REJECTED": 7, + "COMPLETED": 8, } ) @@ -1092,7 +1095,7 @@ var file_rvasp_v1_api_proto_rawDesc = []byte{ 0x72, 0x12, 0x32, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x07, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x2a, 0x8f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x2a, 0x9d, 0x01, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x57, 0x41, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, @@ -1101,33 +1104,34 @@ var file_rvasp_v1_api_proto_rawDesc = []byte{ 0x4f, 0x57, 0x4c, 0x45, 0x44, 0x47, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, - 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x07, - 0x2a, 0x2b, 0x0a, 0x03, 0x52, 0x50, 0x43, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x4f, 0x52, 0x50, 0x43, - 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x01, - 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x2a, 0x53, 0x0a, - 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, - 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x44, 0x47, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, - 0x54, 0x52, 0x49, 0x53, 0x41, 0x44, 0x53, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x52, 0x49, - 0x53, 0x41, 0x50, 0x32, 0x50, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x4c, 0x4f, 0x43, 0x4b, - 0x43, 0x48, 0x41, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x04, 0x32, 0x44, 0x0a, 0x09, 0x54, 0x52, 0x49, 0x53, 0x41, 0x44, 0x65, 0x6d, 0x6f, 0x12, - 0x37, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x11, - 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x1a, 0x11, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x32, 0x95, 0x01, 0x0a, 0x10, 0x54, 0x52, 0x49, - 0x53, 0x41, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, - 0x08, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x72, 0x76, 0x61, 0x73, - 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x41, 0x0a, - 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, - 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, - 0x72, 0x69, 0x73, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x6e, - 0x65, 0x74, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2f, 0x70, 0x62, 0x2f, - 0x76, 0x31, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x07, 0x12, + 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x08, 0x2a, 0x2b, + 0x0a, 0x03, 0x52, 0x50, 0x43, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x4f, 0x52, 0x50, 0x43, 0x10, 0x00, + 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0b, + 0x0a, 0x07, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x2a, 0x53, 0x0a, 0x0f, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x0a, + 0x0a, 0x06, 0x4c, 0x45, 0x44, 0x47, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x52, + 0x49, 0x53, 0x41, 0x44, 0x53, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x52, 0x49, 0x53, 0x41, + 0x50, 0x32, 0x50, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x43, 0x48, + 0x41, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, + 0x32, 0x44, 0x0a, 0x09, 0x54, 0x52, 0x49, 0x53, 0x41, 0x44, 0x65, 0x6d, 0x6f, 0x12, 0x37, 0x0a, + 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x11, 0x2e, 0x72, + 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, + 0x11, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x32, 0x95, 0x01, 0x0a, 0x10, 0x54, 0x52, 0x49, 0x53, 0x41, + 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x08, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x41, 0x0a, 0x0d, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x2e, 0x72, + 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x34, + 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x69, + 0x73, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x6e, 0x65, 0x74, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x76, 0x61, 0x73, 0x70, 0x2f, 0x70, 0x62, 0x2f, 0x76, 0x31, + 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/rvasp/rvasp.go b/pkg/rvasp/rvasp.go index dfda3b5..bd4bb5c 100644 --- a/pkg/rvasp/rvasp.go +++ b/pkg/rvasp/rvasp.go @@ -166,10 +166,10 @@ func (s *Server) Shutdown() (err error) { // Transfer accepts a transfer request from a beneficiary and begins the InterVASP // protocol to perform identity verification prior to establishing the transaction in // the blockchain between crypto wallet addresses. -func (s *Server) Transfer(ctx context.Context, req *pb.TransferRequest) (*pb.TransferReply, error) { +func (s *Server) Transfer(ctx context.Context, req *pb.TransferRequest) (reply *pb.TransferReply, err error) { // Get originator account and confirm it belongs to this RVASP var account db.Account - if err := s.db.LookupAccount(req.Account).First(&account).Error; err != nil { + if err = s.db.LookupAccount(req.Account).First(&account).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { log.Info().Str("account", req.Account).Msg("not found") return nil, status.Error(codes.NotFound, "account not found") @@ -180,7 +180,7 @@ func (s *Server) Transfer(ctx context.Context, req *pb.TransferRequest) (*pb.Tra // Retrieve the policy for the originator account var wallet db.Wallet - if err := s.db.LookupWallet(account.WalletAddress).First(&wallet).Error; err != nil { + if err = s.db.LookupWallet(account.WalletAddress).First(&wallet).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { log.Info().Str("wallet", account.WalletAddress).Msg("not found") return nil, status.Error(codes.NotFound, "wallet not found") @@ -189,25 +189,60 @@ func (s *Server) Transfer(ctx context.Context, req *pb.TransferRequest) (*pb.Tra return nil, status.Errorf(codes.FailedPrecondition, "could not lookup wallet: %s", err) } + // Fetch the beneficiary Wallet + var beneficiary *db.Wallet + if beneficiary, err = s.fetchBeneficiaryWallet(req); err != nil { + return nil, err + } + + // Create a new Transaction + var xfer *db.Transaction + if xfer, err = s.db.MakeTransaction(account.WalletAddress, beneficiary.Address); err != nil { + return nil, err + } + xfer.Account = account + xfer.Amount = decimal.NewFromFloat32(req.Amount) + xfer.Debit = true + // Run the scenario for the wallet's configured policy + var transferError error policy := wallet.OriginatorPolicy log.Debug().Str("wallet", account.WalletAddress).Str("policy", string(policy)).Msg("initiating transfer") switch policy { case db.SendPartial: // Send a transfer request to the beneficiary containing partial beneficiary // identity information. - return s.sendTransfer(req, account, true) + transferError = s.sendTransfer(xfer, beneficiary, true) case db.SendFull: // Send a transfer request to the beneficiary containing full beneficiary // identity information. - return s.sendTransfer(req, account, false) + transferError = s.sendTransfer(xfer, beneficiary, false) case db.SendError: // Send a TRISA error to the beneficiary. - return s.sendError(req, account) + transferError = s.sendError(xfer, beneficiary) default: log.Error().Str("wallet", account.WalletAddress).Str("policy", string(policy)).Msg("unknown policy") return nil, status.Errorf(codes.FailedPrecondition, "unknown originator policy '%s' for wallet '%s'", policy, account.WalletAddress) } + + if transferError != nil { + log.Debug().Err(transferError).Msg("transfer failed") + xfer.SetState(pb.TransactionState_FAILED) + } + + // Return the transfer response + reply = &pb.TransferReply{ + Transaction: xfer.Proto(), + } + + // Save the updated transaction + // TODO: Clean up completed transactions in the database + if err = s.db.Save(xfer).Error; err != nil { + log.Error().Err(err).Msg("could not save transaction") + return nil, status.Errorf(codes.Internal, "could not save transaction: %s", err) + } + + return reply, transferError } // fetchBeneficiary fetches the beneficiary Wallet from the request. @@ -254,60 +289,45 @@ func (s *Server) fetchBeneficiaryWallet(req *pb.TransferRequest) (wallet *db.Wal // to the beneficiary. If partial is true, then the full beneficiary identity // information is not included in the payload. This function handles pending responses // from the beneficiary saving the transaction in an "await" state in the database. -func (s *Server) sendTransfer(req *pb.TransferRequest, account db.Account, partial bool) (rep *pb.TransferReply, err error) { - rep = &pb.TransferReply{} - - // Fetch the beneficiary Wallet - var beneficiary *db.Wallet - if beneficiary, err = s.fetchBeneficiaryWallet(req); err != nil { - return nil, err - } - +func (s *Server) sendTransfer(xfer *db.Transaction, beneficiary *db.Wallet, partial bool) (err error) { // Conduct a GDS lookup if necessary to get the endpoint var peer *peers.Peer if peer, err = s.peers.Search(beneficiary.Provider.Name); err != nil { log.Warn().Err(err).Msg("could not search peer from directory service") - return nil, status.Errorf(codes.FailedPrecondition, "could not search peer from directory service: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not search peer from directory service: %s", err) } // Ensure that the local RVASP has signing keys for the remote, otherwise perform key exchange var signKey *rsa.PublicKey if signKey, err = peer.ExchangeKeys(true); err != nil { log.Warn().Err(err).Msg("could not exchange keys with remote peer") - return nil, status.Errorf(codes.FailedPrecondition, "could not exchange keys with remote peer: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not exchange keys with remote peer: %s", err) } - // Create a new Transaction in the database - var xfer db.Transaction - if xfer, err = s.db.MakeTransaction(account.WalletAddress, beneficiary.Address); err != nil { - return nil, err - } - xfer.Account = account - xfer.Amount = decimal.NewFromFloat32(req.Amount) - xfer.Debit = true - - if err = s.db.Create(&xfer).Error; err != nil { + if err = s.db.Create(xfer).Error; err != nil { log.Error().Err(err).Msg("could not save pending transaction") - return nil, status.Errorf(codes.FailedPrecondition, "could not save pending transaction: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not save pending transaction: %s", err) } // Save the pending transaction on the account // TODO: remove pending transactions - account.Pending++ - if err = s.db.Save(&account).Error; err != nil { + xfer.Account.Pending++ + if err = s.db.Save(&xfer.Account).Error; err != nil { log.Error().Err(err).Msg("could not save originator account") - return nil, status.Errorf(codes.FailedPrecondition, "could not save originator account: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not save originator account: %s", err) } // Create an identity and transaction payload for TRISA exchange transaction := &generic.Transaction{ - Originator: account.WalletAddress, + Originator: xfer.Account.WalletAddress, Beneficiary: beneficiary.Address, - Amount: float64(req.Amount), Network: "TestNet", Timestamp: xfer.Timestamp.Format(time.RFC3339), } + // Set the amount on the transaction payload + transaction.Amount, _ = xfer.Amount.Float64() + var beneficiaryAccount db.Account if partial { // If partial is specified then only populate the benefiiary address @@ -318,53 +338,64 @@ func (s *Server) sendTransfer(req *pb.TransferRequest, account db.Account, parti // If partial is false then retrieve the full beneficiary account if err = s.db.LookupAnyAccount(beneficiary.Address).First(&beneficiaryAccount).Error; err != nil { log.Warn().Err(err).Msg("could not lookup remote beneficiary account") - return nil, status.Errorf(codes.FailedPrecondition, "could not lookup remote beneficiary account: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not lookup remote beneficiary account: %s", err) } } var identity *ivms101.IdentityPayload - if identity, err = s.createIdentityPayload(account, beneficiaryAccount); err != nil { - return nil, err + if identity, err = s.createIdentityPayload(xfer.Account, beneficiaryAccount); err != nil { + return err } var payload *protocol.Payload if payload, err = createTransferPayload(identity, transaction); err != nil { log.Error().Err(err).Msg("could not create transfer payload") - return nil, status.Errorf(codes.Internal, "could not create transfer payload: %s", err) + return status.Errorf(codes.Internal, "could not create transfer payload: %s", err) } // Secure the envelope with the remote beneficiary's signing keys msg, _, err := envelope.Seal(payload, envelope.WithEnvelopeID(xfer.Envelope), envelope.WithRSAPublicKey(signKey)) if err != nil { log.Warn().Err(err).Msg("TRISA protocol error while sealing envelope") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) } // Conduct the TRISA transaction, handle errors and send back to user if msg, err = peer.Transfer(msg); err != nil { log.Warn().Err(err).Msg("could not perform TRISA exchange") - return nil, status.Errorf(codes.FailedPrecondition, "could not perform TRISA exchange: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not perform TRISA exchange: %s", err) + } + + // Check for TRISA rejection errors + reject, isErr := envelope.Check(msg) + if isErr { + if reject != nil { + xfer.SetState(pb.TransactionState_REJECTED) + return nil + } + log.Warn().Err(err).Msg("TRISA protocol error while checking envelope") + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) } // Open the response envelope with local private keys payload, _, err = envelope.Open(msg, envelope.WithRSAPrivateKey(s.trisa.sign)) if err != nil { log.Warn().Err(err).Msg("TRISA protocol error while opening envelope") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) } // Parse the response payload var pending *generic.Pending if identity, transaction, pending, err = parsePayload(payload, true); err != nil { log.Warn().Err(err).Msg("TRISA protocol error while parsing payload") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) } // Update the transaction with the identity information var data []byte if data, err = json.Marshal(identity); err != nil { log.Error().Err(err).Msg("could not marshal IVMS 101 identity") - return nil, status.Errorf(codes.Internal, "could not marshal IVMS 101 identity: %s", err) + return status.Errorf(codes.Internal, "could not marshal IVMS 101 identity: %s", err) } xfer.Identity = string(data) @@ -373,134 +404,99 @@ func (s *Server) sendTransfer(req *pb.TransferRequest, account db.Account, parti // Update the Transaction in the database with the pending timestamps if xfer.NotBefore, err = time.Parse(time.RFC3339, pending.ReplyNotBefore); err != nil { log.Warn().Err(err).Msg("TRISA protocol error: could not parse ReplyNotBefore timestamp") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: could not parse ReplyNotBefore timestamp in pending message: %s", err) + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: could not parse ReplyNotBefore timestamp in pending message: %s", err) } if xfer.NotAfter, err = time.Parse(time.RFC3339, pending.ReplyNotAfter); err != nil { log.Warn().Err(err).Msg("TRISA protocol error: could not parse ReplyNotAfter timestamp") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: could not parse ReplyNotAfter timestamp in pending message: %s", err) + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: could not parse ReplyNotAfter timestamp in pending message: %s", err) } } else if transaction != nil { if !partial { // Validate that the beneficiary identity matches the original request if identity.BeneficiaryVasp == nil || identity.BeneficiaryVasp.BeneficiaryVasp == nil { log.Warn().Msg("TRISA protocol error: missing beneficiary vasp identity") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: missing beneficiary vasp identity") + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: missing beneficiary vasp identity") } beneficiaryInfo := identity.Beneficiary if beneficiaryInfo == nil { log.Warn().Msg("TRISA protocol error: missing beneficiary identity") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: missing beneficiary identity") + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: missing beneficiary identity") } if len(beneficiaryInfo.BeneficiaryPersons) != 1 { log.Warn().Int("persons", len(beneficiaryInfo.BeneficiaryPersons)).Msg("TRISA protocol error: unexpected number of beneficiary persons") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: unexpected number of beneficiary persons") + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: unexpected number of beneficiary persons") } if len(beneficiaryInfo.AccountNumbers) != 1 { log.Warn().Int("accounts", len(beneficiaryInfo.AccountNumbers)).Msg("TRISA protocol error: unexpected number of beneficiary accounts") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: unexpected number of beneficiary accounts") + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: unexpected number of beneficiary accounts") } // TODO: Validate that the actual address was returned if beneficiaryInfo.AccountNumbers[0] == "" { log.Warn().Msg("TRISA protocol error: missing beneficiary address") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: missing beneficiary address") + return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: missing beneficiary address") } } // Update the account information - account.Pending-- - account.Completed++ - account.Balance = account.Balance.Sub(xfer.Amount) - if err = s.db.Save(&account).Error; err != nil { + xfer.Account.Pending-- + xfer.Account.Completed++ + xfer.Account.Balance = xfer.Account.Balance.Sub(xfer.Amount) + if err = s.db.Save(xfer.Account).Error; err != nil { log.Error().Err(err).Msg("could not save originator account") - return nil, status.Errorf(codes.Internal, "could not save originator account: %s", err) + return status.Errorf(codes.Internal, "could not save originator account: %s", err) } // This transaction is now complete - xfer.State = pb.TransactionState_COMPLETED + xfer.SetState(pb.TransactionState_COMPLETED) xfer.Timestamp, _ = time.Parse(time.RFC3339, transaction.Timestamp) } - // Save the updated transaction - // TODO: Clean up completed transactions in the database - if err = s.db.Save(&xfer).Error; err != nil { - log.Error().Err(err).Msg("could not save transaction") - return nil, status.Errorf(codes.Internal, "could not save transaction: %s", err) - } - - // Return the transfer response - rep.Transaction = xfer.Proto() - rep.Transaction.State = xfer.State - return rep, nil + return nil } // sendError sends a TRISA error to the beneficiary. -func (s *Server) sendError(req *pb.TransferRequest, account db.Account) (rep *pb.TransferReply, err error) { - rep = &pb.TransferReply{} - - // Fetch the beneficiary Wallet - var beneficiary *db.Wallet - if beneficiary, err = s.fetchBeneficiaryWallet(req); err != nil { - return nil, err - } - - // Create a new Transaction - var xfer db.Transaction - if xfer, err = s.db.MakeTransaction(account.WalletAddress, beneficiary.Address); err != nil { - return nil, err - } - xfer.Account = account - xfer.Amount = decimal.NewFromFloat32(req.Amount) - xfer.Debit = true - +func (s *Server) sendError(xfer *db.Transaction, beneficiary *db.Wallet) (err error) { // Conduct a GDS lookup if necessary to get the endpoint var peer *peers.Peer if peer, err = s.peers.Search(beneficiary.Provider.Name); err != nil { log.Warn().Err(err).Msg("could not search peer from directory service") - return nil, status.Errorf(codes.FailedPrecondition, "could not search peer from directory service: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not search peer from directory service: %s", err) } reject := protocol.Errorf(protocol.ComplianceCheckFail, "rVASP mock compliance check failed") var msg *protocol.SecureEnvelope if msg, err = envelope.Reject(reject); err != nil { log.Error().Err(err).Msg("could not create TRISA error envelope") - return nil, status.Errorf(codes.Internal, "could not create TRISA error envelope: %s", err) + return status.Errorf(codes.Internal, "could not create TRISA error envelope: %s", err) } // Conduct the TRISA transaction, handle errors and send back to user if msg, err = peer.Transfer(msg); err != nil { log.Warn().Err(err).Msg("could not perform TRISA exchange") - return nil, status.Errorf(codes.FailedPrecondition, "could not perform TRISA exchange: %s", err) + return status.Errorf(codes.FailedPrecondition, "could not perform TRISA exchange: %s", err) } // Check for the TRISA rejection error if state := envelope.Status(msg); state != envelope.Error { log.Warn().Uint("state", uint(state)).Msg("unexpected TRISA response, expected error envelope") - return nil, fmt.Errorf("expected TRISA rejection error, received envelope in state %d", state) + return fmt.Errorf("expected TRISA rejection error, received envelope in state %d", state) } - xfer.State = pb.TransactionState_FAILED + xfer.SetState(pb.TransactionState_REJECTED) - rep.Transaction = xfer.Proto() - return rep, nil + return nil } // respondAsync responds to a serviced transfer request from the beneficiary by // continuing or completing the asynchronous handshake. -func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, identity *ivms101.IdentityPayload, transaction *generic.Transaction, envelopeID string) (out *protocol.SecureEnvelope, err error) { +func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, identity *ivms101.IdentityPayload, transaction *generic.Transaction, xfer *db.Transaction) (out *protocol.SecureEnvelope, err error) { // Secure envelope was successfully received now := time.Now() - // Lookup the pending transaction in the database - var xfer db.Transaction - if err = s.db.LookupTransaction(envelopeID).First(&xfer).Error; err != nil { - log.Error().Err(err).Msg("could not find pending transaction") - return nil, protocol.Errorf(protocol.InternalError, "could not find pending transaction: %s", err) - } - // Verify that the transaction has not expired if now.Before(xfer.NotBefore) || now.After(xfer.NotAfter) { log.Debug().Time("not_before", xfer.NotBefore).Time("not_after", xfer.NotAfter).Msg("received expired async transaction") @@ -514,6 +510,7 @@ func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, ident if out, err = envelope.Reject(reject); err != nil { return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) } + xfer.SetState(pb.TransactionState_REJECTED) return out, nil } log.Warn().Err(err).Msg("TRISA protocol error while sealing envelope") @@ -595,7 +592,7 @@ func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, ident return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: could not parse ReplyNotAfter timestamp in pending message: %s", err) } - xfer.State = pb.TransactionState_ACCEPTED + xfer.SetState(pb.TransactionState_ACCEPTED) case pb.TransactionState_ACCEPTED: // The handshake is complete, finalize the transaction var account db.Account @@ -613,18 +610,12 @@ func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, ident log.Error().Err(err).Msg("could not save originator account") return nil, status.Errorf(codes.Internal, "could not save originator account: %s", err) } - xfer.State = pb.TransactionState_COMPLETED + xfer.SetState(pb.TransactionState_COMPLETED) default: log.Error().Str("state", xfer.State.String()).Msg("unepected transaction state") return nil, protocol.Errorf(protocol.ComplianceCheckFail, "unexpected transaction state: %s", xfer.State.String()) } - // Save the updated transaction - if err = s.db.Save(&xfer).Error; err != nil { - log.Error().Err(err).Msg("could not save transaction") - return nil, status.Errorf(codes.Internal, "could not save transaction: %s", err) - } - return out, nil } @@ -994,7 +985,7 @@ func (s *Server) handleTransaction(client string, req *pb.Command) (err error) { WalletAddress: transaction.Beneficiary, Vasp: s.vasp, } - xfer.State = pb.TransactionState_COMPLETED + xfer.SetState(pb.TransactionState_COMPLETED) xfer.Timestamp, _ = time.Parse(time.RFC3339, transaction.Timestamp) // Serialize the identity information as JSON data diff --git a/pkg/rvasp/testdata/ca.gz b/pkg/rvasp/testdata/ca.gz new file mode 100644 index 0000000000000000000000000000000000000000..efd091424597cea81f0fbd277e63231cde473ab5 GIT binary patch literal 3959 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