diff --git a/lib/python/rvaspy/rvaspy/api_pb2.py b/lib/python/rvaspy/rvaspy/api_pb2.py index 27d40e1..f9743c1 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*\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' + 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*\xd5\x01\n\x10TransactionState\x12\x0b\n\x07INVALID\x10\x00\x12\x12\n\x0e\x41WAITING_REPLY\x10\x01\x12\x10\n\x0cPENDING_SENT\x10\x02\x12\x1a\n\x16\x41WAITING_FULL_TRANSFER\x10\x03\x12\x14\n\x10PENDING_RECEIVED\x10\x04\x12\x18\n\x14PENDING_ACKNOWLEDGED\x10\x05\x12\x0c\n\x08\x41\x43\x43\x45PTED\x10\x06\x12\n\n\x06\x46\x41ILED\x10\x07\x12\x0b\n\x07\x45XPIRED\x10\x08\x12\x0c\n\x08REJECTED\x10\t\x12\r\n\tCOMPLETED\x10\n*+\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( @@ -36,7 +36,7 @@ type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='AWAITING', index=1, number=1, + name='AWAITING_REPLY', index=1, number=1, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), @@ -46,32 +46,42 @@ type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='PENDING_ACKNOWLEDGED', index=3, number=3, + name='AWAITING_FULL_TRANSFER', index=3, number=3, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='ACCEPTED', index=4, number=4, + name='PENDING_RECEIVED', index=4, number=4, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='FAILED', index=5, number=5, + name='PENDING_ACKNOWLEDGED', index=5, number=5, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='EXPIRED', index=6, number=6, + name='ACCEPTED', index=6, number=6, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='REJECTED', index=7, number=7, + name='FAILED', index=7, number=7, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( - name='COMPLETED', index=8, number=8, + name='EXPIRED', index=8, number=8, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='REJECTED', index=9, number=9, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='COMPLETED', index=10, number=10, serialized_options=None, type=None, create_key=_descriptor._internal_create_key), @@ -79,7 +89,7 @@ containing_type=None, serialized_options=None, serialized_start=1304, - serialized_end=1461, + serialized_end=1517, ) _sym_db.RegisterEnumDescriptor(_TRANSACTIONSTATE) @@ -109,8 +119,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1463, - serialized_end=1506, + serialized_start=1519, + serialized_end=1562, ) _sym_db.RegisterEnumDescriptor(_RPC) @@ -150,21 +160,23 @@ ], containing_type=None, serialized_options=None, - serialized_start=1508, - serialized_end=1591, + serialized_start=1564, + serialized_end=1647, ) _sym_db.RegisterEnumDescriptor(_MESSAGECATEGORY) MessageCategory = enum_type_wrapper.EnumTypeWrapper(_MESSAGECATEGORY) INVALID = 0 -AWAITING = 1 +AWAITING_REPLY = 1 PENDING_SENT = 2 -PENDING_ACKNOWLEDGED = 3 -ACCEPTED = 4 -FAILED = 5 -EXPIRED = 6 -REJECTED = 7 -COMPLETED = 8 +AWAITING_FULL_TRANSFER = 3 +PENDING_RECEIVED = 4 +PENDING_ACKNOWLEDGED = 5 +ACCEPTED = 6 +FAILED = 7 +EXPIRED = 8 +REJECTED = 9 +COMPLETED = 10 NORPC = 0 TRANSFER = 1 ACCOUNT = 2 @@ -838,8 +850,8 @@ index=0, serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_start=1593, - serialized_end=1661, + serialized_start=1649, + serialized_end=1717, methods=[ _descriptor.MethodDescriptor( name='LiveUpdates', @@ -864,8 +876,8 @@ index=1, serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_start=1664, - serialized_end=1813, + serialized_start=1720, + serialized_end=1869, methods=[ _descriptor.MethodDescriptor( name='Transfer', diff --git a/pkg/rvasp/async.go b/pkg/rvasp/async.go index e3b0159..8df02d2 100644 --- a/pkg/rvasp/async.go +++ b/pkg/rvasp/async.go @@ -87,10 +87,23 @@ txloop: continue txloop } - // Acknowledge the transaction with the originator - if err = s.acknowledgeTransaction(tx); err != nil { - log.Warn().Err(err).Uint("id", tx.ID).Msg("could not acknowledge transaction") - tx.SetState(pb.TransactionState_FAILED) + switch tx.State { + case pb.TransactionState_PENDING_SENT, pb.TransactionState_PENDING_ACKNOWLEDGED: + // We are the beneficiary, so acknowledge the pending transaction with the + // originator + if err = s.acknowledgeTransaction(tx); err != nil { + log.Warn().Err(err).Uint("id", tx.ID).Msg("could not acknowledge transaction") + tx.SetState(pb.TransactionState_FAILED) + } + case pb.TransactionState_PENDING_RECEIVED: + // We are the originator, so send a new transfer to the beneficiary to + // continue the async handshake + if err = s.parent.continueAsync(tx); err != nil { + log.Warn().Err(err).Uint("id", tx.ID).Msg("could not send transaction") + tx.SetState(pb.TransactionState_FAILED) + } + default: + log.Error().Uint("id", tx.ID).Str("state", tx.StateString).Msg("unexpected transaction state") } // Save the updated transaction in the database diff --git a/pkg/rvasp/db/db.go b/pkg/rvasp/db/db.go index 669a13e..6e9f9fd 100644 --- a/pkg/rvasp/db/db.go +++ b/pkg/rvasp/db/db.go @@ -95,7 +95,7 @@ func (d *DB) LookupIdentity(walletAddress string) *gorm.DB { // LookupPending returns the pending transactions. func (d *DB) LookupPending() *gorm.DB { - return d.Query().Where("state in (?, ?)", pb.TransactionState_PENDING_SENT, pb.TransactionState_PENDING_ACKNOWLEDGED) + return d.Query().Where("state in (?, ?, ?)", pb.TransactionState_PENDING_SENT, pb.TransactionState_PENDING_RECEIVED, pb.TransactionState_PENDING_ACKNOWLEDGED) } // LookupTransaction by envelope ID. @@ -152,8 +152,8 @@ func (d *DB) MakeTransaction(originator string, beneficiary string) (*Transactio Envelope: uuid.New().String(), Originator: originatorIdentity, Beneficiary: beneficiaryIdentity, - State: pb.TransactionState_AWAITING, - StateString: pb.TransactionState_AWAITING.String(), + State: pb.TransactionState_AWAITING_REPLY, + StateString: pb.TransactionState_AWAITING_REPLY.String(), Timestamp: time.Now(), Vasp: d.vasp, }, nil diff --git a/pkg/rvasp/db/db_test.go b/pkg/rvasp/db/db_test.go index d773c90..a334be6 100644 --- a/pkg/rvasp/db/db_test.go +++ b/pkg/rvasp/db/db_test.go @@ -183,8 +183,8 @@ func (s *dbTestSuite) TestLookupPending() { id := s.db.GetVASP().ID // Transaction lookups should be limited to the configured VASP - query := regexp.QuoteMeta(`SELECT * FROM "transactions" WHERE vasp_id = $1 AND state in ($2, $3)`) - s.mock.ExpectQuery(query).WithArgs(id, pb.TransactionState_PENDING_SENT, pb.TransactionState_PENDING_ACKNOWLEDGED).WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id)) + query := regexp.QuoteMeta(`SELECT * FROM "transactions" WHERE vasp_id = $1 AND state in ($2, $3, $4)`) + s.mock.ExpectQuery(query).WithArgs(id, pb.TransactionState_PENDING_SENT, pb.TransactionState_PENDING_RECEIVED, pb.TransactionState_PENDING_ACKNOWLEDGED).WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id)) var transaction db.Transaction tx := s.db.LookupPending().First(&transaction) diff --git a/pkg/rvasp/pb/v1/api.pb.go b/pkg/rvasp/pb/v1/api.pb.go index 1b3fd4f..d93a4b3 100644 --- a/pkg/rvasp/pb/v1/api.pb.go +++ b/pkg/rvasp/pb/v1/api.pb.go @@ -24,40 +24,46 @@ const ( type TransactionState int32 const ( - TransactionState_INVALID TransactionState = 0 // Not a valid transaction state - TransactionState_AWAITING TransactionState = 1 // (Async) Originator is awaiting a response from the beneficiary - TransactionState_PENDING_SENT TransactionState = 2 // (Async) Beneficiary has sent a pending message to the originator - TransactionState_PENDING_ACKNOWLEDGED TransactionState = 3 // (Async) Beneficiary has acknowledged the originator transaction - 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_REJECTED TransactionState = 7 // The transaction has been rejected - TransactionState_COMPLETED TransactionState = 8 // The transaction is completed + TransactionState_INVALID TransactionState = 0 // Not a valid transaction state + TransactionState_AWAITING_REPLY TransactionState = 1 // (Async) Originator is awaiting a response from the beneficiary + TransactionState_PENDING_SENT TransactionState = 2 // (Async) Beneficiary has sent a pending message to the originator + TransactionState_AWAITING_FULL_TRANSFER TransactionState = 3 // (Async) Beneficiary is awaiting a complete transfer request from the originator + TransactionState_PENDING_RECEIVED TransactionState = 4 // (Async) Originator has received a pending message from the beneficiary + TransactionState_PENDING_ACKNOWLEDGED TransactionState = 5 // (Async) Beneficiary has acknowledged the originator transaction + TransactionState_ACCEPTED TransactionState = 6 // (Async) Originator has received the transaction acknowledgement from the beneficiary + TransactionState_FAILED TransactionState = 7 // The transaction has failed + TransactionState_EXPIRED TransactionState = 8 // The asynchronous transaction has expired before completion + TransactionState_REJECTED TransactionState = 9 // The transaction has been rejected + TransactionState_COMPLETED TransactionState = 10 // The transaction is completed ) // Enum value maps for TransactionState. var ( TransactionState_name = map[int32]string{ - 0: "INVALID", - 1: "AWAITING", - 2: "PENDING_SENT", - 3: "PENDING_ACKNOWLEDGED", - 4: "ACCEPTED", - 5: "FAILED", - 6: "EXPIRED", - 7: "REJECTED", - 8: "COMPLETED", + 0: "INVALID", + 1: "AWAITING_REPLY", + 2: "PENDING_SENT", + 3: "AWAITING_FULL_TRANSFER", + 4: "PENDING_RECEIVED", + 5: "PENDING_ACKNOWLEDGED", + 6: "ACCEPTED", + 7: "FAILED", + 8: "EXPIRED", + 9: "REJECTED", + 10: "COMPLETED", } TransactionState_value = map[string]int32{ - "INVALID": 0, - "AWAITING": 1, - "PENDING_SENT": 2, - "PENDING_ACKNOWLEDGED": 3, - "ACCEPTED": 4, - "FAILED": 5, - "EXPIRED": 6, - "REJECTED": 7, - "COMPLETED": 8, + "INVALID": 0, + "AWAITING_REPLY": 1, + "PENDING_SENT": 2, + "AWAITING_FULL_TRANSFER": 3, + "PENDING_RECEIVED": 4, + "PENDING_ACKNOWLEDGED": 5, + "ACCEPTED": 6, + "FAILED": 7, + "EXPIRED": 8, + "REJECTED": 9, + "COMPLETED": 10, } ) @@ -1095,43 +1101,47 @@ 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, 0x9d, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x2a, 0xd5, 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, - 0x0a, 0x0c, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x45, 0x4e, 0x54, 0x10, 0x02, - 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x43, 0x4b, 0x4e, - 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, 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, + 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x57, 0x41, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x50, + 0x4c, 0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, + 0x53, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x57, 0x41, 0x49, 0x54, 0x49, + 0x4e, 0x47, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, + 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, + 0x43, 0x45, 0x49, 0x56, 0x45, 0x44, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x4e, 0x44, + 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x43, 0x4b, 0x4e, 0x4f, 0x57, 0x4c, 0x45, 0x44, 0x47, 0x45, 0x44, + 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x06, + 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x07, 0x12, 0x0b, 0x0a, 0x07, + 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4a, + 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x09, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, + 0x45, 0x54, 0x45, 0x44, 0x10, 0x0a, 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 bd4bb5c..9800392 100644 --- a/pkg/rvasp/rvasp.go +++ b/pkg/rvasp/rvasp.go @@ -31,6 +31,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/anypb" "gorm.io/gorm" ) @@ -391,9 +392,9 @@ func (s *Server) sendTransfer(xfer *db.Transaction, beneficiary *db.Wallet, part return status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) } - // Update the transaction with the identity information + // Update the transaction record with the identity payload var data []byte - if data, err = json.Marshal(identity); err != nil { + if data, err = protojson.Marshal(identity); err != nil { log.Error().Err(err).Msg("could not marshal IVMS 101 identity") return status.Errorf(codes.Internal, "could not marshal IVMS 101 identity: %s", err) } @@ -499,10 +500,38 @@ func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, ident // 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") + log.Debug().Time("now", now).Time("not_before", xfer.NotBefore).Time("not_after", xfer.NotAfter).Str("id", xfer.Envelope).Msg("received expired async transaction") return nil, protocol.Errorf(protocol.ComplianceCheckFail, "received expired transaction") } + // Marshal the identity payload into the transaction record + var data []byte + if data, err = protojson.Marshal(identity); err != nil { + log.Error().Err(err).Msg("could not marshal identity payload") + return nil, status.Errorf(codes.Internal, "could not marshal identity payload: %s", err) + } + xfer.Identity = string(data) + + // Marshal the transaction payload into the transaction record + if data, err = protojson.Marshal(transaction); err != nil { + log.Error().Err(err).Msg("could not marshal transaction payload") + return nil, status.Errorf(codes.Internal, "could not marshal transaction payload: %s", err) + } + xfer.Transaction = string(data) + + // Fetch the beneficiary identity + if err = s.db.LookupIdentity(transaction.Beneficiary).First(&xfer.Beneficiary).Error; err != nil { + log.Error().Err(err).Str("wallet_address", transaction.Beneficiary).Msg("could not lookup beneficiary identity") + return nil, status.Errorf(codes.Internal, "could not lookup beneficiary identity: %s", err) + } + + // Save the peer name so we can access it later + xfer.Beneficiary.Provider = peer.String() + if err = s.db.Save(&xfer.Beneficiary).Error; err != nil { + log.Error().Err(err).Msg("could not save beneficiary identity") + return nil, status.Errorf(codes.Internal, "could not save beneficiary identity: %s", err) + } + // Create the response envelope out, reject, err := envelope.Seal(payload, envelope.WithRSAPublicKey(peer.SigningKey())) if err != nil { @@ -519,80 +548,9 @@ func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, ident // Check the transaction state switch xfer.State { - case pb.TransactionState_AWAITING: - // Fill the transaction with a new TxID to continue the handshake - transaction.Txid = uuid.New().String() - if payload, err = createTransferPayload(identity, transaction); err != nil { - log.Error().Err(err).Msg("could not create transfer payload") - return nil, protocol.Errorf(protocol.InternalError, "could not create transfer payload: %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) - } - - // 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) - } - - // 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) - } - - // 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) - } - - // Parse the response payload - var pending *generic.Pending - if identity, _, 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) - } - - if pending == nil { - log.Warn().Msg("TRISA protocol error: expected pending response") - return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: expected pending response") - } - - // 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) - } - xfer.Identity = string(data) - - // Update the transaction with the new generic.Transaction - if data, err = json.Marshal(transaction); err != nil { - log.Error().Err(err).Msg("could not marshal generic.Transaction") - return nil, status.Errorf(codes.Internal, "could not marshal generic.Transaction: %s", err) - } - xfer.Transaction = string(data) - - // 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) - } - - 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) - } - - xfer.SetState(pb.TransactionState_ACCEPTED) + case pb.TransactionState_AWAITING_REPLY: + // Mark the transaction as pending for the async routine + xfer.SetState(pb.TransactionState_PENDING_RECEIVED) case pb.TransactionState_ACCEPTED: // The handshake is complete, finalize the transaction var account db.Account @@ -619,6 +577,114 @@ func (s *Server) respondAsync(peer *peers.Peer, payload *protocol.Payload, ident return out, nil } +// continueAsync continues an asynchronous transaction by sending a new transfer to the +// beneficiary with a populated TxID. +func (s *Server) continueAsync(xfer *db.Transaction) (err error) { + // Unmarshal the transaction payload from the transaction record + transaction := &generic.Transaction{} + if err = protojson.Unmarshal([]byte(xfer.Transaction), transaction); err != nil { + log.Error().Err(err).Msg("could not unmarshal transaction payload") + return fmt.Errorf("could not unmarshal transaction payload: %s", err) + } + + // Unmarshal the identity payload from the identity record + identity := &ivms101.IdentityPayload{} + if err = protojson.Unmarshal([]byte(xfer.Identity), identity); err != nil { + log.Error().Err(err).Msg("could not unmarshal identity payload") + return fmt.Errorf("could not unmarshal identity payload: %s", err) + } + + // Fetch the beneficiary name from the database + var beneficiary *db.Identity + if beneficiary, err = xfer.GetBeneficiary(s.db); err != nil { + log.Error().Err(err).Msg("could not fetch beneficiary address") + return fmt.Errorf("could not fetch beneficiary address") + } + + // Fill the transaction with a new TxID to continue the handshake + var payload *protocol.Payload + transaction.Txid = uuid.New().String() + if payload, err = createTransferPayload(identity, transaction); err != nil { + log.Error().Err(err).Msg("could not create transfer payload") + return fmt.Errorf("could not create transfer payload: %s", err) + } + + // Conduct a GDS lookup if necessary to get the endpoint + var peer *peers.Peer + if peer, err = s.peers.Search(beneficiary.Provider); err != nil { + log.Error().Err(err).Msg("could not search peer from directory service") + return fmt.Errorf("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 fmt.Errorf("could not exchange keys with remote peer: %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 fmt.Errorf("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 fmt.Errorf("could not perform TRISA exchange: %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 fmt.Errorf("TRISA protocol error: %s", err) + } + + // Parse the response payload + var pending *generic.Pending + if identity, _, pending, err = parsePayload(payload, true); err != nil { + log.Warn().Err(err).Msg("TRISA protocol error while parsing payload") + return fmt.Errorf("TRISA protocol error: %s", err) + } + + if pending == nil { + log.Warn().Msg("TRISA protocol error: expected pending response") + return fmt.Errorf("TRISA protocol error: expected pending response") + } + + // Update the transaction with the identity information + var data []byte + if data, err = protojson.Marshal(identity); err != nil { + log.Error().Err(err).Msg("could not marshal IVMS 101 identity") + return fmt.Errorf("could not marshal IVMS 101 identity: %s", err) + } + xfer.Identity = string(data) + + // Update the transaction with the new generic.Transaction + if data, err = protojson.Marshal(transaction); err != nil { + log.Error().Err(err).Msg("could not marshal generic.Transaction") + return fmt.Errorf("could not marshal generic.Transaction: %s", err) + } + xfer.Transaction = string(data) + + // 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 fmt.Errorf("TRISA protocol error: could not parse ReplyNotBefore timestamp: %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 fmt.Errorf("TRISA protocol error: could not parse ReplyNotAfter timestamp: %s", err) + } + + xfer.SetState(pb.TransactionState_ACCEPTED) + return nil +} + // AccountStatus is a demo RPC to allow demo clients to fetch their recent transactions. func (s *Server) AccountStatus(ctx context.Context, req *pb.AccountRequest) (rep *pb.AccountReply, err error) { rep = &pb.AccountReply{} @@ -845,7 +911,7 @@ func (s *Server) handleTransaction(client string, req *pb.Command) (err error) { Account: account, Amount: decimal.NewFromFloat32(transfer.Amount), Debit: true, - State: pb.TransactionState_AWAITING, + State: pb.TransactionState_AWAITING_REPLY, Vasp: s.vasp, } diff --git a/pkg/rvasp/trisa.go b/pkg/rvasp/trisa.go index 4cd33e9..6bf01d1 100644 --- a/pkg/rvasp/trisa.go +++ b/pkg/rvasp/trisa.go @@ -316,13 +316,6 @@ func (s *TRISA) handleTransaction(ctx context.Context, peer *peers.Peer, in *pro // For async transactions the originator receives a transfer request from the // beneficiary, so call the originator handler to continue the transaction. if proto.Equal(localIdentity, identity.OriginatingVasp.OriginatingVasp) { - // We need the remote peer's endpoint in order to initiate async transactions - // with the beneficiary. - if peer, err = s.parent.peers.Search(peer.String()); err != nil { - log.Warn().Err(err).Msg("could not lookup remote peer") - return nil, protocol.Errorf(protocol.InternalError, "could not lookup remote peer") - } - // Lookup the pending transaction in the database xfer := &db.Transaction{} if err = s.parent.db.LookupTransaction(in.Id).First(xfer).Error; err != nil { @@ -557,13 +550,6 @@ func (s *TRISA) respondTransfer(in *protocol.SecureEnvelope, peer *peers.Peer, i func (s *TRISA) respondPending(in *protocol.SecureEnvelope, peer *peers.Peer, identity *ivms101.IdentityPayload, transaction *generic.Transaction, xfer *db.Transaction, account db.Account) (out *protocol.SecureEnvelope, err error) { now := time.Now() - // Update the transaction state - if xfer.State == pb.TransactionState_AWAITING { - xfer.SetState(pb.TransactionState_PENDING_SENT) - } else { - xfer.SetState(pb.TransactionState_PENDING_ACKNOWLEDGED) - } - xfer.NotBefore = now.Add(s.parent.conf.AsyncNotBefore) xfer.NotAfter = now.Add(s.parent.conf.AsyncNotAfter) @@ -634,6 +620,13 @@ func (s *TRISA) respondPending(in *protocol.SecureEnvelope, peer *peers.Peer, id return nil, status.Errorf(codes.FailedPrecondition, "TRISA protocol error: %s", err) } + // Mark the transaction as pending for the async routine + if xfer.State == pb.TransactionState_AWAITING_REPLY { + xfer.SetState(pb.TransactionState_PENDING_SENT) + } else { + xfer.SetState(pb.TransactionState_PENDING_ACKNOWLEDGED) + } + return out, nil } @@ -738,7 +731,7 @@ func (s *TRISA) sendAsync(tx *db.Transaction) (err error) { switch tx.State { case pb.TransactionState_PENDING_SENT: // The first handshake is complete so move the transaction to the next state - tx.SetState(pb.TransactionState_PENDING_ACKNOWLEDGED) + tx.SetState(pb.TransactionState_AWAITING_FULL_TRANSFER) case pb.TransactionState_PENDING_ACKNOWLEDGED: // This is a complete transaction so update the database var account *db.Account diff --git a/proto/rvasp/v1/api.proto b/proto/rvasp/v1/api.proto index c138c26..9f98990 100644 --- a/proto/rvasp/v1/api.proto +++ b/proto/rvasp/v1/api.proto @@ -52,14 +52,16 @@ message Transaction { // Describes the current state of a transaction. enum TransactionState { INVALID = 0; // Not a valid transaction state - AWAITING = 1; // (Async) Originator is awaiting a response from the beneficiary + AWAITING_REPLY = 1; // (Async) Originator is awaiting a response from the beneficiary PENDING_SENT = 2; // (Async) Beneficiary has sent a pending message to the originator - PENDING_ACKNOWLEDGED = 3; // (Async) Beneficiary has acknowledged the originator transaction - ACCEPTED = 4; // (Async) Originator has received the transaction acknowledgement from the beneficiary - FAILED = 5; // The transaction has failed - EXPIRED = 6; // The asynchronous transaction has expired before completion - REJECTED = 7; // The transaction has been rejected - COMPLETED = 8; // The transaction is completed + AWAITING_FULL_TRANSFER = 3; // (Async) Beneficiary is awaiting a complete transfer request from the originator + PENDING_RECEIVED = 4; // (Async) Originator has received a pending message from the beneficiary + PENDING_ACKNOWLEDGED = 5; // (Async) Beneficiary has acknowledged the originator transaction + ACCEPTED = 6; // (Async) Originator has received the transaction acknowledgement from the beneficiary + FAILED = 7; // The transaction has failed + EXPIRED = 8; // The asynchronous transaction has expired before completion + REJECTED = 9; // The transaction has been rejected + COMPLETED = 10; // The transaction is completed } // Initiates a transfer from the specified account to the specified wallet address or