bsc/core/data_availability_test.go
2024-04-10 14:31:23 +08:00

437 lines
13 KiB
Go

package core
import (
"crypto/rand"
"math/big"
"testing"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
gokzg4844 "github.com/crate-crypto/go-kzg-4844"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
)
var (
emptyBlob = kzg4844.Blob{}
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
)
func TestIsDataAvailable(t *testing.T) {
hr := NewMockDAHeaderReader(params.ParliaTestChainConfig)
tests := []struct {
block *types.Block
chasingHead uint64
withSidecar bool
err bool
}{
{
block: types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
}, nil),
chasingHead: 1,
withSidecar: true,
err: false,
},
{
block: types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), nil),
}, nil),
chasingHead: 1,
withSidecar: true,
err: false,
},
{
block: types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
}, nil),
chasingHead: 1,
withSidecar: false,
err: true,
},
{
block: types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof},
}),
}, nil),
chasingHead: 1,
withSidecar: true,
err: false,
},
{
block: types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob, emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit, emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof, emptyBlobProof, emptyBlobProof},
}),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob, emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit, emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof, emptyBlobProof, emptyBlobProof},
}),
}, nil),
chasingHead: params.MinBlocksForBlobRequests + 1,
withSidecar: true,
err: true,
},
{
block: types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(0),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
}, nil),
chasingHead: params.MinBlocksForBlobRequests + 1,
withSidecar: false,
err: false,
},
}
for i, item := range tests {
if item.withSidecar {
item.block = item.block.WithSidecars(collectBlobsFromTxs(item.block.Header(), item.block.Transactions()))
}
hr.setChasingHead(item.chasingHead)
err := IsDataAvailable(hr, item.block)
if item.err {
require.Error(t, err, i)
t.Log(err)
continue
}
require.NoError(t, err, i)
}
}
func TestCheckDataAvailableInBatch(t *testing.T) {
hr := NewMockDAHeaderReader(params.ParliaTestChainConfig)
tests := []struct {
chain types.Blocks
err bool
index int
}{
{
chain: types.Blocks{
types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof},
}),
}, nil),
types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(2),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof},
}),
}, nil),
},
err: false,
},
{
chain: types.Blocks{
types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
}, nil),
types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(2),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
}, nil),
types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(3),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}),
}, nil),
},
err: true,
index: 1,
},
{
chain: types.Blocks{
types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), nil),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob, emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit, emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof, emptyBlobProof, emptyBlobProof},
}),
createMockDATx(hr.Config(), &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob, emptyBlob, emptyBlob, emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit, emptyBlobCommit, emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof, emptyBlobProof, emptyBlobProof},
}),
}, nil),
},
err: true,
index: 0,
},
}
for i, item := range tests {
for j, block := range item.chain {
item.chain[j] = block.WithSidecars(collectBlobsFromTxs(block.Header(), block.Transactions()))
}
index, err := CheckDataAvailableInBatch(hr, item.chain)
if item.err {
t.Log(index, err)
require.Error(t, err, i)
require.Equal(t, item.index, index, i)
continue
}
require.NoError(t, err, i)
}
}
func BenchmarkEmptySidecarDAChecking(b *testing.B) {
hr := NewMockDAHeaderReader(params.ParliaTestChainConfig)
block := types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), emptySidecar()),
createMockDATx(hr.Config(), emptySidecar()),
createMockDATx(hr.Config(), emptySidecar()),
createMockDATx(hr.Config(), emptySidecar()),
createMockDATx(hr.Config(), emptySidecar()),
createMockDATx(hr.Config(), emptySidecar()),
}, nil)
block = block.WithSidecars(collectBlobsFromTxs(block.Header(), block.Transactions()))
b.ResetTimer()
for i := 0; i < b.N; i++ {
IsDataAvailable(hr, block)
}
}
func BenchmarkRandomSidecarDAChecking(b *testing.B) {
hr := NewMockDAHeaderReader(params.ParliaTestChainConfig)
const count = 10
blocks := make([]*types.Block, count)
for i := 0; i < len(blocks); i++ {
block := types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
}).WithBody(types.Transactions{
createMockDATx(hr.Config(), randomSidecar()),
createMockDATx(hr.Config(), randomSidecar()),
createMockDATx(hr.Config(), randomSidecar()),
createMockDATx(hr.Config(), randomSidecar()),
createMockDATx(hr.Config(), randomSidecar()),
createMockDATx(hr.Config(), randomSidecar()),
}, nil)
block = block.WithSidecars(collectBlobsFromTxs(block.Header(), block.Transactions()))
blocks[i] = block
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
IsDataAvailable(hr, blocks[i%count])
}
}
func collectBlobsFromTxs(header *types.Header, txs types.Transactions) types.BlobSidecars {
sidecars := make(types.BlobSidecars, 0, len(txs))
for i, tx := range txs {
sidecar := types.NewBlobSidecarFromTx(tx)
if sidecar == nil {
continue
}
sidecar.TxIndex = uint64(i)
sidecar.TxHash = tx.Hash()
sidecar.BlockNumber = header.Number
sidecar.BlockHash = header.Hash()
sidecars = append(sidecars, sidecar)
}
return sidecars
}
type mockDAHeaderReader struct {
config *params.ChainConfig
chasingHead uint64
}
func NewMockDAHeaderReader(config *params.ChainConfig) *mockDAHeaderReader {
return &mockDAHeaderReader{
config: config,
chasingHead: 0,
}
}
func (r *mockDAHeaderReader) setChasingHead(h uint64) {
r.chasingHead = h
}
func (r *mockDAHeaderReader) Config() *params.ChainConfig {
return r.config
}
func (r *mockDAHeaderReader) CurrentHeader() *types.Header {
return &types.Header{
Number: new(big.Int).SetUint64(r.chasingHead),
}
}
func (r *mockDAHeaderReader) ChasingHead() *types.Header {
return &types.Header{
Number: new(big.Int).SetUint64(r.chasingHead),
}
}
func (r *mockDAHeaderReader) GenesisHeader() *types.Header {
panic("not supported")
}
func (r *mockDAHeaderReader) GetHeader(hash common.Hash, number uint64) *types.Header {
panic("not supported")
}
func (r *mockDAHeaderReader) GetHeaderByNumber(number uint64) *types.Header {
panic("not supported")
}
func (r *mockDAHeaderReader) GetHeaderByHash(hash common.Hash) *types.Header {
panic("not supported")
}
func (r *mockDAHeaderReader) GetTd(hash common.Hash, number uint64) *big.Int {
panic("not supported")
}
func (r *mockDAHeaderReader) GetHighestVerifiedHeader() *types.Header {
panic("not supported")
}
func createMockDATx(config *params.ChainConfig, sidecar *types.BlobTxSidecar) *types.Transaction {
if sidecar == nil {
tx := &types.DynamicFeeTx{
ChainID: config.ChainID,
Nonce: 0,
GasTipCap: big.NewInt(22),
GasFeeCap: big.NewInt(5),
Gas: 25000,
To: &common.Address{0x03, 0x04, 0x05},
Value: big.NewInt(99),
Data: make([]byte, 50),
}
return types.NewTx(tx)
}
tx := &types.BlobTx{
ChainID: uint256.MustFromBig(config.ChainID),
Nonce: 5,
GasTipCap: uint256.NewInt(22),
GasFeeCap: uint256.NewInt(5),
Gas: 25000,
To: common.Address{0x03, 0x04, 0x05},
Value: uint256.NewInt(99),
Data: make([]byte, 50),
BlobFeeCap: uint256.NewInt(15),
BlobHashes: sidecar.BlobHashes(),
Sidecar: sidecar,
}
return types.NewTx(tx)
}
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)
}
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
}
func randomSidecar() *types.BlobTxSidecar {
blob := randBlob()
commitment, _ := kzg4844.BlobToCommitment(blob)
proof, _ := kzg4844.ComputeBlobProof(blob, commitment)
return &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{blob},
Commitments: []kzg4844.Commitment{commitment},
Proofs: []kzg4844.Proof{proof},
}
}
func emptySidecar() *types.BlobTxSidecar {
return &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}
}