Skip to content

Commit

Permalink
types: add ID field to transaction JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
n8maninger committed Nov 13, 2024
1 parent 7b530e0 commit 8941fc1
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 0 deletions.
66 changes: 66 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,38 @@ type Transaction struct {
Signatures []TransactionSignature `json:"signatures,omitempty"`
}

// MarshalJSON implements json.Marshaller.
//
// json.Umarshaller is not implemented because the ID should be discarded.
func (txn Transaction) MarshalJSON() ([]byte, error) {
jsonTxn := struct {
ID TransactionID `json:"id"`
SiacoinInputs []SiacoinInput `json:"siacoinInputs,omitempty"`
SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"`
FileContracts []FileContract `json:"fileContracts,omitempty"`
FileContractRevisions []FileContractRevision `json:"fileContractRevisions,omitempty"`
StorageProofs []StorageProof `json:"storageProofs,omitempty"`
SiafundInputs []SiafundInput `json:"siafundInputs,omitempty"`
SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"`
MinerFees []Currency `json:"minerFees,omitempty"`
ArbitraryData [][]byte `json:"arbitraryData,omitempty"`
Signatures []TransactionSignature `json:"signatures,omitempty"`
}{
ID: txn.ID(),
SiacoinInputs: txn.SiacoinInputs,
SiacoinOutputs: txn.SiacoinOutputs,
FileContracts: txn.FileContracts,
FileContractRevisions: txn.FileContractRevisions,
StorageProofs: txn.StorageProofs,
SiafundInputs: txn.SiafundInputs,
SiafundOutputs: txn.SiafundOutputs,
MinerFees: txn.MinerFees,
ArbitraryData: txn.ArbitraryData,
Signatures: txn.Signatures,
}
return json.Marshal(jsonTxn)
}

// ID returns the "semantic hash" of the transaction, covering all of the
// transaction's effects, but not incidental data such as signatures. This
// ensures that the ID will remain stable (i.e. non-malleable).
Expand Down Expand Up @@ -689,6 +721,40 @@ type V2Transaction struct {
MinerFee Currency `json:"minerFee"`
}

// MarshalJSON implements json.Marshaller.
//
// json.Umarshaller is not implemented because the ID should be discarded.
func (txn V2Transaction) MarshalJSON() ([]byte, error) {
jsonTxn := struct {
ID TransactionID `json:"id"`
SiacoinInputs []V2SiacoinInput `json:"siacoinInputs,omitempty"`
SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"`
SiafundInputs []V2SiafundInput `json:"siafundInputs,omitempty"`
SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"`
FileContracts []V2FileContract `json:"fileContracts,omitempty"`
FileContractRevisions []V2FileContractRevision `json:"fileContractRevisions,omitempty"`
FileContractResolutions []V2FileContractResolution `json:"fileContractResolutions,omitempty"`
Attestations []Attestation `json:"attestations,omitempty"`
ArbitraryData []byte `json:"arbitraryData,omitempty"`
NewFoundationAddress *Address `json:"newFoundationAddress,omitempty"`
MinerFee Currency `json:"minerFee"`
}{
ID: txn.ID(),
SiacoinInputs: txn.SiacoinInputs,
SiacoinOutputs: txn.SiacoinOutputs,
SiafundInputs: txn.SiafundInputs,
SiafundOutputs: txn.SiafundOutputs,
FileContracts: txn.FileContracts,
FileContractRevisions: txn.FileContractRevisions,
FileContractResolutions: txn.FileContractResolutions,
Attestations: txn.Attestations,
ArbitraryData: txn.ArbitraryData,
NewFoundationAddress: txn.NewFoundationAddress,
MinerFee: txn.MinerFee,
}
return json.Marshal(jsonTxn)
}

// ID returns the "semantic hash" of the transaction, covering all of the
// transaction's effects, but not incidental data such as signatures or Merkle
// proofs. This ensures that the ID will remain stable (i.e. non-malleable).
Expand Down
74 changes: 74 additions & 0 deletions types/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,80 @@ func TestParseCurrency(t *testing.T) {
}
}

func TestTransactionJSONMarshalling(t *testing.T) {
txn := Transaction{
SiacoinOutputs: []SiacoinOutput{
{Address: frand.Entropy256(), Value: Siacoins(uint32(frand.Uint64n(math.MaxUint32)))},
},
SiacoinInputs: []SiacoinInput{
{
ParentID: frand.Entropy256(),
UnlockConditions: UnlockConditions{
PublicKeys: []UnlockKey{
PublicKey(frand.Entropy256()).UnlockKey(),
},
SignaturesRequired: 1,
},
},
},
}
expectedID := txn.ID()

buf, err := json.Marshal(txn)
if err != nil {
t.Fatal(err)
}

txnMap := make(map[string]any)
if err := json.Unmarshal(buf, &txnMap); err != nil {
t.Fatal(err)
} else if txnMap["id"] != expectedID.String() {
t.Fatalf("expected ID %q, got %q", expectedID.String(), txnMap["id"].(string))
}

var txn2 Transaction
if err := json.Unmarshal(buf, &txn2); err != nil {
t.Fatal(err)
} else if txn2.ID() != expectedID {
t.Fatalf("expected unmarshalled ID to be %q, got %q", expectedID, txn2.ID())
}
}

func TestV2TransactionJSONMarshalling(t *testing.T) {
txn := V2Transaction{
SiacoinInputs: []V2SiacoinInput{
{
Parent: SiacoinElement{
ID: frand.Entropy256(),
StateElement: StateElement{
LeafIndex: frand.Uint64n(math.MaxUint64),
},
},
},
},
}
expectedID := txn.ID()

buf, err := json.Marshal(txn)
if err != nil {
t.Fatal(err)
}

txnMap := make(map[string]any)
if err := json.Unmarshal(buf, &txnMap); err != nil {
t.Fatal(err)
} else if txnMap["id"] != expectedID.String() {
t.Fatalf("expected ID %q, got %q", expectedID.String(), txnMap["id"].(string))
}

var txn2 V2Transaction
if err := json.Unmarshal(buf, &txn2); err != nil {
t.Fatal(err)
} else if txn2.ID() != expectedID {
t.Fatalf("expected unmarshalled ID to be %q, got %q", expectedID, txn2.ID())
}
}

func TestUnmarshalHex(t *testing.T) {
for _, test := range []struct {
data string
Expand Down

0 comments on commit 8941fc1

Please sign in to comment.