Skip to content

Commit

Permalink
add v2 file contract resolutions and test
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Nov 13, 2024
1 parent 2d4219a commit 7e5afd1
Show file tree
Hide file tree
Showing 12 changed files with 782 additions and 59 deletions.
76 changes: 67 additions & 9 deletions explorer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ type V2FileContract struct {
ConfirmationIndex *types.ChainIndex `json:"confirmationIndex"`
ConfirmationTransactionID *types.TransactionID `json:"confirmationTransactionID"`

Resolution *types.V2FileContractResolutionType
ResolutionIndex *types.ChainIndex `json:"resolutionIndex"`
ResolutionTransactionID *types.TransactionID `json:"resolutionTransactionID"`

Expand All @@ -210,6 +209,63 @@ type V2HostAnnouncement struct {
chain.V2HostAnnouncement
}

// V2FileContractResolutionType enumerates the types of file contract resolution.
type V2FileContractResolutionType interface {
isV2FileContractResolution()
}

func (*V2FileContractFinalization) isV2FileContractResolution() {}
func (*V2FileContractRenewal) isV2FileContractResolution() {}
func (*V2StorageProof) isV2FileContractResolution() {}
func (*V2FileContractExpiration) isV2FileContractResolution() {}

// A V2FileContractFinalization finalizes a contract, preventing further
// revisions and immediately creating its valid outputs.
type V2FileContractFinalization types.Signature

// A V2FileContractRenewal renews a file contract.
type V2FileContractRenewal struct {
FinalRevision V2FileContract `json:"finalRevision"`
NewContract V2FileContract `json:"newContract"`
RenterRollover types.Currency `json:"renterRollover"`
HostRollover types.Currency `json:"hostRollover"`

// signatures cover above fields
RenterSignature types.Signature `json:"renterSignature"`
HostSignature types.Signature `json:"hostSignature"`
}

// A V2StorageProof asserts the presence of a randomly-selected leaf within the
// Merkle tree of a V2FileContract's data.
type V2StorageProof struct {
// Selecting the leaf requires a source of unpredictable entropy; we use the
// ID of the block at the contract's ProofHeight. The storage proof thus
// includes a proof that this ID is the correct ancestor.
//
// During validation, it is imperative to check that ProofIndex.Height
// matches the ProofHeight field of the contract's final revision;
// otherwise, the prover could use any ProofIndex, giving them control over
// the leaf index.
ProofIndex types.ChainIndexElement

// The leaf is always 64 bytes, extended with zeros if necessary.
Leaf [64]byte
Proof []types.Hash256
}

// A V2FileContractExpiration resolves an expired contract. A contract is
// considered expired when its proof window has elapsed. If the contract is not
// storing any data, it will resolve as valid; otherwise, it resolves as missed.
type V2FileContractExpiration struct{}

// A V2FileContractResolution closes a v2 file contract's payment channel.
// There are four resolution types: renewwal, storage proof, finalization,
// and expiration.
type V2FileContractResolution struct {
Parent V2FileContract `json:"parent"`
Resolution V2FileContractResolutionType `json:"resolution"`
}

// A V2Transaction is a V2 transaction that uses the wrapped types above.
type V2Transaction struct {
ID types.TransactionID `json:"id"`
Expand All @@ -219,8 +275,9 @@ type V2Transaction struct {
SiafundInputs []types.V2SiafundInput `json:"siafundInputs,omitempty"`
SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"`

FileContracts []V2FileContract `json:"fileContracts,omitempty"`
FileContractRevisions []V2FileContractRevision `json:"fileContractRevisions,omitempty"`
FileContracts []V2FileContract `json:"fileContracts,omitempty"`
FileContractRevisions []V2FileContractRevision `json:"fileContractRevisions,omitempty"`
FileContractResolutions []V2FileContractResolution `json:"fileContractResolutions,omitempty"`

Attestations []types.Attestation `json:"attestations,omitempty"`
ArbitraryData []byte `json:"arbitraryData,omitempty"`
Expand All @@ -242,12 +299,13 @@ type V2BlockData struct {
// A Block is a block containing wrapped transactions and siacoin
// outputs for the miner payouts.
type Block struct {
Height uint64 `json:"height"`
ParentID types.BlockID `json:"parentID"`
Nonce uint64 `json:"nonce"`
Timestamp time.Time `json:"timestamp"`
MinerPayouts []SiacoinOutput `json:"minerPayouts"`
Transactions []Transaction `json:"transactions"`
Height uint64 `json:"height"`
ParentID types.BlockID `json:"parentID"`
Nonce uint64 `json:"nonce"`
Timestamp time.Time `json:"timestamp"`
ChainIndexElement types.ChainIndexElement `json:"chainIndexElement"`
MinerPayouts []SiacoinOutput `json:"minerPayouts"`
Transactions []Transaction `json:"transactions"`

V2 *V2BlockData `json:"v2,omitempty"`
}
Expand Down
6 changes: 4 additions & 2 deletions explorer/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ type (
// An UpdateState contains information relevant to the block being applied
// or reverted.
UpdateState struct {
Block types.Block
Block types.Block
ChainIndexElement types.ChainIndexElement

Events []Event
Metrics Metrics
Expand Down Expand Up @@ -211,7 +212,8 @@ func applyChainUpdate(tx UpdateTx, cau chain.ApplyUpdate) error {
events := AppliedEvents(cau.State, cau.Block, cau)

state := UpdateState{
Block: cau.Block,
Block: cau.Block,
ChainIndexElement: cau.ChainIndexElement(),

Events: events,
TreeUpdates: treeUpdates,
Expand Down
14 changes: 0 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,6 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.sia.tech/core v0.5.0 h1:feLC7DSCF+PhU157s/94106hFKyiGrGQ9HC3/dF/l7E=
go.sia.tech/core v0.5.0/go.mod h1:P3C1BWa/7J4XgdzWuaYHBvLo2RzZ0UBaJM4TG1GWB2g=
go.sia.tech/coreutils v0.5.0 h1:/xKxdw83iZy0jjLzI2NGHyG4azyjK5DJscxpkr6nIGQ=
go.sia.tech/coreutils v0.5.0/go.mod h1:VYM4FcmlhVrpDGvglLHjRW+gitoaxPNLvp5mL2quilo=
go.sia.tech/jape v0.12.1 h1:xr+o9V8FO8ScRqbSaqYf9bjj1UJ2eipZuNcI1nYousU=
go.sia.tech/jape v0.12.1/go.mod h1:wU+h6Wh5olDjkPXjF0tbZ1GDgoZ6VTi4naFw91yyWC4=
go.sia.tech/mux v1.3.0 h1:hgR34IEkqvfBKUJkAzGi31OADeW2y7D6Bmy/Jcbop9c=
go.sia.tech/mux v1.3.0/go.mod h1:I46++RD4beqA3cW9Xm9SwXbezwPqLvHhVs9HLpDtt58=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
Expand Down
4 changes: 3 additions & 1 deletion internal/testutil/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ func SignV2TransactionWithContracts(cs consensus.State, pk, renterPK, hostPK typ
r.RenterSignature = renterPK.SignHash(cs.RenewalSigHash(*r))
r.HostSignature = hostPK.SignHash(cs.RenewalSigHash(*r))
case *types.V2FileContractFinalization:
*r = types.V2FileContractFinalization(renterPK.SignHash(cs.ContractSigHash(txn.FileContractResolutions[i].Parent.V2FileContract)))
finalRevision := txn.FileContractResolutions[i].Parent.V2FileContract
finalRevision.RevisionNumber = types.MaxRevisionNumber
*r = types.V2FileContractFinalization(renterPK.SignHash(cs.ContractSigHash(finalRevision)))
}
}
}
Expand Down
41 changes: 41 additions & 0 deletions internal/testutil/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,47 @@ func CheckV2Transaction(t *testing.T, expectTxn types.V2Transaction, gotTxn expl
CheckV2FC(t, expected.Revision, got.Revision)
}

Equal(t, "file contract resolutions", len(expectTxn.FileContractResolutions), len(gotTxn.FileContractResolutions))
for i := range expectTxn.FileContractResolutions {
expected := expectTxn.FileContractResolutions[i]
got := gotTxn.FileContractResolutions[i]

CheckV2FC(t, expected.Parent.V2FileContract, got.Parent)

switch v := expected.Resolution.(type) {
case *types.V2FileContractRenewal:
if gotV, ok := got.Resolution.(*explorer.V2FileContractRenewal); !ok {
t.Fatalf("expected V2FileContractRenewal, got %v", reflect.TypeOf(got.Resolution))
} else {
CheckV2FC(t, v.FinalRevision, gotV.FinalRevision)
CheckV2FC(t, v.NewContract, gotV.NewContract)

Equal(t, "renter rollover", v.RenterRollover, gotV.RenterRollover)
Equal(t, "host rollover", v.HostRollover, gotV.HostRollover)
Equal(t, "renter signature", v.RenterSignature, gotV.RenterSignature)
Equal(t, "host signature", v.HostSignature, gotV.HostSignature)
}
case *types.V2StorageProof:
if gotV, ok := got.Resolution.(*explorer.V2StorageProof); !ok {
t.Fatalf("expected V2StorageProof, got %v", reflect.TypeOf(got.Resolution))
} else {
Equal(t, "proof index", v.ProofIndex, gotV.ProofIndex)
Equal(t, "leaf", v.Leaf, gotV.Leaf)
Equal(t, "proof", v.Proof, gotV.Proof)
}
case *types.V2FileContractFinalization:
if gotV, ok := got.Resolution.(*explorer.V2FileContractFinalization); !ok {
t.Fatalf("expected V2FileContractFinalization, got %v", reflect.TypeOf(got.Resolution))
} else {
Equal(t, "finalization signature", types.Signature(*v), types.Signature(*gotV))
}
case *types.V2FileContractExpiration:
if _, ok := got.Resolution.(*explorer.V2FileContractExpiration); !ok {
t.Fatalf("expected V2FileContractExpiration, got %v", reflect.TypeOf(got.Resolution))
}
}
}

Equal(t, "attestations", len(expectTxn.Attestations), len(gotTxn.Attestations))
for i := range expectTxn.Attestations {
expected := expectTxn.Attestations[i]
Expand Down
2 changes: 1 addition & 1 deletion persist/sqlite/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (s *Store) Block(id types.BlockID) (result explorer.Block, err error) {
err = s.transaction(func(tx *txn) error {
var v2Height uint64
var v2Commitment types.Hash256
err := tx.QueryRow(`SELECT parent_id, nonce, timestamp, height, v2_height, v2_commitment FROM blocks WHERE id = ?`, encode(id)).Scan(decode(&result.ParentID), decode(&result.Nonce), decode(&result.Timestamp), &result.Height, decodeNull(&v2Height), decodeNull(&v2Commitment))
err := tx.QueryRow(`SELECT parent_id, nonce, timestamp, height, chain_index_element, v2_height, v2_commitment FROM blocks WHERE id = ?`, encode(id)).Scan(decode(&result.ParentID), decode(&result.Nonce), decode(&result.Timestamp), &result.Height, decode(&result.ChainIndexElement), decodeNull(&v2Height), decodeNull(&v2Commitment))
if err != nil {
return fmt.Errorf("failed to get block: %w", err)
}
Expand Down
9 changes: 6 additions & 3 deletions persist/sqlite/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ type updateTx struct {
tx *txn
}

func addBlock(tx *txn, b types.Block, height uint64) error {
func addBlock(tx *txn, b types.Block, cie types.ChainIndexElement, height uint64) error {
// nonce is encoded because database/sql doesn't support uint64 with high bit set
var v2Height any
var v2Commitment any
if b.V2 != nil {
v2Height = encode(b.V2.Height)
v2Commitment = encode(b.V2.Commitment)
}
_, err := tx.Exec("INSERT INTO blocks(id, height, parent_id, nonce, timestamp, v2_height, v2_commitment) VALUES (?, ?, ?, ?, ?, ?, ?);", encode(b.ID()), height, encode(b.ParentID), encode(b.Nonce), encode(b.Timestamp), v2Height, v2Commitment)
cie.StateElement.MerkleProof = nil
_, err := tx.Exec("INSERT INTO blocks(id, height, parent_id, nonce, timestamp, chain_index_element, v2_height, v2_commitment) VALUES (?, ?, ?, ?, ?, ?, ?, ?);", encode(b.ID()), height, encode(b.ParentID), encode(b.Nonce), encode(b.Timestamp), encode(cie), v2Height, v2Commitment)
return err
}

Expand Down Expand Up @@ -1018,7 +1019,7 @@ func (ut *updateTx) Metrics(height uint64) (explorer.Metrics, error) {
}

func (ut *updateTx) ApplyIndex(state explorer.UpdateState) error {
if err := addBlock(ut.tx, state.Block, state.Metrics.Index.Height); err != nil {
if err := addBlock(ut.tx, state.Block, state.ChainIndexElement, state.Metrics.Index.Height); err != nil {
return fmt.Errorf("ApplyIndex: failed to add block: %w", err)
} else if err := updateMaturedBalances(ut.tx, false, state.Metrics.Index.Height); err != nil {
return fmt.Errorf("ApplyIndex: failed to update matured balances: %w", err)
Expand Down Expand Up @@ -1106,6 +1107,8 @@ func (ut *updateTx) RevertIndex(state explorer.UpdateState) error {
return fmt.Errorf("RevertIndex: failed to update balances: %w", err)
} else if _, err := updateFileContractElements(ut.tx, true, state.Block, state.FileContractElements); err != nil {
return fmt.Errorf("RevertIndex: failed to update file contract state: %w", err)
} else if _, err := updateV2FileContractElements(ut.tx, true, state.Block, state.V2FileContractElements); err != nil {
return fmt.Errorf("ApplyIndex: failed to add v2 file contracts: %w", err)
} else if err := deleteBlock(ut.tx, state.Block.ID()); err != nil {
return fmt.Errorf("RevertIndex: failed to delete block: %w", err)
} else if err := updateStateTree(ut.tx, state.TreeUpdates); err != nil {
Expand Down
33 changes: 32 additions & 1 deletion persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CREATE TABLE blocks (
parent_id BLOB NOT NULL,
nonce BLOB NOT NULL,
timestamp INTEGER NOT NULL,
chain_index_element BLOB NOT NULL,

v2_height INTEGER,
v2_commitment BLOB
Expand Down Expand Up @@ -327,6 +328,37 @@ CREATE TABLE v2_transaction_file_contract_revisions (
);
CREATE INDEX v2_transaction_file_contract_revisions_transaction_id_index ON v2_transaction_file_contract_revisions(transaction_id);

CREATE TABLE v2_transaction_file_contract_resolutions (
transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL,
transaction_order INTEGER NOT NULL,
parent_contract_id INTEGER REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE NOT NULL, -- add an index to all foreign keys

-- V2FileContractRenewal = 0, V2StorageProof = 1, V2FileContractFinalization = 2, V2FileContractExpiration = 3
resolution_type INTEGER NOT NULL,

-- V2FileContractRenewal
renewal_final_revision_contract_id INTEGER REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE,
renewal_new_contract_id INTEGER REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE,
renewal_renter_rollover BLOB,
renewal_host_rollover BLOB,
renewal_renter_signature BLOB,
renewal_host_signature BLOB,

-- V2StorageProof
storage_proof_proof_index BLOB,
storage_proof_leaf BLOB,
storage_proof_proof BLOB,

-- V2FileContractFinalization
finalization_signature BLOB,

-- V2FileContractExpiration
-- no fields

UNIQUE(transaction_id, transaction_order)
);
CREATE INDEX v2_transaction_file_contract_resolutions_transaction_id_index ON v2_transaction_file_contract_resolutions(transaction_id);

CREATE TABLE v2_transaction_attestations (
transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL,
transaction_order INTEGER NOT NULL,
Expand Down Expand Up @@ -423,7 +455,6 @@ CREATE TABLE v2_last_contract_revision (
confirmation_index BLOB,
confirmation_transaction_id BLOB REFERENCES v2_transactions(transaction_id),

resolution BLOB,
resolution_index BLOB,
resolution_transaction_id BLOB REFERENCES v2_transactions(transaction_id),

Expand Down
Loading

0 comments on commit 7e5afd1

Please sign in to comment.