From 86ae6537c97e41acfb4716db663674af8101ebd8 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 17:03:35 +0800 Subject: [PATCH 01/12] add blob version check for blobTx --- tx-submitter/services/blob_hash_test.go | 126 ++++++++++++++++++++++++ tx-submitter/services/rollup.go | 85 ++++++++++------ tx-submitter/services/rollup_test.go | 8 +- 3 files changed, 186 insertions(+), 33 deletions(-) create mode 100644 tx-submitter/services/blob_hash_test.go diff --git a/tx-submitter/services/blob_hash_test.go b/tx-submitter/services/blob_hash_test.go new file mode 100644 index 00000000..929d5710 --- /dev/null +++ b/tx-submitter/services/blob_hash_test.go @@ -0,0 +1,126 @@ +package services + +import ( + "crypto/sha256" + "testing" + + "github.com/morph-l2/go-ethereum/common" + "github.com/morph-l2/go-ethereum/crypto/kzg4844" + "github.com/stretchr/testify/assert" +) + +// TestCalcBlobHashV1VsKZGToVersionedHash verifies that CalcBlobHashV1 and kZGToVersionedHash +// produce the same results for the same commitment. +func TestCalcBlobHashV1VsKZGToVersionedHash(t *testing.T) { + tests := []struct { + name string + commitment kzg4844.Commitment + }{ + { + name: "zero commitment", + commitment: kzg4844.Commitment{}, + }, + { + name: "all ones commitment", + commitment: func() kzg4844.Commitment { + var c kzg4844.Commitment + for i := range c { + c[i] = 0xFF + } + return c + }(), + }, + { + name: "pattern commitment", + commitment: func() kzg4844.Commitment { + var c kzg4844.Commitment + for i := range c { + c[i] = byte(i % 256) + } + return c + }(), + }, + { + name: "reversed pattern commitment", + commitment: func() kzg4844.Commitment { + var c kzg4844.Commitment + for i := range c { + c[i] = byte(255 - (i % 256)) + } + return c + }(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Calculate using kZGToVersionedHash (v0 method) + hashV0 := kZGToVersionedHash(tt.commitment) + + // Calculate using CalcBlobHashV1 (v1 method) + hasher := sha256.New() + hashV1 := kzg4844.CalcBlobHashV1(hasher, &tt.commitment) + + // Convert hashV1 to common.Hash for comparison + hashV1CommonHash := common.Hash(hashV1) + + // They should be equal + assert.Equal(t, hashV0, hashV1CommonHash, + "CalcBlobHashV1 and kZGToVersionedHash should produce the same result") + + // Verify the first byte is 0x01 (version byte) + assert.Equal(t, uint8(0x01), hashV0[0], "First byte should be version 0x01") + assert.Equal(t, uint8(0x01), hashV1[0], "First byte should be version 0x01") + }) + } +} + +// TestCalcBlobHashV1ReuseHasher verifies that CalcBlobHashV1 can correctly reuse a hasher +// for multiple commitments. +func TestCalcBlobHashV1ReuseHasher(t *testing.T) { + // Create multiple commitments + commitments := []kzg4844.Commitment{ + {0x01, 0x02, 0x03}, // Will be padded with zeros + {0xFF, 0xFE, 0xFD}, // Will be padded with zeros + {}, // Zero commitment + } + + // Calculate hashes using reused hasher + hasher := sha256.New() + hashesV1 := make([]common.Hash, len(commitments)) + for i, commit := range commitments { + hashV1 := kzg4844.CalcBlobHashV1(hasher, &commit) + hashesV1[i] = common.Hash(hashV1) + } + + // Calculate hashes using kZGToVersionedHash (should produce same results) + hashesV0 := make([]common.Hash, len(commitments)) + for i, commit := range commitments { + hashesV0[i] = kZGToVersionedHash(commit) + } + + // Compare results + for i := range commitments { + assert.Equal(t, hashesV0[i], hashesV1[i], + "Hash %d should be equal regardless of hasher reuse", i) + } +} + +// TestCalcBlobHashV1MultipleCalls verifies that CalcBlobHashV1 produces consistent +// results when called multiple times with the same commitment. +func TestCalcBlobHashV1MultipleCalls(t *testing.T) { + commitment := kzg4844.Commitment{0x42} + + hasher1 := sha256.New() + hash1 := kzg4844.CalcBlobHashV1(hasher1, &commitment) + + hasher2 := sha256.New() + hash2 := kzg4844.CalcBlobHashV1(hasher2, &commitment) + + // Same commitment should produce same hash + assert.Equal(t, hash1, hash2, "Same commitment should produce same hash") + + // Should also match kZGToVersionedHash + hashV0 := kZGToVersionedHash(commitment) + assert.Equal(t, common.Hash(hash1), hashV0, "Should match kZGToVersionedHash result") +} diff --git a/tx-submitter/services/rollup.go b/tx-submitter/services/rollup.go index 4c64f24f..57d45453 100644 --- a/tx-submitter/services/rollup.go +++ b/tx-submitter/services/rollup.go @@ -4,6 +4,7 @@ import ( "context" "crypto/ecdsa" "crypto/rsa" + "crypto/sha256" "errors" "fmt" "math/big" @@ -842,7 +843,7 @@ func (r *Rollup) finalize() error { if err != nil { return fmt.Errorf("pack finalizeBatch error:%v", err) } - tip, feecap, _, err := r.GetGasTipAndCap() + tip, feecap, _, _, err := r.GetGasTipAndCap() if err != nil { log.Error("get gas tip and cap error", "business", "finalize") return fmt.Errorf("get gas tip and cap error:%v", err) @@ -1068,7 +1069,7 @@ func (r *Rollup) rollup() error { } // tip and cap - tip, gasFeeCap, blobFee, err := r.GetGasTipAndCap() + tip, gasFeeCap, blobFee, head, err := r.GetGasTipAndCap() if err != nil { return fmt.Errorf("get gas tip and cap error:%v", err) } @@ -1105,7 +1106,7 @@ func (r *Rollup) rollup() error { } // Create and sign transaction - tx, err := r.createRollupTx(batch, nonce, gas, tip, gasFeeCap, blobFee, calldata) + tx, err := r.createRollupTx(batch, nonce, gas, tip, gasFeeCap, blobFee, calldata, head) if err != nil { return fmt.Errorf("failed to create rollup tx: %w", err) } @@ -1119,14 +1120,14 @@ func (r *Rollup) rollup() error { r.logTxInfo(signedTx, batchIndex) // Send transaction - if err := r.SendTx(signedTx); err != nil { + if err = r.SendTx(signedTx); err != nil { return fmt.Errorf("failed to send tx: %w", err) } // Update pending state r.pendingTxs.SetPindex(batchIndex) r.pendingTxs.SetNonce(tx.Nonce()) - if err := r.pendingTxs.Add(signedTx); err != nil { + if err = r.pendingTxs.Add(signedTx); err != nil { log.Error("Failed to track transaction", "error", err) } @@ -1146,17 +1147,31 @@ func (r *Rollup) getNextNonce() uint64 { return nonce } -func (r *Rollup) createRollupTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, gasFeeCap, blobFee *big.Int, calldata []byte) (*ethtypes.Transaction, error) { +func (r *Rollup) createRollupTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, gasFeeCap, blobFee *big.Int, calldata []byte, head *ethtypes.Header) (*ethtypes.Transaction, error) { if len(batch.Sidecar.Blobs) > 0 { - return r.createBlobTx(batch, nonce, gas, tip, gasFeeCap, blobFee, calldata) + return r.createBlobTx(batch, nonce, gas, tip, gasFeeCap, blobFee, calldata, head) } return r.createDynamicFeeTx(nonce, gas, tip, gasFeeCap, calldata) } -func (r *Rollup) createBlobTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, gasFeeCap, blobFee *big.Int, calldata []byte) (*ethtypes.Transaction, error) { +func (r *Rollup) createBlobTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, gasFeeCap, blobFee *big.Int, calldata []byte, head *ethtypes.Header) (*ethtypes.Transaction, error) { versionedHashes := make([]common.Hash, 0, len(batch.Sidecar.Commitments)) + hasher := sha256.New() for _, commit := range batch.Sidecar.Commitments { - versionedHashes = append(versionedHashes, kZGToVersionedHash(commit)) + versionedHashes = append(versionedHashes, kzg4844.CalcBlobHashV1(hasher, &commit)) + } + sidecar := ðtypes.BlobTxSidecar{ + Version: batch.Sidecar.Version, + Blobs: batch.Sidecar.Blobs, + Commitments: batch.Sidecar.Commitments, + Proofs: batch.Sidecar.Proofs, + } + version := r.DetermineBlobVersion(head) + if sidecar.Version == ethtypes.BlobSidecarVersion0 && version == ethtypes.BlobSidecarVersion1 { + err := sidecar.ToV1() + if err != nil { + return nil, err + } } return ethtypes.NewTx(ðtypes.BlobTx{ @@ -1169,11 +1184,7 @@ func (r *Rollup) createBlobTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, Data: calldata, BlobFeeCap: uint256.MustFromBig(blobFee), BlobHashes: versionedHashes, - Sidecar: ðtypes.BlobTxSidecar{ - Blobs: batch.Sidecar.Blobs, - Commitments: batch.Sidecar.Commitments, - Proofs: batch.Sidecar.Proofs, - }, + Sidecar: sidecar, }), nil } @@ -1242,21 +1253,21 @@ func (r *Rollup) buildSignatureInput(batch *eth.RPCRollupBatch) (*bindings.IRoll return &sigData, nil } -func (r *Rollup) GetGasTipAndCap() (*big.Int, *big.Int, *big.Int, error) { +func (r *Rollup) GetGasTipAndCap() (*big.Int, *big.Int, *big.Int, *ethtypes.Header, error) { head, err := r.L1Client.HeaderByNumber(context.Background(), nil) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } if head.BaseFee != nil { log.Info("market fee info", "feecap", head.BaseFee) if r.cfg.MaxBaseFee > 0 && head.BaseFee.Cmp(big.NewInt(int64(r.cfg.MaxBaseFee))) > 0 { - return nil, nil, nil, fmt.Errorf("base fee is too high, base fee %v exceeds max %v", head.BaseFee, r.cfg.MaxBaseFee) + return nil, nil, nil, nil, fmt.Errorf("base fee is too high, base fee %v exceeds max %v", head.BaseFee, r.cfg.MaxBaseFee) } } tip, err := r.L1Client.SuggestGasTipCap(context.Background()) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } log.Info("market fee info", "tip", tip) @@ -1265,7 +1276,7 @@ func (r *Rollup) GetGasTipAndCap() (*big.Int, *big.Int, *big.Int, error) { tip = new(big.Int).Div(tip, big.NewInt(100)) } if r.cfg.MaxTip > 0 && tip.Cmp(big.NewInt(int64(r.cfg.MaxTip))) > 0 { - return nil, nil, nil, fmt.Errorf("tip is too high, tip %v exceeds max %v", tip, r.cfg.MaxTip) + return nil, nil, nil, nil, fmt.Errorf("tip is too high, tip %v exceeds max %v", tip, r.cfg.MaxTip) } var gasFeeCap *big.Int @@ -1298,7 +1309,7 @@ func (r *Rollup) GetGasTipAndCap() (*big.Int, *big.Int, *big.Int, error) { "blobfee", blobFee, ) - return tip, gasFeeCap, blobFee, nil + return tip, gasFeeCap, blobFee, head, nil } // PreCheck is run before the submitter to check whether the submitter can be started @@ -1578,7 +1589,7 @@ func (r *Rollup) ReSubmitTx(resend bool, tx *ethtypes.Transaction) (*ethtypes.Tr "nonce", tx.Nonce(), ) - tip, gasFeeCap, blobFeeCap, err := r.GetGasTipAndCap() + tip, gasFeeCap, blobFeeCap, head, err := r.GetGasTipAndCap() if err != nil { return nil, fmt.Errorf("get gas tip and cap error:%w", err) } @@ -1606,11 +1617,6 @@ func (r *Rollup) ReSubmitTx(resend bool, tx *ethtypes.Transaction) (*ethtypes.Tr if r.cfg.MinTip > 0 && tip.Cmp(big.NewInt(int64(r.cfg.MinTip))) < 0 { log.Info("replace tip is too low, update tip to min tip ", "tip", tip, "min_tip", r.cfg.MinTip) tip = big.NewInt(int64(r.cfg.MinTip)) - // recalc feecap - head, err := r.L1Client.HeaderByNumber(context.Background(), nil) - if err != nil { - return nil, fmt.Errorf("get l1 head error:%w", err) - } var recalculatedFeecap *big.Int if head.BaseFee != nil { recalculatedFeecap = new(big.Int).Add( @@ -1640,7 +1646,14 @@ func (r *Rollup) ReSubmitTx(resend bool, tx *ethtypes.Transaction) (*ethtypes.Tr Data: tx.Data(), }) case ethtypes.BlobTxType: - + sidecar := tx.BlobTxSidecar() + version := r.DetermineBlobVersion(head) + if sidecar.Version == ethtypes.BlobSidecarVersion0 && version == ethtypes.BlobSidecarVersion1 { + err = sidecar.ToV1() + if err != nil { + return nil, err + } + } newTx = ethtypes.NewTx(ðtypes.BlobTx{ ChainID: uint256.MustFromBig(tx.ChainId()), Nonce: tx.Nonce(), @@ -1652,7 +1665,7 @@ func (r *Rollup) ReSubmitTx(resend bool, tx *ethtypes.Transaction) (*ethtypes.Tr Data: tx.Data(), BlobFeeCap: uint256.MustFromBig(blobFeeCap), BlobHashes: tx.BlobHashes(), - Sidecar: tx.BlobTxSidecar(), + Sidecar: sidecar, }) default: @@ -1818,7 +1831,7 @@ func (r *Rollup) CancelTx(tx *ethtypes.Transaction) (*ethtypes.Transaction, erro "nonce", tx.Nonce(), ) - tip, gasFeeCap, blobFeeCap, err := r.GetGasTipAndCap() + tip, gasFeeCap, blobFeeCap, _, err := r.GetGasTipAndCap() if err != nil { return nil, fmt.Errorf("get gas tip and cap error:%w", err) } @@ -1910,3 +1923,17 @@ func (r *Rollup) CancelTx(tx *ethtypes.Transaction) (*ethtypes.Transaction, erro return newTx, nil } + +func (r *Rollup) DetermineBlobVersion(head *ethtypes.Header) byte { + if head == nil { + return ethtypes.BlobSidecarVersion0 + } + blobConfig, exist := r.ChainConfigMap[r.chainId.Uint64()] + if !exist { + blobConfig = types.DefaultBlobConfig + } + if blobConfig.OsakaTime != nil && blobConfig.IsOsaka(head.Number, head.Time) { + return ethtypes.BlobSidecarVersion1 + } + return ethtypes.BlobSidecarVersion0 +} diff --git a/tx-submitter/services/rollup_test.go b/tx-submitter/services/rollup_test.go index abbca104..79fe4fcd 100644 --- a/tx-submitter/services/rollup_test.go +++ b/tx-submitter/services/rollup_test.go @@ -64,7 +64,7 @@ func TestGetGasTipAndCap(t *testing.T) { l1Mock.TipCap = initTip l1Mock.Block = block - tip, feecap, blobfee, err := r.GetGasTipAndCap() + tip, feecap, blobfee, _, err := r.GetGasTipAndCap() require.NoError(t, err) require.NotNil(t, tip) require.NotNil(t, feecap) @@ -77,7 +77,7 @@ func TestGetGasTipAndCap(t *testing.T) { l1Mock.Block = block r.cfg.TipFeeBump = 200 - tip, feecap, blobfee, err = r.GetGasTipAndCap() + tip, feecap, blobfee, _, err = r.GetGasTipAndCap() require.NoError(t, err) require.NotNil(t, tip) require.NotNil(t, feecap) @@ -90,7 +90,7 @@ func TestGetGasTipAndCap(t *testing.T) { l1Mock.Block = block r.cfg.MaxBaseFee = baseFee.Uint64() - 1 - _, _, _, err = r.GetGasTipAndCap() + _, _, _, _, err = r.GetGasTipAndCap() require.ErrorContains(t, err, "base fee is too high") // Test with tip too high @@ -99,7 +99,7 @@ func TestGetGasTipAndCap(t *testing.T) { l1Mock.Block = block r.cfg.MaxTip = initTip.Uint64() - 1 - _, _, _, err = r.GetGasTipAndCap() + _, _, _, _, err = r.GetGasTipAndCap() require.ErrorContains(t, err, "tip is too high") } From 7fa7f4755a3e84ea4922e9b0e0106827e4e6cd76 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 17:05:32 +0800 Subject: [PATCH 02/12] update --- tx-submitter/services/rollup.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tx-submitter/services/rollup.go b/tx-submitter/services/rollup.go index 57d45453..e3eb5736 100644 --- a/tx-submitter/services/rollup.go +++ b/tx-submitter/services/rollup.go @@ -4,7 +4,6 @@ import ( "context" "crypto/ecdsa" "crypto/rsa" - "crypto/sha256" "errors" "fmt" "math/big" @@ -1155,17 +1154,13 @@ func (r *Rollup) createRollupTx(batch *eth.RPCRollupBatch, nonce, gas uint64, ti } func (r *Rollup) createBlobTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, gasFeeCap, blobFee *big.Int, calldata []byte, head *ethtypes.Header) (*ethtypes.Transaction, error) { - versionedHashes := make([]common.Hash, 0, len(batch.Sidecar.Commitments)) - hasher := sha256.New() - for _, commit := range batch.Sidecar.Commitments { - versionedHashes = append(versionedHashes, kzg4844.CalcBlobHashV1(hasher, &commit)) - } sidecar := ðtypes.BlobTxSidecar{ Version: batch.Sidecar.Version, Blobs: batch.Sidecar.Blobs, Commitments: batch.Sidecar.Commitments, Proofs: batch.Sidecar.Proofs, } + versionedHashes := sidecar.BlobHashes() version := r.DetermineBlobVersion(head) if sidecar.Version == ethtypes.BlobSidecarVersion0 && version == ethtypes.BlobSidecarVersion1 { err := sidecar.ToV1() @@ -1173,7 +1168,6 @@ func (r *Rollup) createBlobTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, return nil, err } } - return ethtypes.NewTx(ðtypes.BlobTx{ ChainID: uint256.MustFromBig(r.chainId), Nonce: nonce, From 5c3442762cc33dea50f10b9acaf87b71d710a9bd Mon Sep 17 00:00:00 2001 From: FletcherMan Date: Tue, 4 Nov 2025 17:22:33 +0800 Subject: [PATCH 03/12] remove blob proof generation when making blob (#801) Co-authored-by: fletcher.fan --- node/types/blob.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/node/types/blob.go b/node/types/blob.go index 847e15eb..8abbeb4c 100644 --- a/node/types/blob.go +++ b/node/types/blob.go @@ -52,7 +52,7 @@ func RetrieveBlobBytes(blob *kzg4844.Blob) ([]byte, error) { return data, nil } -func makeBCP(bz []byte) (b kzg4844.Blob, c kzg4844.Commitment, p kzg4844.Proof, err error) { +func makeBlobCommitment(bz []byte) (b kzg4844.Blob, c kzg4844.Commitment, err error) { blob, err := MakeBlobCanonical(bz) if err != nil { return @@ -62,10 +62,6 @@ func makeBCP(bz []byte) (b kzg4844.Blob, c kzg4844.Commitment, p kzg4844.Proof, if err != nil { return } - p, err = kzg4844.ComputeBlobProof(&b, c) - if err != nil { - return - } return } @@ -85,20 +81,19 @@ func MakeBlobTxSidecar(blobBytes []byte) (*eth.BlobTxSidecar, error) { err error blobs = make([]kzg4844.Blob, blobCount) commitments = make([]kzg4844.Commitment, blobCount) - proofs = make([]kzg4844.Proof, blobCount) ) switch blobCount { case 1: - blobs[0], commitments[0], proofs[0], err = makeBCP(blobBytes) + blobs[0], commitments[0], err = makeBlobCommitment(blobBytes) if err != nil { return nil, err } case 2: - blobs[0], commitments[0], proofs[0], err = makeBCP(blobBytes[:MaxBlobBytesSize]) + blobs[0], commitments[0], err = makeBlobCommitment(blobBytes[:MaxBlobBytesSize]) if err != nil { return nil, err } - blobs[1], commitments[1], proofs[1], err = makeBCP(blobBytes[MaxBlobBytesSize:]) + blobs[1], commitments[1], err = makeBlobCommitment(blobBytes[MaxBlobBytesSize:]) if err != nil { return nil, err } @@ -106,7 +101,6 @@ func MakeBlobTxSidecar(blobBytes []byte) (*eth.BlobTxSidecar, error) { return ð.BlobTxSidecar{ Blobs: blobs, Commitments: commitments, - Proofs: proofs, }, nil } From 0ef27184e64120994104b77aeb0f0492da1dce23 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 17:46:15 +0800 Subject: [PATCH 04/12] add blob version check for blobTx --- tx-submitter/services/rollup.go | 44 +++++++++---------- tx-submitter/types/blob.go | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 tx-submitter/types/blob.go diff --git a/tx-submitter/services/rollup.go b/tx-submitter/services/rollup.go index e3eb5736..e27f8637 100644 --- a/tx-submitter/services/rollup.go +++ b/tx-submitter/services/rollup.go @@ -1154,20 +1154,30 @@ func (r *Rollup) createRollupTx(batch *eth.RPCRollupBatch, nonce, gas uint64, ti } func (r *Rollup) createBlobTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, gasFeeCap, blobFee *big.Int, calldata []byte, head *ethtypes.Header) (*ethtypes.Transaction, error) { + versionedHashes := types.BlobHashes(batch.Sidecar.Blobs, batch.Sidecar.Commitments) sidecar := ðtypes.BlobTxSidecar{ - Version: batch.Sidecar.Version, Blobs: batch.Sidecar.Blobs, Commitments: batch.Sidecar.Commitments, - Proofs: batch.Sidecar.Proofs, } - versionedHashes := sidecar.BlobHashes() - version := r.DetermineBlobVersion(head) - if sidecar.Version == ethtypes.BlobSidecarVersion0 && version == ethtypes.BlobSidecarVersion1 { - err := sidecar.ToV1() + switch types.DetermineBlobVersion(head, r.chainId.Uint64()) { + case ethtypes.BlobSidecarVersion0: + sidecar.Version = ethtypes.BlobSidecarVersion1 + proof, err := types.MakeBlobProof(sidecar.Blobs, sidecar.Commitments) if err != nil { - return nil, err + return nil, fmt.Errorf("gen blob proof failed %v", err) } + sidecar.Proofs = proof + case ethtypes.BlobSidecarVersion1: + sidecar.Version = ethtypes.BlobSidecarVersion1 + proof, err := types.MakeCellProof(sidecar.Blobs) + if err != nil { + return nil, fmt.Errorf("gen cell proof failed %v", err) + } + sidecar.Proofs = proof + default: + return nil, fmt.Errorf("unsupported blob version") } + return ethtypes.NewTx(ðtypes.BlobTx{ ChainID: uint256.MustFromBig(r.chainId), Nonce: nonce, @@ -1287,7 +1297,7 @@ func (r *Rollup) GetGasTipAndCap() (*big.Int, *big.Int, *big.Int, *ethtypes.Head var blobFee *big.Int if head.ExcessBlobGas != nil { log.Info("market blob fee info", "excess blob gas", *head.ExcessBlobGas) - blobConfig, exist := r.ChainConfigMap[r.chainId.Uint64()] + blobConfig, exist := types.ChainConfigMap[r.chainId.Uint64()] if !exist { blobConfig = types.DefaultBlobConfig } @@ -1641,9 +1651,9 @@ func (r *Rollup) ReSubmitTx(resend bool, tx *ethtypes.Transaction) (*ethtypes.Tr }) case ethtypes.BlobTxType: sidecar := tx.BlobTxSidecar() - version := r.DetermineBlobVersion(head) + version := types.DetermineBlobVersion(head, r.chainId.Uint64()) if sidecar.Version == ethtypes.BlobSidecarVersion0 && version == ethtypes.BlobSidecarVersion1 { - err = sidecar.ToV1() + err = types.BlobSidecarVersionToV1(sidecar) if err != nil { return nil, err } @@ -1917,17 +1927,3 @@ func (r *Rollup) CancelTx(tx *ethtypes.Transaction) (*ethtypes.Transaction, erro return newTx, nil } - -func (r *Rollup) DetermineBlobVersion(head *ethtypes.Header) byte { - if head == nil { - return ethtypes.BlobSidecarVersion0 - } - blobConfig, exist := r.ChainConfigMap[r.chainId.Uint64()] - if !exist { - blobConfig = types.DefaultBlobConfig - } - if blobConfig.OsakaTime != nil && blobConfig.IsOsaka(head.Number, head.Time) { - return ethtypes.BlobSidecarVersion1 - } - return ethtypes.BlobSidecarVersion0 -} diff --git a/tx-submitter/types/blob.go b/tx-submitter/types/blob.go new file mode 100644 index 00000000..9d90cb61 --- /dev/null +++ b/tx-submitter/types/blob.go @@ -0,0 +1,76 @@ +package types + +import ( + "crypto/sha256" + + "github.com/morph-l2/go-ethereum/common" + ethtypes "github.com/morph-l2/go-ethereum/core/types" + "github.com/morph-l2/go-ethereum/crypto/kzg4844" +) + +// BlobHashes computes the blob hashes of the given blobs. +func BlobHashes(blobs []kzg4844.Blob, commitments []kzg4844.Commitment) []common.Hash { + hasher := sha256.New() + h := make([]common.Hash, len(commitments)) + for i := range blobs { + h[i] = kzg4844.CalcBlobHashV1(hasher, &commitments[i]) + } + return h +} + +func MakeBlobProof(blobs []kzg4844.Blob, commitment []kzg4844.Commitment) (p []kzg4844.Proof, err error) { + p = make([]kzg4844.Proof, 0, len(blobs)) + for i, _ := range blobs { + p[i], err = kzg4844.ComputeBlobProof(&blobs[i], commitment[i]) + if err != nil { + return nil, err + } + } + return +} + +func MakeCellProof(blobs []kzg4844.Blob) ([]kzg4844.Proof, error) { + proofs := make([]kzg4844.Proof, 0, len(blobs)*kzg4844.CellProofsPerBlob) + for _, blob := range blobs { + cellProofs, err := kzg4844.ComputeCellProofs(&blob) + if err != nil { + return nil, err + } + proofs = append(proofs, cellProofs...) + } + return proofs, nil +} + +func DetermineBlobVersion(head *ethtypes.Header, chainID uint64) byte { + if head == nil { + return ethtypes.BlobSidecarVersion0 + } + blobConfig, exist := ChainConfigMap[chainID] + if !exist { + blobConfig = DefaultBlobConfig + } + if blobConfig.OsakaTime != nil && blobConfig.IsOsaka(head.Number, head.Time) { + return ethtypes.BlobSidecarVersion1 + } + return ethtypes.BlobSidecarVersion0 +} + +// BlobSidecarVersionToV1 converts the BlobSidecar to version 1, attaching the cell proofs. +func BlobSidecarVersionToV1(sc *ethtypes.BlobTxSidecar) error { + if sc.Version == ethtypes.BlobSidecarVersion1 { + return nil + } + if sc.Version == ethtypes.BlobSidecarVersion0 { + proofs := make([]kzg4844.Proof, 0, len(sc.Blobs)*kzg4844.CellProofsPerBlob) + for _, blob := range sc.Blobs { + cellProofs, err := kzg4844.ComputeCellProofs(&blob) + if err != nil { + return err + } + proofs = append(proofs, cellProofs...) + } + sc.Version = ethtypes.BlobSidecarVersion1 + sc.Proofs = proofs + } + return nil +} From 5fe00c1d16d00af2eedf42b70c2352fcb3f8f560 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 17:48:36 +0800 Subject: [PATCH 05/12] update --- tx-submitter/services/rollup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tx-submitter/services/rollup.go b/tx-submitter/services/rollup.go index e27f8637..22d3125f 100644 --- a/tx-submitter/services/rollup.go +++ b/tx-submitter/services/rollup.go @@ -1161,7 +1161,7 @@ func (r *Rollup) createBlobTx(batch *eth.RPCRollupBatch, nonce, gas uint64, tip, } switch types.DetermineBlobVersion(head, r.chainId.Uint64()) { case ethtypes.BlobSidecarVersion0: - sidecar.Version = ethtypes.BlobSidecarVersion1 + sidecar.Version = ethtypes.BlobSidecarVersion0 proof, err := types.MakeBlobProof(sidecar.Blobs, sidecar.Commitments) if err != nil { return nil, fmt.Errorf("gen blob proof failed %v", err) From 9ce45b445dd93f85b4fa1059b08c1c352bf18dc4 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 17:52:05 +0800 Subject: [PATCH 06/12] fix blob proof bug --- tx-submitter/types/blob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tx-submitter/types/blob.go b/tx-submitter/types/blob.go index 9d90cb61..6809c19f 100644 --- a/tx-submitter/types/blob.go +++ b/tx-submitter/types/blob.go @@ -19,7 +19,7 @@ func BlobHashes(blobs []kzg4844.Blob, commitments []kzg4844.Commitment) []common } func MakeBlobProof(blobs []kzg4844.Blob, commitment []kzg4844.Commitment) (p []kzg4844.Proof, err error) { - p = make([]kzg4844.Proof, 0, len(blobs)) + p = make([]kzg4844.Proof, len(blobs)) for i, _ := range blobs { p[i], err = kzg4844.ComputeBlobProof(&blobs[i], commitment[i]) if err != nil { From faa837f13df213cf27366291ebd1e8c0103eebc6 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 17:58:31 +0800 Subject: [PATCH 07/12] add test for make blob proof check --- tx-submitter/types/blob.go | 9 ++- tx-submitter/types/blob_test.go | 138 ++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 tx-submitter/types/blob_test.go diff --git a/tx-submitter/types/blob.go b/tx-submitter/types/blob.go index 6809c19f..32b62c31 100644 --- a/tx-submitter/types/blob.go +++ b/tx-submitter/types/blob.go @@ -18,15 +18,16 @@ func BlobHashes(blobs []kzg4844.Blob, commitments []kzg4844.Commitment) []common return h } -func MakeBlobProof(blobs []kzg4844.Blob, commitment []kzg4844.Commitment) (p []kzg4844.Proof, err error) { - p = make([]kzg4844.Proof, len(blobs)) +func MakeBlobProof(blobs []kzg4844.Blob, commitment []kzg4844.Commitment) ([]kzg4844.Proof, error) { + proofs := make([]kzg4844.Proof, len(blobs)) for i, _ := range blobs { - p[i], err = kzg4844.ComputeBlobProof(&blobs[i], commitment[i]) + proof, err := kzg4844.ComputeBlobProof(&blobs[i], commitment[i]) if err != nil { return nil, err } + proofs[i] = proof } - return + return proofs, nil } func MakeCellProof(blobs []kzg4844.Blob) ([]kzg4844.Proof, error) { diff --git a/tx-submitter/types/blob_test.go b/tx-submitter/types/blob_test.go new file mode 100644 index 00000000..2c3351a2 --- /dev/null +++ b/tx-submitter/types/blob_test.go @@ -0,0 +1,138 @@ +package types + +import ( + "crypto/rand" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + gokzg4844 "github.com/crate-crypto/go-eth-kzg" + "github.com/morph-l2/go-ethereum/crypto/kzg4844" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// randFieldElement generates a random valid field element for BLS12-381 +func randFieldElement() [32]byte { + bytes := make([]byte, 32) + _, err := rand.Read(bytes) + if err != nil { + panic("failed to get random field element") + } + var r fr.Element + r.SetBytes(bytes) + return gokzg4844.SerializeScalar(r) +} + +// randBlob creates a random valid blob +func randBlob() *kzg4844.Blob { + var blob kzg4844.Blob + for i := 0; i < len(blob); i += gokzg4844.SerializedScalarSize { + fieldElementBytes := randFieldElement() + copy(blob[i:i+gokzg4844.SerializedScalarSize], fieldElementBytes[:]) + } + return &blob +} + +// emptyBlob creates an empty (zero) blob +func emptyBlob() *kzg4844.Blob { + return new(kzg4844.Blob) +} + +func TestMakeBlobProof_SingleBlob(t *testing.T) { + // Create a single blob + blob := randBlob() + + // Generate commitment from blob + commitment, err := kzg4844.BlobToCommitment(blob) + require.NoError(t, err, "should create commitment from blob") + + // Generate proofs using MakeBlobProof + blobs := []kzg4844.Blob{*blob} + commitments := []kzg4844.Commitment{commitment} + + proofs, err := MakeBlobProof(blobs, commitments) + require.NoError(t, err, "should generate proofs") + require.Len(t, proofs, 1, "should generate one proof for one blob") + + // Verify the proof + err = kzg4844.VerifyBlobProof(blob, commitment, proofs[0]) + assert.NoError(t, err, "proof should verify successfully") +} + +func TestMakeBlobProof_MultipleBlobs(t *testing.T) { + // Create multiple blobs + numBlobs := 3 + blobs := make([]kzg4844.Blob, numBlobs) + commitments := make([]kzg4844.Commitment, numBlobs) + + // Generate commitments for each blob + for i := 0; i < numBlobs; i++ { + blob := randBlob() + blobs[i] = *blob + + commitment, err := kzg4844.BlobToCommitment(blob) + require.NoError(t, err, "should create commitment from blob") + commitments[i] = commitment + } + + // Generate proofs using MakeBlobProof + proofs, err := MakeBlobProof(blobs, commitments) + require.NoError(t, err, "should generate proofs") + require.Len(t, proofs, numBlobs, "should generate one proof per blob") + + // Verify each proof + for i := 0; i < numBlobs; i++ { + err = kzg4844.VerifyBlobProof(&blobs[i], commitments[i], proofs[i]) + assert.NoError(t, err, "proof %d should verify successfully", i) + } +} + +func TestMakeBlobProof_EmptyBlob(t *testing.T) { + // Test with empty (zero) blob + blob := emptyBlob() + + commitment, err := kzg4844.BlobToCommitment(blob) + require.NoError(t, err, "should create commitment from empty blob") + + blobs := []kzg4844.Blob{*blob} + commitments := []kzg4844.Commitment{commitment} + + proofs, err := MakeBlobProof(blobs, commitments) + require.NoError(t, err, "should generate proof for empty blob") + require.Len(t, proofs, 1, "should generate one proof") + + // Verify the proof + err = kzg4844.VerifyBlobProof(blob, commitment, proofs[0]) + assert.NoError(t, err, "proof for empty blob should verify successfully") +} + +func TestMakeBlobProof_InvalidCommitment(t *testing.T) { + // Test with blob and mismatched commitment + blob := randBlob() + // Create a different blob and its commitment + otherBlob := randBlob() + wrongCommitment, err := kzg4844.BlobToCommitment(otherBlob) + require.NoError(t, err) + + blobs := []kzg4844.Blob{*blob} + commitments := []kzg4844.Commitment{wrongCommitment} + + // MakeBlobProof should succeed (it doesn't validate commitment matches blob) + proofs, err := MakeBlobProof(blobs, commitments) + require.NoError(t, err, "MakeBlobProof should succeed even with wrong commitment") + require.Len(t, proofs, 1) + + // But verification should fail + err = kzg4844.VerifyBlobProof(blob, wrongCommitment, proofs[0]) + assert.Error(t, err, "verification should fail with mismatched commitment") +} + +func TestMakeBlobProof_EmptySlice(t *testing.T) { + // Test with empty slices + blobs := []kzg4844.Blob{} + commitments := []kzg4844.Commitment{} + + proofs, err := MakeBlobProof(blobs, commitments) + require.NoError(t, err, "should handle empty slices") + require.Len(t, proofs, 0, "should return empty proofs slice") +} From 426e65d6772a7c11d068197bad6c1a85fdba5e86 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 18:00:52 +0800 Subject: [PATCH 08/12] update --- tx-submitter/types/blob_test.go | 135 ++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/tx-submitter/types/blob_test.go b/tx-submitter/types/blob_test.go index 2c3351a2..8f5beae7 100644 --- a/tx-submitter/types/blob_test.go +++ b/tx-submitter/types/blob_test.go @@ -136,3 +136,138 @@ func TestMakeBlobProof_EmptySlice(t *testing.T) { require.NoError(t, err, "should handle empty slices") require.Len(t, proofs, 0, "should return empty proofs slice") } + +func TestMakeCellProof_SingleBlob(t *testing.T) { + // Create a single blob + blob := randBlob() + + // Generate commitment from blob + commitment, err := kzg4844.BlobToCommitment(blob) + require.NoError(t, err, "should create commitment from blob") + + // Generate cell proofs using MakeCellProof + blobs := []kzg4844.Blob{*blob} + proofs, err := MakeCellProof(blobs) + require.NoError(t, err, "should generate cell proofs") + require.Len(t, proofs, kzg4844.CellProofsPerBlob, "should generate %d proofs for one blob", kzg4844.CellProofsPerBlob) + + // Verify the cell proofs + err = kzg4844.VerifyCellProofs(blobs, []kzg4844.Commitment{commitment}, proofs) + assert.NoError(t, err, "cell proofs should verify successfully") +} + +func TestMakeCellProof_MultipleBlobs(t *testing.T) { + // Create multiple blobs + numBlobs := 3 + blobs := make([]kzg4844.Blob, numBlobs) + commitments := make([]kzg4844.Commitment, numBlobs) + + // Generate commitments for each blob + for i := 0; i < numBlobs; i++ { + blob := randBlob() + blobs[i] = *blob + + commitment, err := kzg4844.BlobToCommitment(blob) + require.NoError(t, err, "should create commitment from blob") + commitments[i] = commitment + } + + // Generate cell proofs using MakeCellProof + proofs, err := MakeCellProof(blobs) + require.NoError(t, err, "should generate cell proofs") + expectedProofCount := numBlobs * kzg4844.CellProofsPerBlob + require.Len(t, proofs, expectedProofCount, "should generate %d proofs for %d blobs", expectedProofCount, numBlobs) + + // Verify all cell proofs + err = kzg4844.VerifyCellProofs(blobs, commitments, proofs) + assert.NoError(t, err, "cell proofs should verify successfully") +} + +func TestMakeCellProof_EmptyBlob(t *testing.T) { + // Test with empty (zero) blob + blob := emptyBlob() + + commitment, err := kzg4844.BlobToCommitment(blob) + require.NoError(t, err, "should create commitment from empty blob") + + blobs := []kzg4844.Blob{*blob} + proofs, err := MakeCellProof(blobs) + require.NoError(t, err, "should generate cell proofs for empty blob") + require.Len(t, proofs, kzg4844.CellProofsPerBlob, "should generate %d proofs", kzg4844.CellProofsPerBlob) + + // Verify the cell proofs + err = kzg4844.VerifyCellProofs(blobs, []kzg4844.Commitment{commitment}, proofs) + assert.NoError(t, err, "cell proofs for empty blob should verify successfully") +} + +func TestMakeCellProof_EmptySlice(t *testing.T) { + // Test with empty slice + blobs := []kzg4844.Blob{} + + proofs, err := MakeCellProof(blobs) + require.NoError(t, err, "should handle empty slice") + require.Len(t, proofs, 0, "should return empty proofs slice") +} + +func TestMakeCellProof_ProofCount(t *testing.T) { + // Test that each blob generates exactly CellProofsPerBlob proofs + testCases := []struct { + name string + numBlobs int + }{ + {"single blob", 1}, + {"two blobs", 2}, + {"five blobs", 5}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + blobs := make([]kzg4844.Blob, tc.numBlobs) + for i := 0; i < tc.numBlobs; i++ { + blob := randBlob() + blobs[i] = *blob + } + + proofs, err := MakeCellProof(blobs) + require.NoError(t, err) + expectedCount := tc.numBlobs * kzg4844.CellProofsPerBlob + assert.Equal(t, expectedCount, len(proofs), "should generate %d proofs for %d blobs", expectedCount, tc.numBlobs) + }) + } +} + +func TestMakeCellProof_ProofGrouping(t *testing.T) { + // Test that proofs are correctly grouped by blob + // Each blob's proofs should be consecutive in the proofs slice + numBlobs := 2 + blobs := make([]kzg4844.Blob, numBlobs) + commitments := make([]kzg4844.Commitment, numBlobs) + + for i := 0; i < numBlobs; i++ { + blob := randBlob() + blobs[i] = *blob + + commitment, err := kzg4844.BlobToCommitment(blob) + require.NoError(t, err) + commitments[i] = commitment + } + + // Generate all proofs + allProofs, err := MakeCellProof(blobs) + require.NoError(t, err) + + // Verify each blob's proofs individually + for i := 0; i < numBlobs; i++ { + startIdx := i * kzg4844.CellProofsPerBlob + endIdx := startIdx + kzg4844.CellProofsPerBlob + blobProofs := allProofs[startIdx:endIdx] + + // Verify this blob's proofs + err = kzg4844.VerifyCellProofs( + []kzg4844.Blob{blobs[i]}, + []kzg4844.Commitment{commitments[i]}, + blobProofs, + ) + assert.NoError(t, err, "blob %d proofs should verify successfully", i) + } +} From 94fbdaee6315d1b53cc90576fd811faae99b0ee7 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Tue, 4 Nov 2025 18:04:47 +0800 Subject: [PATCH 09/12] update --- tx-submitter/types/blob.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tx-submitter/types/blob.go b/tx-submitter/types/blob.go index 32b62c31..b924a554 100644 --- a/tx-submitter/types/blob.go +++ b/tx-submitter/types/blob.go @@ -62,13 +62,9 @@ func BlobSidecarVersionToV1(sc *ethtypes.BlobTxSidecar) error { return nil } if sc.Version == ethtypes.BlobSidecarVersion0 { - proofs := make([]kzg4844.Proof, 0, len(sc.Blobs)*kzg4844.CellProofsPerBlob) - for _, blob := range sc.Blobs { - cellProofs, err := kzg4844.ComputeCellProofs(&blob) - if err != nil { - return err - } - proofs = append(proofs, cellProofs...) + proofs, err := MakeCellProof(sc.Blobs) + if err != nil { + return err } sc.Version = ethtypes.BlobSidecarVersion1 sc.Proofs = proofs From 30e196125f15fadfa2ee732504fe7547bae4de11 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Wed, 5 Nov 2025 14:15:52 +0800 Subject: [PATCH 10/12] fix lint --- tx-submitter/types/blob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tx-submitter/types/blob.go b/tx-submitter/types/blob.go index b924a554..d0da2b6b 100644 --- a/tx-submitter/types/blob.go +++ b/tx-submitter/types/blob.go @@ -20,7 +20,7 @@ func BlobHashes(blobs []kzg4844.Blob, commitments []kzg4844.Commitment) []common func MakeBlobProof(blobs []kzg4844.Blob, commitment []kzg4844.Commitment) ([]kzg4844.Proof, error) { proofs := make([]kzg4844.Proof, len(blobs)) - for i, _ := range blobs { + for i := range blobs { proof, err := kzg4844.ComputeBlobProof(&blobs[i], commitment[i]) if err != nil { return nil, err From 33514df25606862939ff4dda712184e618c6c926 Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Thu, 6 Nov 2025 10:27:08 +0800 Subject: [PATCH 11/12] update mainnet fork time --- tx-submitter/types/blob_config_test.go | 13 +++++++++++++ tx-submitter/types/blob_params.go | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/tx-submitter/types/blob_config_test.go b/tx-submitter/types/blob_config_test.go index a1a5f3df..2fbf5f0b 100644 --- a/tx-submitter/types/blob_config_test.go +++ b/tx-submitter/types/blob_config_test.go @@ -1,6 +1,7 @@ package types import ( + "github.com/morph-l2/go-ethereum/consensus/misc/eip4844" "strconv" "testing" @@ -256,3 +257,15 @@ func TestGetBlobFeeDenominator_ForkPriority(t *testing.T) { result = GetBlobFeeDenominator(config, osakaTime) assert.Equal(t, DefaultOsakaBlobConfig.UpdateFraction, result.Uint64()) } + +func TestCal(t *testing.T) { + // Test that higher priority forks are checked first + config := HoodiChainConfig + assert.NotNil(t, config) + + timeNow := uint64(1762395060) + result := GetBlobFeeDenominator(config, timeNow) + t.Log(result) + fee := eip4844.CalcBlobFee(170172092, result.Uint64()) + t.Log(fee.Uint64()) +} diff --git a/tx-submitter/types/blob_params.go b/tx-submitter/types/blob_params.go index 0e51f9a3..4ebe43a4 100644 --- a/tx-submitter/types/blob_params.go +++ b/tx-submitter/types/blob_params.go @@ -25,8 +25,14 @@ var ( LondonBlock: big.NewInt(12_965_000), CancunTime: newUint64(1710338135), PragueTime: newUint64(1746612311), + OsakaTime: newUint64(1764798551), + BPO1Time: newUint64(1765290071), + BPO2Time: newUint64(1767747671), Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, + Osaka: DefaultOsakaBlobConfig, + BPO1: DefaultBPO1BlobConfig, + BPO2: DefaultBPO2BlobConfig, Default: DefaultOsakaBlobConfig, } From b8f06ed537b3ce86adb4c038447692ad47df2a8b Mon Sep 17 00:00:00 2001 From: kukoomomo Date: Thu, 6 Nov 2025 10:28:11 +0800 Subject: [PATCH 12/12] fix import fmt --- tx-submitter/types/blob_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tx-submitter/types/blob_config_test.go b/tx-submitter/types/blob_config_test.go index 2fbf5f0b..cfa4e79a 100644 --- a/tx-submitter/types/blob_config_test.go +++ b/tx-submitter/types/blob_config_test.go @@ -1,10 +1,10 @@ package types import ( - "github.com/morph-l2/go-ethereum/consensus/misc/eip4844" "strconv" "testing" + "github.com/morph-l2/go-ethereum/consensus/misc/eip4844" "github.com/stretchr/testify/assert" )