package handler import ( "encoding/binary" "errors" "time" "ripple/bug" "ripple/commands" "ripple/config" "ripple/crypto" "ripple/errmsgs" "ripple/lockstep" "ripple/services" "ripple/state" "ripple/transport" "ripple/types" ) var errInvalidStateTransition = errors.New("invalid state transition") type LockstepHandlers struct { lockstep *lockstep.Lockstep cptSender *transport.CounterpartTransport resilience *services.Resilience } func NewLockstepHandlers( lockstep *lockstep.Lockstep, sender *transport.CounterpartTransport, resilience *services.Resilience, ) *LockstepHandlers { return &LockstepHandlers{ lockstep: lockstep, cptSender: sender, resilience: resilience, } } func (h *LockstepHandlers) SetTrustlineInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 8 { return nil, errmsgs.ErrInvalidArgs } value := binary.BigEndian.Uint64(args[:8]) account := st.Storage.MustGetAccount(userID) account.TrustlineOut = value st.Storage.Accounts[userID] = account return nil, nil } func (h *LockstepHandlers) SetTrustlineExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 8 { return nil, errmsgs.ErrInvalidArgs } value := binary.BigEndian.Uint64(args[:8]) account := st.Storage.MustGetAccount(userID) account.TrustlineIn = value st.Storage.Accounts[userID] = account return nil, nil } func (h *LockstepHandlers) InitPaymentInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 48 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) if crypto.Sha256(st.Memory.Preimage[:]) != paymentID { return nil, nil } st.Storage.Preimage = st.Memory.Preimage return func() { h.lockstep.Enqueue(userID, types.Instruction{ Command: commands.LOCKSTEP_COMMIT_PAYMENT, Arguments: args, }) }, nil } func (h *LockstepHandlers) InitPaymentExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 48 { return nil, errmsgs.ErrInvalidArgs } return nil, nil } func (h *LockstepHandlers) CommitPaymentInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 48 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) amount := binary.BigEndian.Uint64(args[32:40]) tax := binary.BigEndian.Uint64(args[40:48]) if st.Storage.GetBandwidthOut(userID) < int64(amount+tax) { return nil, errInvalidStateTransition } { account := st.Storage.MustGetAccount(userID) account.Pending[paymentID] = struct{}{} st.Storage.Accounts[userID] = account } payment, ok := st.Storage.Payments[paymentID] if !ok { pf, ok := st.Memory.GetPathfinding(paymentID) if !ok { st.Storage.Payments[paymentID] = state.Payment{ Amount: amount, Tax: tax, Outgoing: userID, Status: state.Cancel, } return func() { h.lockstep.Enqueue(userID, types.Instruction{ Command: commands.LOCKSTEP_CANCEL_PAYMENT, Arguments: paymentID[:], }) }, nil } delete(st.Memory.Pathfinding, paymentID) st.Storage.Payments[paymentID] = state.Payment{ Amount: amount, Tax: tax, Outgoing: userID, Counterpart: pf.Counterpart, Timeout: time.Now().Add(config.Timeout).Unix(), } return nil, nil } payment.Outgoing = userID st.Storage.Payments[paymentID] = payment return nil, nil } func (h *LockstepHandlers) CommitPaymentExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 48 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) amount := binary.BigEndian.Uint64(args[32:40]) tax := binary.BigEndian.Uint64(args[40:48]) if st.Storage.GetBandwidthIn(userID) < int64(amount+tax) { return nil, errInvalidStateTransition } { account := st.Storage.MustGetAccount(userID) account.Pending[paymentID] = struct{}{} st.Storage.Accounts[userID] = account } pf, ok := st.Memory.GetPathfinding(paymentID) if !ok { st.Storage.Payments[paymentID] = state.Payment{ Amount: amount, Tax: tax, Incoming: userID, Status: state.Cancel, } return func() { h.lockstep.Enqueue(userID, types.Instruction{ Command: commands.LOCKSTEP_CANCEL_PAYMENT, Arguments: paymentID[:], }) }, nil } delete(st.Memory.Pathfinding, paymentID) if pf.Counterpart != (types.UserIdentifier{}) { st.Storage.Payments[paymentID] = state.Payment{ Amount: amount, Tax: tax, Incoming: userID, Counterpart: pf.Counterpart, Timeout: time.Now().Add(config.Timeout).Unix(), } return func() { h.cptSender.Send(types.Instruction{Command: commands.COUNTERPART_SEAL_PAYMENT}) }, nil } st.Storage.Payments[paymentID] = state.Payment{ Amount: amount, Tax: tax, Incoming: userID, Timeout: time.Now().Add(config.Timeout).Unix(), } return func() { h.lockstep.Enqueue(pf.Outgoing, types.Instruction{ Command: commands.LOCKSTEP_COMMIT_PAYMENT, Arguments: args, }) }, nil } func (h *LockstepHandlers) SealPaymentInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 32 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) payment, ok := st.Storage.Payments[paymentID] if !ok { panic(bug.BugStateViolated) } if !payment.Committed() || payment.Outgoing != userID { return nil, errInvalidStateTransition } if payment.Incoming == (types.UserIdentifier{}) { payment.Status = state.Sealed st.Storage.Payments[paymentID] = payment } return nil, nil } func (h *LockstepHandlers) SealPaymentExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 32 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) payment, ok := st.Storage.Payments[paymentID] if !ok { return nil, errInvalidStateTransition } if !payment.Committed() || payment.Incoming != userID { return nil, errInvalidStateTransition } payment.Status = state.Sealed st.Storage.Payments[paymentID] = payment if payment.Outgoing == (types.UserIdentifier{}) { return func() { h.lockstep.Enqueue(payment.Incoming, types.Instruction{ Command: commands.LOCKSTEP_FINALIZE_PAYMENT, Arguments: paymentID[:], }) }, nil } else { return func() { h.lockstep.Enqueue(payment.Outgoing, types.Instruction{ Command: commands.LOCKSTEP_SEAL_PAYMENT, Arguments: paymentID[:], }) }, nil } return nil, nil } func (h *LockstepHandlers) FinalizePaymentInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 64 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) var preimage [32]byte copy(preimage[:], args[32:64]) if crypto.Sha256(preimage[:]) != paymentID { return nil, errInvalidStateTransition } payment, ok := st.Storage.Payments[paymentID] if !ok { panic(bug.BugStateViolated) } if !payment.Sealed() || payment.Incoming != userID { return nil, errInvalidStateTransition } account := st.Storage.MustGetAccount(userID) if _, ok := account.Pending[paymentID]; !ok { panic(bug.BugStateViolated) } if payment.FeeIn > payment.Amount { panic(bug.BugStateViolated) } prevCreditline := account.Creditline account.IncreaseCreditline(int64(payment.Amount-payment.FeeIn), payment.GetTaxRate()) delete(account.Pending, paymentID) st.Storage.Accounts[userID] = account if prevCreditline <= 0 && account.Creditline > 0 { st.Memory.TaxBuffer[userID] = 0 } if payment.Counterpart != (types.UserIdentifier{}) { st.Storage.AddReceipt(state.Receipt{ Identifier: paymentID, Counterpart: payment.Counterpart, Amount: int64(payment.Amount), Timestamp: time.Now().Unix(), }) } delete(st.Storage.Payments, paymentID) return func() { h.resilience.Redistribute(userID, payment.Tax, payment.GetTaxRate()) }, nil } func (h *LockstepHandlers) FinalizePaymentExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 64 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) var preimage [32]byte copy(preimage[:], args[32:64]) if crypto.Sha256(preimage[:]) != paymentID { return nil, errInvalidStateTransition } payment, ok := st.Storage.Payments[paymentID] if !ok || !payment.Sealed() || payment.Outgoing != userID { return nil, errInvalidStateTransition } acc := st.Storage.MustGetAccount(userID) if _, ok := acc.Pending[paymentID]; !ok { panic(bug.BugStateViolated) } if payment.FeeOut > payment.Amount { panic(bug.BugStateViolated) } acc.DecreaseCreditline(int64(payment.Amount-payment.FeeOut), payment.GetTaxRate()) delete(acc.Pending, paymentID) st.Storage.Accounts[userID] = acc if payment.Counterpart != (types.UserIdentifier{}) { st.Storage.AddReceipt(state.Receipt{ Identifier: paymentID, Counterpart: payment.Counterpart, Amount: -int64(payment.Amount), Timestamp: time.Now().Unix(), }) delete(st.Storage.Payments, paymentID) return nil, nil } payment.Preimage = preimage payment.Outgoing = types.UserIdentifier{} st.Storage.Payments[paymentID] = payment return func() { h.lockstep.Enqueue(payment.Incoming, types.Instruction{ Command: commands.LOCKSTEP_FINALIZE_PAYMENT, Arguments: paymentID[:], }) }, nil } func (h *LockstepHandlers) CancelPaymentInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 32 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) payment, ok := st.Storage.Payments[paymentID] if !ok || !payment.Cancelled() { panic(bug.BugStateViolated) } if payment.Outgoing == userID { if len(args) < 64 { return nil, errmsgs.ErrInvalidArgs } var preimage [32]byte copy(preimage[:], args[32:64]) if crypto.Sha256(preimage[:]) != paymentID { return nil, errInvalidStateTransition } } else if payment.Incoming != userID { return nil, errInvalidStateTransition } account := st.Storage.MustGetAccount(userID) if _, ok := account.Pending[paymentID]; !ok { panic(bug.BugStateViolated) } delete(account.Pending, paymentID) st.Storage.Accounts[userID] = account delete(st.Storage.Payments, paymentID) return nil, nil } func (h *LockstepHandlers) CancelPaymentExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 32 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) payment, ok := st.Storage.Payments[paymentID] if !ok || payment.Sealed() { return nil, errInvalidStateTransition } if payment.Incoming == userID { if len(args) < 64 { return nil, errmsgs.ErrInvalidArgs } var preimage [32]byte copy(preimage[:], args[32:64]) if crypto.Sha256(preimage[:]) != paymentID { return nil, errInvalidStateTransition } payment.Preimage = preimage } else if payment.Outgoing != userID { return nil, errInvalidStateTransition } account := st.Storage.MustGetAccount(userID) if _, ok := account.Pending[paymentID]; !ok { panic(bug.BugStateViolated) } delete(account.Pending, paymentID) st.Storage.Accounts[userID] = account if payment.Incoming == userID { if payment.Outgoing == (types.UserIdentifier{}) { delete(st.Storage.Payments, paymentID) return nil, nil } payment.Incoming = types.UserIdentifier{} } else { if payment.Incoming == (types.UserIdentifier{}) { delete(st.Storage.Payments, paymentID) return nil, nil } payment.Outgoing = types.UserIdentifier{} } payment.Status = state.Cancel st.Storage.Payments[paymentID] = payment return func() { h.lockstep.Enqueue(payment.Outgoing, types.Instruction{ Command: commands.LOCKSTEP_CANCEL_PAYMENT, Arguments: args, }) }, nil } func (h *LockstepHandlers) PaymentFeeInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 40 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) fee := binary.BigEndian.Uint64(args[32:40]) payment, ok := st.Storage.Payments[paymentID] if !ok { return nil, errInvalidStateTransition } if userID != payment.Incoming && userID != payment.Outgoing { return nil, errInvalidStateTransition } if payment.Incoming == userID { if fee + payment.FeeIn >= payment.Amount { fee = payment.Amount - payment.FeeIn acc := st.Storage.MustGetAccount(payment.Incoming) delete(acc.Pending, paymentID) st.Storage.Accounts[payment.Incoming] = acc payment.Incoming = (types.UserIdentifier{}) } acc := st.Storage.MustGetAccount(userID) prevCreditline := acc.Creditline acc.IncreaseCreditline(int64(fee), payment.GetTaxRate()) st.Storage.Accounts[userID] = acc if prevCreditline <= 0 && acc.Creditline > 0 { st.Memory.TaxBuffer[userID] = 0 } if payment.Incoming == (types.UserIdentifier{}) && payment.Outgoing == (types.UserIdentifier{}) { delete(st.Storage.Payments, paymentID) return nil, nil } payment.FeeIn += fee st.Storage.Payments[paymentID] = payment return nil, nil } if payment.Outgoing == userID { if fee + payment.FeeOut < payment.Amount { payment.FeeOut += fee } else { payment.FeeOut = payment.Amount acc := st.Storage.MustGetAccount(payment.Outgoing) delete(acc.Pending, paymentID) st.Storage.Accounts[payment.Outgoing] = acc payment.Outgoing = (types.UserIdentifier{}) if payment.Incoming == (types.UserIdentifier{}) { delete(st.Storage.Payments, paymentID) return nil, nil } } st.Storage.Payments[paymentID] = payment return nil, nil } return nil, nil } func (h *LockstepHandlers) PaymentFeeExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 40 { return nil, errmsgs.ErrInvalidArgs } var paymentID [32]byte copy(paymentID[:], args[:32]) fee := binary.BigEndian.Uint64(args[32:40]) payment, ok := st.Storage.Payments[paymentID] if !ok { return nil, errInvalidStateTransition } if userID != payment.Incoming && userID != payment.Outgoing { return nil, errInvalidStateTransition } if userID == payment.Incoming { if fee+payment.FeeIn < payment.Amount { payment.FeeIn += fee } else { payment.FeeIn = payment.Amount acc := st.Storage.MustGetAccount(payment.Incoming) delete(acc.Pending, paymentID) st.Storage.Accounts[payment.Incoming] = acc payment.Incoming = (types.UserIdentifier{}) } st.Storage.Payments[paymentID] = payment if payment.Outgoing == (types.UserIdentifier{}) { if payment.Incoming == (types.UserIdentifier{}) { delete(st.Storage.Payments, paymentID) } return nil, nil } if time.Now().Unix() < payment.Timeout || payment.FeeIn > payment.CalculateFee() { return nil, nil } return func() { h.lockstep.Enqueue(payment.Outgoing, types.Instruction{ Command: commands.LOCKSTEP_PAYMENT_FEE, Arguments: args, }) }, nil } if userID == payment.Outgoing { if fee + payment.FeeOut >= payment.Amount { fee = payment.Amount - payment.FeeOut acc := st.Storage.MustGetAccount(payment.Outgoing) delete(acc.Pending, paymentID) st.Storage.Accounts[payment.Outgoing] = acc payment.Outgoing = (types.UserIdentifier{}) } acc := st.Storage.MustGetAccount(userID) acc.DecreaseCreditline(int64(fee), payment.GetTaxRate()) st.Storage.Accounts[userID] = acc payment.FeeOut += fee st.Storage.Payments[paymentID] = payment if payment.Incoming == (types.UserIdentifier{}) { if payment.Outgoing == (types.UserIdentifier{}) { delete(st.Storage.Payments, paymentID) } return nil, nil } if time.Now().Unix() < payment.Timeout || payment.FeeOut > payment.CalculateFee() { return nil, nil } return func() { h.lockstep.Enqueue(payment.Incoming, types.Instruction{ Command: commands.LOCKSTEP_PAYMENT_FEE, Arguments: args, }) }, nil } return nil, nil } func (h *LockstepHandlers) SwarmRedistributionInternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 8 { return nil, errmsgs.ErrInvalidArgs } amount := int64(binary.BigEndian.Uint64(args[:8])) acc := st.Storage.MustGetAccount(userID) if amount > acc.Creditline { return nil, errmsgs.ErrInvalidArgs } acc.Creditline -= amount st.Storage.Accounts[userID] = acc return nil, nil } func (h *LockstepHandlers) SwarmRedistributionExternal(st *state.State, userID types.UserIdentifier, args []byte) (func(), error) { if len(args) < 8 { return nil, errmsgs.ErrInvalidArgs } amount := binary.BigEndian.Uint64(args[:8]) acc := st.Storage.MustGetAccount(userID) if int64(amount) > -acc.Creditline { return nil, errmsgs.ErrInvalidArgs } acc.Creditline += int64(amount) st.Storage.Accounts[userID] = acc return func() { h.resilience.Redistribute(userID, amount, acc.Width) }, nil }