Skip to content

Commit

Permalink
wallet: make event structs consistent across all apps
Browse files Browse the repository at this point in the history
  • Loading branch information
n8maninger committed Aug 9, 2024
1 parent becc186 commit b680c3b
Show file tree
Hide file tree
Showing 5 changed files with 1,064 additions and 425 deletions.
7 changes: 5 additions & 2 deletions testutil/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ func (et *ephemeralWalletUpdateTxn) WalletStateElements() (elements []types.Stat

func (et *ephemeralWalletUpdateTxn) UpdateWalletStateElements(elements []types.StateElement) error {
for _, se := range elements {
utxo := et.store.utxos[types.SiacoinOutputID(se.ID)]
utxo, ok := et.store.utxos[types.SiacoinOutputID(se.ID)]
if !ok {
panic(fmt.Sprintf("siacoin element %q does not exist", se.ID))
}
utxo.StateElement = se
et.store.utxos[types.SiacoinOutputID(se.ID)] = utxo
}
Expand All @@ -52,7 +55,7 @@ func (et *ephemeralWalletUpdateTxn) WalletApplyIndex(index types.ChainIndex, cre
// add siacoin elements
for _, se := range created {
if _, ok := et.store.utxos[types.SiacoinOutputID(se.ID)]; ok {
continue
panic("duplicate element")
}
et.store.utxos[types.SiacoinOutputID(se.ID)] = se
}
Expand Down
182 changes: 164 additions & 18 deletions wallet/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,17 @@ type (
SiacoinElement types.SiacoinElement `json:"siacoinElement"`
}

// An EventV1ContractResolution represents a file contract resolution from a v1
// An EventV1Transaction pairs a v1 transaction with its spent siacoin and
// siafund elements.
EventV1Transaction struct {
Transaction types.Transaction `json:"transaction"`
// v1 siacoin inputs do not describe the value of the spent utxo
SpentSiacoinElements []types.SiacoinElement `json:"spentSiacoinElements,omitempty"`
// v1 siafund inputs do not describe the value of the spent utxo
SpentSiafundElements []types.SiafundElement `json:"spentSiafundElements,omitempty"`
}

// An EventV1ContractResolution represents a file contract payout from a v1
// contract.
EventV1ContractResolution struct {
Parent types.FileContractElement `json:"parent"`
Expand All @@ -41,14 +51,11 @@ type (
// An EventV2ContractResolution represents a file contract payout from a v2
// contract.
EventV2ContractResolution struct {
types.V2FileContractResolution
SiacoinElement types.SiacoinElement `json:"siacoinElement"`
Missed bool `json:"missed"`
Resolution types.V2FileContractResolution `json:"resolution"`
SiacoinElement types.SiacoinElement `json:"siacoinElement"`
Missed bool `json:"missed"`
}

// EventV1Transaction is a transaction event that includes the transaction
EventV1Transaction types.Transaction

// EventV2Transaction is a transaction event that includes the transaction
EventV2Transaction types.V2Transaction

Expand All @@ -62,12 +69,11 @@ type (
Event struct {
ID types.Hash256 `json:"id"`
Index types.ChainIndex `json:"index"`
Inflow types.Currency `json:"inflow"`
Outflow types.Currency `json:"outflow"`
Type string `json:"type"`
Data EventData `json:"data"`
MaturityHeight uint64 `json:"maturityHeight"`
Timestamp time.Time `json:"timestamp"`
Relevant []types.Address `json:"relevant,omitempty"`
}
)

Expand All @@ -77,30 +83,170 @@ func (EventV1ContractResolution) isEvent() bool { return true }
func (EventV2Transaction) isEvent() bool { return true }
func (EventV2ContractResolution) isEvent() bool { return true }

// SiacoinOutflow calculates the sum of Siacoins that were spent by relevant
// addresses
func (e *Event) SiacoinOutflow() types.Currency {
relevant := make(map[types.Address]bool)
for _, addr := range e.Relevant {
relevant[addr] = true
}

switch data := e.Data.(type) {
case EventPayout, EventV1ContractResolution, EventV2ContractResolution:
// payout events cannot have outflows
return types.ZeroCurrency
case EventV1Transaction:
var inflow types.Currency
for _, se := range data.SpentSiacoinElements {
if !relevant[se.SiacoinOutput.Address] {
continue
}
inflow = inflow.Add(se.SiacoinOutput.Value)
}
return inflow
case EventV2Transaction:
var inflow types.Currency
for _, se := range data.SiacoinInputs {
if !relevant[se.Parent.SiacoinOutput.Address] {
continue
}
inflow = inflow.Add(se.Parent.SiacoinOutput.Value)
}
return inflow
default:
panic(fmt.Errorf("unknown event type: %T", e.Data))
}
}

// SiacoinInflow calculates the sum of Siacoins that were received by relevant
// addresses
func (e *Event) SiacoinInflow() types.Currency {
relevant := make(map[types.Address]bool)
for _, addr := range e.Relevant {
relevant[addr] = true
}

switch data := e.Data.(type) {
case EventPayout:
return data.SiacoinElement.SiacoinOutput.Value
case EventV1ContractResolution:
return e.Data.(EventV1ContractResolution).SiacoinElement.SiacoinOutput.Value
case EventV2ContractResolution:
return e.Data.(EventV2ContractResolution).SiacoinElement.SiacoinOutput.Value
case EventV1Transaction:
var inflow types.Currency
for _, se := range data.Transaction.SiacoinOutputs {
if !relevant[se.Address] {
continue
}
inflow = inflow.Add(se.Value)
}
return inflow
case EventV2Transaction:
var inflow types.Currency
for _, se := range data.SiacoinOutputs {
if !relevant[se.Address] {
continue
}
inflow = inflow.Add(se.Value)
}
return inflow
default:
panic(fmt.Errorf("unknown event type: %T", e.Data))
}
}

// SiafundOutflow calculates the sum of Siafunds that were spent by relevant
// addresses
func (e *Event) SiafundOutflow() uint64 {
relevant := make(map[types.Address]bool)
for _, addr := range e.Relevant {
relevant[addr] = true
}

switch data := e.Data.(type) {
case EventPayout, EventV1ContractResolution, EventV2ContractResolution:
// payout events cannot have outflows
return 0
case EventV1Transaction:
var inflow uint64
for _, se := range data.SpentSiafundElements {
if !relevant[se.SiafundOutput.Address] {
continue
}
inflow += se.SiafundOutput.Value
}
return inflow
case EventV2Transaction:
var inflow uint64
for _, se := range data.SiafundInputs {
if !relevant[se.Parent.SiafundOutput.Address] {
continue
}
inflow += se.Parent.SiafundOutput.Value
}
return inflow
default:
panic(fmt.Errorf("unknown event type: %T", e.Data))
}
}

// SiafundInflow calculates the sum of Siafunds that were received by relevant
// addresses
func (e *Event) SiafundInflow() uint64 {
relevant := make(map[types.Address]bool)
for _, addr := range e.Relevant {
relevant[addr] = true
}

switch data := e.Data.(type) {
case EventPayout, EventV1ContractResolution, EventV2ContractResolution:
// payout events cannot have siafund inflows
return 0
case EventV1Transaction:
var outflow uint64
for _, se := range data.Transaction.SiafundOutputs {
if !relevant[se.Address] {
continue
}
outflow += se.Value
}
return outflow
case EventV2Transaction:
var outflow uint64
for _, se := range data.SiafundOutputs {
if !relevant[se.Address] {
continue
}
outflow += se.Value
}
return outflow
default:
panic(fmt.Errorf("unknown event type: %T", e.Data))
}
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (e *Event) UnmarshalJSON(b []byte) error {
var je struct {
ID types.Hash256 `json:"id"`
Index types.ChainIndex `json:"index"`
Inflow types.Currency `json:"inflow"`
Outflow types.Currency `json:"outflow"`
Timestamp time.Time `json:"timestamp"`
MaturityHeight uint64 `json:"maturityHeight"`
Type string `json:"type"`
Data json.RawMessage `json:"data"`
MaturityHeight uint64 `json:"maturityHeight"`
Timestamp time.Time `json:"timestamp"`
Relevant []types.Address `json:"relevant,omitempty"`
}
if err := json.Unmarshal(b, &je); err != nil {
return err
}

e.ID = je.ID
e.Index = je.Index
e.Inflow = je.Inflow
e.Outflow = je.Outflow
e.Type = je.Type
// Data is unmarshaled based on the event type below
e.MaturityHeight = je.MaturityHeight
e.Timestamp = je.Timestamp
e.MaturityHeight = je.MaturityHeight
e.Type = je.Type
e.Relevant = je.Relevant

var err error
switch je.Type {
Expand Down
Loading

0 comments on commit b680c3b

Please sign in to comment.