-
Notifications
You must be signed in to change notification settings - Fork 21
Add DDO integration test skeleton and EVM wallet funding helper #653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
71a51f4
Add DDO integration test skeleton and EVM wallet funding helper
anjor 1393185
Fix FundEVMWallet to wait for transaction to be mined
anjor 1d12af0
switch anvil to automine, replace spinwait with MineBlock
parkan 329cac5
factor calibnet test constants into testutil
parkan e8ddfa8
Merge branch 'main' into feat/ddo-integration-test
parkan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| package dealpusher | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/data-preservation-programs/singularity/util/testutil" | ||
| "github.com/ethereum/go-ethereum/ethclient" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func startCalibnetFork(t *testing.T) *testutil.AnvilInstance { | ||
| t.Helper() | ||
| return testutil.StartAnvil(t, testutil.CalibnetRPC) | ||
| } | ||
|
|
||
| // TestIntegration_DDOClientConnectivity verifies that OnChainDDO can connect | ||
| // to a calibnet fork and detect the correct chain ID. | ||
| func TestIntegration_DDOClientConnectivity(t *testing.T) { | ||
| anvil := startCalibnetFork(t) | ||
| rpcURL := anvil.RPCURL | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
| defer cancel() | ||
|
|
||
| // Verify raw RPC connectivity and chain ID | ||
| ethClient, err := ethclient.DialContext(ctx, rpcURL) | ||
| require.NoError(t, err) | ||
| defer ethClient.Close() | ||
|
|
||
| chainID, err := ethClient.ChainID(ctx) | ||
| require.NoError(t, err) | ||
| require.EqualValues(t, testutil.CalibnetChainID, chainID.Int64()) | ||
|
|
||
| // Initialize OnChainDDO client with calibnet contract addresses | ||
| ddo, err := NewOnChainDDO(ctx, rpcURL, | ||
| testutil.CalibnetDDOContract, | ||
| testutil.CalibnetPaymentsContract, | ||
| testutil.CalibnetUSDFC, | ||
| ) | ||
| require.NoError(t, err) | ||
| defer ddo.Close() | ||
|
|
||
| require.EqualValues(t, testutil.CalibnetChainID, ddo.chainID.Int64()) | ||
| t.Logf("DDO client connected: chainID=%d, ddo=%s, payments=%s", | ||
| ddo.chainID, ddo.ddoContractAddr.Hex(), ddo.paymentsContractAddr.Hex()) | ||
| } | ||
|
|
||
| // TestIntegration_DDOWalletFunding verifies the testutil wallet funding helper | ||
| // works against an Anvil fork. | ||
| func TestIntegration_DDOWalletFunding(t *testing.T) { | ||
| anvil := startCalibnetFork(t) | ||
| rpcURL := anvil.RPCURL | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
| defer cancel() | ||
|
|
||
| // Generate a fresh test wallet | ||
| _, addr := testutil.GenerateTestKey(t) | ||
|
|
||
| // Fund it with 10 ETH (FIL on calibnet) | ||
| testutil.FundEVMWallet(t, rpcURL, addr, testutil.OneEther) | ||
|
|
||
| // Verify the balance | ||
| client, err := ethclient.DialContext(ctx, rpcURL) | ||
| require.NoError(t, err) | ||
| defer client.Close() | ||
|
|
||
| balance, err := client.BalanceAt(ctx, addr, nil) | ||
| require.NoError(t, err) | ||
| require.Equal(t, testutil.OneEther, balance) | ||
| t.Logf("funded wallet %s with %s wei", addr.Hex(), balance.String()) | ||
| } | ||
|
|
||
| // TestIntegration_DDOValidateSP attempts to validate an SP on calibnet. | ||
| // This test exercises the contract read path. Without a registered SP, | ||
| // ValidateSP returns an empty (inactive) config — we verify the call | ||
| // succeeds and returns a well-formed response. | ||
| func TestIntegration_DDOValidateSP(t *testing.T) { | ||
| anvil := startCalibnetFork(t) | ||
| rpcURL := anvil.RPCURL | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
| defer cancel() | ||
|
|
||
| ddo, err := NewOnChainDDO(ctx, rpcURL, | ||
| testutil.CalibnetDDOContract, | ||
| testutil.CalibnetPaymentsContract, | ||
| testutil.CalibnetUSDFC, | ||
| ) | ||
| require.NoError(t, err) | ||
| defer ddo.Close() | ||
|
|
||
| // Use a known-invalid provider ID — should return inactive, not error. | ||
| // When a real calibnet SP is registered, this test should be updated | ||
| // to use that provider ID and assert IsActive=true. | ||
| cfg, err := ddo.ValidateSP(ctx, 99999) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, cfg) | ||
| t.Logf("ValidateSP(99999): active=%v, minPiece=%d, maxPiece=%d", | ||
| cfg.IsActive, cfg.MinPieceSize, cfg.MaxPieceSize) | ||
|
|
||
| // TODO: Once a calibnet SP is registered in the DDO contract, add a | ||
| // test here with the real provider actor ID and assert: | ||
| // require.True(t, cfg.IsActive) | ||
| // require.Greater(t, cfg.MaxPieceSize, uint64(0)) | ||
| } | ||
|
|
||
| // TODO: TestIntegration_DDOFullDealFlow | ||
| // This test requires a registered, active SP on calibnet. Once FF provides | ||
| // the SP and it's registered in the DDO contract: | ||
| // | ||
| // 1. Fork calibnet via Anvil | ||
| // 2. Fund a test wallet with FIL | ||
| // 3. Create a test preparation with a piece in the database | ||
| // 4. Create a DDO schedule pointing to the funded wallet and the SP | ||
| // 5. Run the deal pusher schedule | ||
| // 6. Verify allocation was created on-chain | ||
| // 7. Initialize DDOTrackingClient | ||
| // 8. Verify allocation tracking returns the correct status | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package dealtracker | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/data-preservation-programs/singularity/util/testutil" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func startCalibnetFork(t *testing.T) *testutil.AnvilInstance { | ||
| t.Helper() | ||
| return testutil.StartAnvil(t, testutil.CalibnetRPC) | ||
| } | ||
|
|
||
| // TestIntegration_DDOTrackingClientConnectivity verifies that the DDO tracking | ||
| // client can connect to a calibnet fork and query allocation info. | ||
| func TestIntegration_DDOTrackingClientConnectivity(t *testing.T) { | ||
| anvil := startCalibnetFork(t) | ||
| rpcURL := anvil.RPCURL | ||
|
|
||
| client, err := NewDDOTrackingClient(rpcURL, testutil.CalibnetDDOContract) | ||
| require.NoError(t, err) | ||
| defer client.Close() | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
| defer cancel() | ||
|
|
||
| // Query a non-existent allocation — should return non-nil status, not error. | ||
| status, err := client.GetAllocationInfo(ctx, 0) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, status) | ||
| require.False(t, status.Activated) | ||
| t.Logf("GetAllocationInfo(0): activated=%v, sectorNumber=%d", | ||
| status.Activated, status.SectorNumber) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package testutil | ||
|
|
||
| // calibnet constants shared by integration tests | ||
| const ( | ||
| CalibnetRPC = "https://api.calibration.node.glif.io/rpc/v1" | ||
| CalibnetChainID = 314159 | ||
| CalibnetDDOContract = "0x889fD50196BE300D06dc4b8F0F17fdB0af587095" | ||
| CalibnetPaymentsContract = "0x09a0fDc2723fAd1A7b8e3e00eE5DF73841df55a0" | ||
| CalibnetUSDFC = "0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0" | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| package testutil | ||
|
|
||
| import ( | ||
| "context" | ||
| "crypto/ecdsa" | ||
| "errors" | ||
| "math/big" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/ethereum/go-ethereum" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/core/types" | ||
| "github.com/ethereum/go-ethereum/crypto" | ||
| "github.com/ethereum/go-ethereum/ethclient" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // anvilAccount0Key is the private key for Anvil's first pre-funded account | ||
| // (0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 10000 ETH). | ||
| // This is a well-known deterministic test key — never use in production. | ||
| const anvilAccount0Key = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" | ||
|
|
||
| // AnvilFunderKey returns the parsed private key for Anvil's first pre-funded account. | ||
| func AnvilFunderKey(t *testing.T) *ecdsa.PrivateKey { | ||
| t.Helper() | ||
| key, err := crypto.HexToECDSA(anvilAccount0Key) | ||
| require.NoError(t, err) | ||
| return key | ||
| } | ||
|
|
||
| // FundEVMWallet sends ETH from Anvil's pre-funded account 0 to the given address. | ||
| // amount is in wei. Anvil automines the tx immediately. | ||
| func FundEVMWallet(t *testing.T, rpcURL string, to common.Address, amount *big.Int) common.Hash { | ||
| t.Helper() | ||
|
|
||
| ctx := context.Background() | ||
| client, err := ethclient.DialContext(ctx, rpcURL) | ||
| require.NoError(t, err) | ||
| defer client.Close() | ||
|
|
||
| funderKey := AnvilFunderKey(t) | ||
| funderAddr := crypto.PubkeyToAddress(funderKey.PublicKey) | ||
|
|
||
| nonce, err := client.PendingNonceAt(ctx, funderAddr) | ||
| require.NoError(t, err) | ||
|
|
||
| chainID, err := client.ChainID(ctx) | ||
| require.NoError(t, err) | ||
|
|
||
| gasPrice, err := client.SuggestGasPrice(ctx) | ||
| require.NoError(t, err) | ||
|
|
||
| tx := types.NewTx(&types.LegacyTx{ | ||
| Nonce: nonce, | ||
| To: &to, | ||
| Value: amount, | ||
| Gas: 21000, | ||
| GasPrice: gasPrice, | ||
| }) | ||
|
|
||
| signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), funderKey) | ||
| require.NoError(t, err) | ||
|
|
||
| err = client.SendTransaction(ctx, signedTx) | ||
| require.NoError(t, err) | ||
|
|
||
| // automine should be instant but some anvil versions need a moment | ||
| var receipt *types.Receipt | ||
| for range 20 { | ||
| receipt, err = client.TransactionReceipt(ctx, signedTx.Hash()) | ||
| if err == nil { | ||
| break | ||
| } | ||
| if !errors.Is(err, ethereum.NotFound) { | ||
| require.NoError(t, err) | ||
| } | ||
| time.Sleep(100 * time.Millisecond) | ||
| } | ||
| require.NoError(t, err, "receipt not available after automine") | ||
| require.EqualValues(t, 1, receipt.Status, "funding transaction failed") | ||
|
|
||
| return signedTx.Hash() | ||
| } | ||
|
|
||
| // GenerateTestKey creates a fresh ECDSA key pair for testing and returns | ||
| // the private key and its corresponding EVM address. | ||
| func GenerateTestKey(t *testing.T) (*ecdsa.PrivateKey, common.Address) { | ||
| t.Helper() | ||
| key, err := crypto.GenerateKey() | ||
| require.NoError(t, err) | ||
| addr := crypto.PubkeyToAddress(key.PublicKey) | ||
| return key, addr | ||
| } | ||
|
|
||
| // OneEther is 1e18 wei, useful as a unit for test funding amounts. | ||
| var OneEther = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be under testutil tests probably