mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
318 lines
7.2 KiB
Go
318 lines
7.2 KiB
Go
package ledger
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"math/big"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/formancehq/stack/libs/go-libs/time"
|
|
|
|
"github.com/formancehq/stack/libs/go-libs/metadata"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type LogType int16
|
|
|
|
const (
|
|
// TODO(gfyrag): Create dedicated log type for account and metadata
|
|
SetMetadataLogType LogType = iota // "SET_METADATA"
|
|
NewTransactionLogType // "NEW_TRANSACTION"
|
|
RevertedTransactionLogType // "REVERTED_TRANSACTION"
|
|
DeleteMetadataLogType
|
|
)
|
|
|
|
func (l LogType) String() string {
|
|
switch l {
|
|
case SetMetadataLogType:
|
|
return "SET_METADATA"
|
|
case NewTransactionLogType:
|
|
return "NEW_TRANSACTION"
|
|
case RevertedTransactionLogType:
|
|
return "REVERTED_TRANSACTION"
|
|
case DeleteMetadataLogType:
|
|
return "DELETE_METADATA"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func LogTypeFromString(logType string) LogType {
|
|
switch logType {
|
|
case "SET_METADATA":
|
|
return SetMetadataLogType
|
|
case "NEW_TRANSACTION":
|
|
return NewTransactionLogType
|
|
case "REVERTED_TRANSACTION":
|
|
return RevertedTransactionLogType
|
|
case "DELETE_METADATA":
|
|
return DeleteMetadataLogType
|
|
}
|
|
|
|
panic(errors.New("invalid log type"))
|
|
}
|
|
|
|
// Needed in order to keep the compatibility with the openapi response for
|
|
// ListLogs.
|
|
func (lt LogType) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(lt.String())
|
|
}
|
|
|
|
func (lt *LogType) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
|
|
*lt = LogTypeFromString(s)
|
|
|
|
return nil
|
|
}
|
|
|
|
type ChainedLogWithContext struct {
|
|
ChainedLog
|
|
Context context.Context
|
|
}
|
|
|
|
type ChainedLog struct {
|
|
Log
|
|
ID *big.Int `json:"id"`
|
|
Hash []byte `json:"hash"`
|
|
}
|
|
|
|
func (l *ChainedLog) WithID(id uint64) *ChainedLog {
|
|
l.ID = big.NewInt(int64(id))
|
|
return l
|
|
}
|
|
|
|
func (l *ChainedLog) UnmarshalJSON(data []byte) error {
|
|
type auxLog ChainedLog
|
|
type log struct {
|
|
auxLog
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
rawLog := log{}
|
|
if err := json.Unmarshal(data, &rawLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
var err error
|
|
rawLog.auxLog.Data, err = HydrateLog(rawLog.Type, rawLog.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*l = ChainedLog(rawLog.auxLog)
|
|
return err
|
|
}
|
|
|
|
func (l *ChainedLog) ComputeHash(previous *ChainedLog) {
|
|
digest := sha256.New()
|
|
enc := json.NewEncoder(digest)
|
|
|
|
if previous != nil {
|
|
if err := enc.Encode(previous.Hash); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
if err := enc.Encode(l); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
l.Hash = digest.Sum(nil)
|
|
}
|
|
|
|
type Log struct {
|
|
Type LogType `json:"type"`
|
|
Data any `json:"data"`
|
|
Date time.Time `json:"date"`
|
|
IdempotencyKey string `json:"idempotencyKey"`
|
|
}
|
|
|
|
func (l *Log) WithDate(date time.Time) *Log {
|
|
l.Date = date
|
|
return l
|
|
}
|
|
|
|
func (l *Log) WithIdempotencyKey(key string) *Log {
|
|
l.IdempotencyKey = key
|
|
return l
|
|
}
|
|
|
|
func (l *Log) ChainLog(previous *ChainedLog) *ChainedLog {
|
|
ret := &ChainedLog{
|
|
Log: *l,
|
|
ID: big.NewInt(0),
|
|
}
|
|
ret.ComputeHash(previous)
|
|
if previous != nil {
|
|
ret.ID = ret.ID.Add(previous.ID, big.NewInt(1))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
type AccountMetadata map[string]metadata.Metadata
|
|
|
|
type NewTransactionLogPayload struct {
|
|
Transaction *Transaction `json:"transaction"`
|
|
AccountMetadata AccountMetadata `json:"accountMetadata"`
|
|
}
|
|
|
|
func NewTransactionLogWithDate(tx *Transaction, accountMetadata map[string]metadata.Metadata, time time.Time) *Log {
|
|
// Since the id is unique and the hash is a hash of the previous log, they
|
|
// will be filled at insertion time during the batch process.
|
|
return &Log{
|
|
Type: NewTransactionLogType,
|
|
Date: time,
|
|
Data: NewTransactionLogPayload{
|
|
Transaction: tx,
|
|
AccountMetadata: accountMetadata,
|
|
},
|
|
}
|
|
}
|
|
|
|
func NewTransactionLog(tx *Transaction, accountMetadata map[string]metadata.Metadata) *Log {
|
|
return NewTransactionLogWithDate(tx, accountMetadata, time.Now())
|
|
}
|
|
|
|
type SetMetadataLogPayload struct {
|
|
TargetType string `json:"targetType"`
|
|
TargetID any `json:"targetId"`
|
|
Metadata metadata.Metadata `json:"metadata"`
|
|
}
|
|
|
|
func (s *SetMetadataLogPayload) UnmarshalJSON(data []byte) error {
|
|
type X struct {
|
|
TargetType string `json:"targetType"`
|
|
TargetID json.RawMessage `json:"targetId"`
|
|
Metadata metadata.Metadata `json:"metadata"`
|
|
}
|
|
x := X{}
|
|
err := json.Unmarshal(data, &x)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var id interface{}
|
|
switch strings.ToUpper(x.TargetType) {
|
|
case strings.ToUpper(MetaTargetTypeAccount):
|
|
id = ""
|
|
err = json.Unmarshal(x.TargetID, &id)
|
|
case strings.ToUpper(MetaTargetTypeTransaction):
|
|
id, err = strconv.ParseUint(string(x.TargetID), 10, 64)
|
|
default:
|
|
panic("unknown type")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*s = SetMetadataLogPayload{
|
|
TargetType: x.TargetType,
|
|
TargetID: id,
|
|
Metadata: x.Metadata,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewSetMetadataLog(at time.Time, metadata SetMetadataLogPayload) *Log {
|
|
// Since the id is unique and the hash is a hash of the previous log, they
|
|
// will be filled at insertion time during the batch process.
|
|
return &Log{
|
|
Type: SetMetadataLogType,
|
|
Date: at,
|
|
Data: metadata,
|
|
}
|
|
}
|
|
|
|
type DeleteMetadataLogPayload struct {
|
|
TargetType string `json:"targetType"`
|
|
TargetID any `json:"targetId"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
func NewDeleteMetadataLog(at time.Time, payload DeleteMetadataLogPayload) *Log {
|
|
// Since the id is unique and the hash is a hash of the previous log, they
|
|
// will be filled at insertion time during the batch process.
|
|
return &Log{
|
|
Type: DeleteMetadataLogType,
|
|
Date: at,
|
|
Data: payload,
|
|
}
|
|
}
|
|
|
|
func NewSetMetadataOnAccountLog(at time.Time, account string, metadata metadata.Metadata) *Log {
|
|
return &Log{
|
|
Type: SetMetadataLogType,
|
|
Date: at,
|
|
Data: SetMetadataLogPayload{
|
|
TargetType: MetaTargetTypeAccount,
|
|
TargetID: account,
|
|
Metadata: metadata,
|
|
},
|
|
}
|
|
}
|
|
|
|
func NewSetMetadataOnTransactionLog(at time.Time, txID *big.Int, metadata metadata.Metadata) *Log {
|
|
return &Log{
|
|
Type: SetMetadataLogType,
|
|
Date: at,
|
|
Data: SetMetadataLogPayload{
|
|
TargetType: MetaTargetTypeTransaction,
|
|
TargetID: txID,
|
|
Metadata: metadata,
|
|
},
|
|
}
|
|
}
|
|
|
|
type RevertedTransactionLogPayload struct {
|
|
RevertedTransactionID *big.Int `json:"revertedTransactionID"`
|
|
RevertTransaction *Transaction `json:"transaction"`
|
|
}
|
|
|
|
func NewRevertedTransactionLog(at time.Time, revertedTxID *big.Int, tx *Transaction) *Log {
|
|
return &Log{
|
|
Type: RevertedTransactionLogType,
|
|
Date: at,
|
|
Data: RevertedTransactionLogPayload{
|
|
RevertedTransactionID: revertedTxID,
|
|
RevertTransaction: tx,
|
|
},
|
|
}
|
|
}
|
|
|
|
func HydrateLog(_type LogType, data []byte) (any, error) {
|
|
var payload any
|
|
switch _type {
|
|
case NewTransactionLogType:
|
|
payload = &NewTransactionLogPayload{}
|
|
case SetMetadataLogType:
|
|
payload = &SetMetadataLogPayload{}
|
|
case RevertedTransactionLogType:
|
|
payload = &RevertedTransactionLogPayload{}
|
|
default:
|
|
panic("unknown type " + _type.String())
|
|
}
|
|
err := json.Unmarshal(data, &payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return reflect.ValueOf(payload).Elem().Interface(), nil
|
|
}
|
|
|
|
type Accounts map[string]Account
|
|
|
|
func ChainLogs(logs ...*Log) []*ChainedLog {
|
|
var previous *ChainedLog
|
|
ret := make([]*ChainedLog, 0)
|
|
for _, log := range logs {
|
|
next := log.ChainLog(previous)
|
|
ret = append(ret, next)
|
|
previous = next
|
|
}
|
|
return ret
|
|
}
|