From cadbe69f6dec7e3e0e829da14385c8740a6629e1 Mon Sep 17 00:00:00 2001 From: chiphamskymavis Date: Tue, 3 Dec 2024 17:29:52 +0700 Subject: [PATCH] core/vm: Add Venoki header's fields to verify double-sign --- core/types/block.go | 198 ++++++++-- core/vm/consortium_precompiled_contracts.go | 81 ++-- .../consortium_precompiled_contracts_test.go | 369 +++++++++++++++--- 3 files changed, 553 insertions(+), 95 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 5d464ac37c..59b65da3df 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -66,28 +66,39 @@ func (n *BlockNonce) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedText("BlockNonce", input, n[:]) } -// BlockHeader is used for consortium precompiled contracts -type BlockHeader struct { - ChainId *big.Int `abi:"chainId"` - ParentHash [32]uint8 `abi:"parentHash"` - OmmersHash [32]uint8 `abi:"ommersHash"` - Benificiary common.Address `abi:"coinbase"` - StateRoot [32]uint8 `abi:"stateRoot"` - TransactionsRoot [32]uint8 `abi:"transactionsRoot"` - ReceiptsRoot [32]uint8 `abi:"receiptsRoot"` - LogsBloom [256]uint8 `abi:"logsBloom"` - Difficulty *big.Int `abi:"difficulty"` - Number *big.Int `abi:"number"` - GasLimit uint64 `abi:"gasLimit"` - GasUsed uint64 `abi:"gasUsed"` - Timestamp uint64 `abi:"timestamp"` - ExtraData []byte `abi:"extraData"` - MixHash [32]uint8 `abi:"mixHash"` - Nonce uint64 `abi:"nonce"` -} +type BlockHeader interface { + GetNumber() *big.Int + GetChainId() *big.Int + GetExtraData() []byte + GetBenificiary() common.Address + ToHeader() *Header + Bytes(verifyHeadersAbi string, methodName string) ([]byte, error) +} + +// FromHeader creates a BlockHeader from a Header, this due to the lack of null or optional fields in the abi package +func FromHeader(header *Header, chainId *big.Int, isVenoki bool) BlockHeader { + if !isVenoki { + blockHeader := &BlockHeaderV1{ + ChainId: chainId, + Difficulty: header.Difficulty, + Number: header.Number, + GasLimit: header.GasLimit, + GasUsed: header.GasUsed, + Timestamp: header.Time, + Nonce: header.Nonce.Uint64(), + ExtraData: header.Extra, + LogsBloom: header.Bloom, + Benificiary: header.Coinbase, + } + copy(blockHeader.ParentHash[:], header.ParentHash.Bytes()) + copy(blockHeader.StateRoot[:], header.Root.Bytes()) + copy(blockHeader.TransactionsRoot[:], header.TxHash.Bytes()) + copy(blockHeader.ReceiptsRoot[:], header.ReceiptHash.Bytes()) + copy(blockHeader.MixHash[:], header.MixDigest.Bytes()) + return blockHeader + } -func FromHeader(header *Header, chainId *big.Int) *BlockHeader { - blockHeader := &BlockHeader{ + blockHeader := &BlockHeaderV2{ ChainId: chainId, Difficulty: header.Difficulty, Number: header.Number, @@ -98,20 +109,64 @@ func FromHeader(header *Header, chainId *big.Int) *BlockHeader { ExtraData: header.Extra, LogsBloom: header.Bloom, Benificiary: header.Coinbase, + BaseFee: header.BaseFee, } copy(blockHeader.ParentHash[:], header.ParentHash.Bytes()) copy(blockHeader.StateRoot[:], header.Root.Bytes()) copy(blockHeader.TransactionsRoot[:], header.TxHash.Bytes()) copy(blockHeader.ReceiptsRoot[:], header.ReceiptHash.Bytes()) copy(blockHeader.MixHash[:], header.MixDigest.Bytes()) + if header.BlobGasUsed != nil { + blockHeader.BlobGasUsed = *header.BlobGasUsed + } + if header.ExcessBlobGas != nil { + blockHeader.ExcessBlobGas = *header.ExcessBlobGas + } return blockHeader } -func (b *BlockHeader) Bytes(consortiumVerifyHeadersAbi string, methodName string) ([]byte, error) { +// BlockHeaderV1 is used for consortium precompiled contracts +type BlockHeaderV1 struct { + ChainId *big.Int `abi:"chainId"` + ParentHash [32]uint8 `abi:"parentHash"` + OmmersHash [32]uint8 `abi:"ommersHash"` + Benificiary common.Address `abi:"coinbase"` + StateRoot [32]uint8 `abi:"stateRoot"` + TransactionsRoot [32]uint8 `abi:"transactionsRoot"` + ReceiptsRoot [32]uint8 `abi:"receiptsRoot"` + LogsBloom [256]uint8 `abi:"logsBloom"` + Difficulty *big.Int `abi:"difficulty"` + Number *big.Int `abi:"number"` + GasLimit uint64 `abi:"gasLimit"` + GasUsed uint64 `abi:"gasUsed"` + Timestamp uint64 `abi:"timestamp"` + ExtraData []byte `abi:"extraData"` + MixHash [32]uint8 `abi:"mixHash"` + Nonce uint64 `abi:"nonce"` +} + +func (b *BlockHeaderV1) GetNumber() *big.Int { + return b.Number +} + +func (b *BlockHeaderV1) GetChainId() *big.Int { + return b.ChainId +} + +func (b *BlockHeaderV1) GetExtraData() []byte { + return b.ExtraData +} + +func (b *BlockHeaderV1) GetBenificiary() common.Address { + return b.Benificiary +} + +func (b *BlockHeaderV1) Bytes(consortiumVerifyHeadersAbi string, methodName string) ([]byte, error) { pAbi, _ := abi.JSON(strings.NewReader(consortiumVerifyHeadersAbi)) bloom := BytesToBloom(b.LogsBloom[:]) - var uncles [32]uint8 - return pAbi.Methods[methodName].Inputs.Pack( + var uncles [32]uint8 // this field only for POW + + args := []any{ b.ChainId, b.ParentHash, uncles, @@ -128,10 +183,11 @@ func (b *BlockHeader) Bytes(consortiumVerifyHeadersAbi string, methodName string b.ExtraData, b.MixHash, b.Nonce, - ) + } + return pAbi.Methods[methodName].Inputs.Pack(args...) } -func (b *BlockHeader) ToHeader() *Header { +func (b *BlockHeaderV1) ToHeader() *Header { return &Header{ ParentHash: common.BytesToHash(b.ParentHash[:]), Root: common.BytesToHash(b.StateRoot[:]), @@ -150,6 +206,96 @@ func (b *BlockHeader) ToHeader() *Header { } } +// BlockHeaderV2 is used for consortium precompiled contracts after Venoki hard fork +type BlockHeaderV2 struct { + ChainId *big.Int `abi:"chainId"` + ParentHash [32]uint8 `abi:"parentHash"` + OmmersHash [32]uint8 `abi:"ommersHash"` + Benificiary common.Address `abi:"coinbase"` + StateRoot [32]uint8 `abi:"stateRoot"` + TransactionsRoot [32]uint8 `abi:"transactionsRoot"` + ReceiptsRoot [32]uint8 `abi:"receiptsRoot"` + LogsBloom [256]uint8 `abi:"logsBloom"` + Difficulty *big.Int `abi:"difficulty"` + Number *big.Int `abi:"number"` + GasLimit uint64 `abi:"gasLimit"` + GasUsed uint64 `abi:"gasUsed"` + Timestamp uint64 `abi:"timestamp"` + ExtraData []byte `abi:"extraData"` + MixHash [32]uint8 `abi:"mixHash"` + Nonce uint64 `abi:"nonce"` + BaseFee *big.Int `abi:"baseFee"` + BlobGasUsed uint64 `abi:"blobGasUsed"` + ExcessBlobGas uint64 `abi:"excessBlobGas"` +} + +func (b *BlockHeaderV2) GetNumber() *big.Int { + return b.Number +} + +func (b *BlockHeaderV2) GetChainId() *big.Int { + return b.ChainId +} + +func (b *BlockHeaderV2) GetExtraData() []byte { + return b.ExtraData +} + +func (b *BlockHeaderV2) GetBenificiary() common.Address { + return b.Benificiary +} + +func (b *BlockHeaderV2) Bytes(consortiumVerifyHeadersAbi string, methodName string) ([]byte, error) { + pAbi, _ := abi.JSON(strings.NewReader(consortiumVerifyHeadersAbi)) + bloom := BytesToBloom(b.LogsBloom[:]) + var uncles [32]uint8 // this field only for POW + + args := []any{ + b.ChainId, + b.ParentHash, + uncles, + b.Benificiary, + b.StateRoot, + b.TransactionsRoot, + b.ReceiptsRoot, + bloom.Bytes(), + b.Difficulty, + b.Number, + b.GasLimit, + b.GasUsed, + b.Timestamp, + b.ExtraData, + b.MixHash, + b.Nonce, + b.BaseFee, + b.BlobGasUsed, + b.ExcessBlobGas, + } + return pAbi.Methods[methodName].Inputs.Pack(args...) +} + +func (b *BlockHeaderV2) ToHeader() *Header { + return &Header{ + ParentHash: common.BytesToHash(b.ParentHash[:]), + Root: common.BytesToHash(b.StateRoot[:]), + TxHash: common.BytesToHash(b.TransactionsRoot[:]), + ReceiptHash: common.BytesToHash(b.ReceiptsRoot[:]), + Bloom: BytesToBloom(b.LogsBloom[:]), + Difficulty: b.Difficulty, + Number: b.Number, + GasLimit: b.GasLimit, + GasUsed: b.GasUsed, + Time: b.Timestamp, + Extra: b.ExtraData, + MixDigest: common.BytesToHash(b.MixHash[:]), + Nonce: EncodeNonce(b.Nonce), + Coinbase: b.Benificiary, + BaseFee: b.BaseFee, + BlobGasUsed: &b.BlobGasUsed, + ExcessBlobGas: &b.ExcessBlobGas, + } +} + //go:generate gencodec -type Header -field-override headerMarshaling -out gen_header_json.go // Header represents a block header in the Ethereum blockchain. diff --git a/core/vm/consortium_precompiled_contracts.go b/core/vm/consortium_precompiled_contracts.go index 0f8f38e763..a0b0eb3db6 100644 --- a/core/vm/consortium_precompiled_contracts.go +++ b/core/vm/consortium_precompiled_contracts.go @@ -28,6 +28,7 @@ const ( LogContract = iota SortValidator VerifyHeaders + VerifyHeadersVenoki PickValidatorSet GetDoubleSignSlashingConfig ValidateFinalityVoteProof @@ -39,6 +40,7 @@ var ( rawConsortiumLogAbi = `[{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"log","outputs":[],"stateMutability":"nonpayable","type":"function"}]` rawConsortiumSortValidatorAbi = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"sortValidators","outputs":[{"internalType":"address[]","name":"_validators","type":"address[]"}],"stateMutability":"view","type":"function"}]` rawConsortiumVerifyHeadersAbi = `[{"outputs":[],"name":"getHeader","inputs":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"bytes32","name":"ommersHash","type":"bytes32"},{"internalType":"address","name":"coinbase","type":"address"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"internalType":"bytes32","name":"transactionsRoot","type":"bytes32"},{"internalType":"bytes32","name":"receiptsRoot","type":"bytes32"},{"internalType":"uint8[256]","name":"logsBloom","type":"uint8[256]"},{"internalType":"uint256","name":"difficulty","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint64","name":"gasLimit","type":"uint64"},{"internalType":"uint64","name":"gasUsed","type":"uint64"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"bytes","name":"extraData","type":"bytes"},{"internalType":"bytes32","name":"mixHash","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"bytes","name":"header1","type":"bytes"},{"internalType":"bytes","name":"header2","type":"bytes"}],"name":"validatingDoubleSignProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]` + rawConsortiumVerifyHeadersV2Abi = `[{"outputs":[],"name":"getHeader","inputs":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"bytes32","name":"ommersHash","type":"bytes32"},{"internalType":"address","name":"coinbase","type":"address"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"internalType":"bytes32","name":"transactionsRoot","type":"bytes32"},{"internalType":"bytes32","name":"receiptsRoot","type":"bytes32"},{"internalType":"uint8[256]","name":"logsBloom","type":"uint8[256]"},{"internalType":"uint256","name":"difficulty","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint64","name":"gasLimit","type":"uint64"},{"internalType":"uint64","name":"gasUsed","type":"uint64"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"bytes","name":"extraData","type":"bytes"},{"internalType":"bytes32","name":"mixHash","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint64","name":"blobGasUsed","type":"uint64"},{"internalType":"uint64","name":"excessBlobGas","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"bytes","name":"header1","type":"bytes"},{"internalType":"bytes","name":"header2","type":"bytes"}],"name":"validatingDoubleSignProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]` rawConsortiumPickValidatorSetAbi = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address[]","name":"_candidates","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"},{"internalType":"uint256[]","name":"_trustedWeights","type":"uint256[]"},{"internalType":"uint256","name":"_maxValidatorNumber","type":"uint256"},{"internalType":"uint256","name":"_maxPrioritizedValidatorNumber","type":"uint256"}],"name":"pickValidatorSet","outputs":[{"internalType":"address[]","name":"_validators","type":"address[]"}],"stateMutability":"view","type":"function"}]` rawGetDoubleSignSlashingConfigsAbi = `[{"inputs":[],"name":"getDoubleSignSlashingConfigs","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` rawValidateFinalityVoteProofAbi = `[{"inputs":[{"internalType":"bytes","name":"voterPublicKey","type":"bytes"},{"internalType":"uint256","name":"targetBlockNumber","type":"uint256"},{"internalType":"bytes32[2]","name":"targetBlockHash","type":"bytes32[2]"},{"internalType":"bytes[][2]","name":"listOfPublicKey","type":"bytes[][2]"},{"internalType":"bytes[2]","name":"aggregatedSignature","type":"bytes[2]"}],"name":"validateFinalityVoteProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]` @@ -48,6 +50,7 @@ var ( LogContract: rawConsortiumLogAbi, SortValidator: rawConsortiumSortValidatorAbi, VerifyHeaders: rawConsortiumVerifyHeadersAbi, + VerifyHeadersVenoki: rawConsortiumVerifyHeadersV2Abi, PickValidatorSet: rawConsortiumPickValidatorSetAbi, GetDoubleSignSlashingConfig: rawGetDoubleSignSlashingConfigsAbi, ValidateFinalityVoteProof: rawValidateFinalityVoteProofAbi, @@ -455,8 +458,14 @@ func (c *consortiumVerifyHeaders) Run(input []byte) ([]byte, error) { if err := isSystemContractCaller(c.caller, c.evm); err != nil { return nil, err } - // get method, args from abi - smcAbi, method, args, err := loadMethodAndArgs(VerifyHeaders, input) + isVenoki := c.evm.chainRules.IsVenoki + contractIdx := VerifyHeaders + if isVenoki { + contractIdx = VerifyHeadersVenoki + } + + // get method, args from abi and check if method is valid + smcAbi, method, args, err := loadMethodAndArgs(contractIdx, input) if err != nil { return nil, err } @@ -470,18 +479,35 @@ func (c *consortiumVerifyHeaders) Run(input []byte) ([]byte, error) { if !ok { return nil, errors.New("invalid first argument type") } - // decode header1, header2 - var blockHeader1, blockHeader2 types.BlockHeader - if err := c.unpack(smcAbi, &blockHeader1, args[1].([]byte)); err != nil { + blockHeader1, err := c.unpackHeader(smcAbi, args[1].([]byte), isVenoki) + if err != nil { return nil, err } - if err := c.unpack(smcAbi, &blockHeader2, args[2].([]byte)); err != nil { + blockHeader2, err := c.unpackHeader(smcAbi, args[2].([]byte), isVenoki) + if err != nil { return nil, err } output := c.verify(consensusAddr, blockHeader1, blockHeader2) return smcAbi.Methods[verifyHeaders].Outputs.Pack(output) } +func (c *consortiumVerifyHeaders) unpackHeader(abi abi.ABI, input []byte, isVenoki bool) (types.BlockHeader, error) { + if isVenoki { + var blochHeader types.BlockHeaderV2 + if err := c.unpack(abi, &blochHeader, input); err != nil { + return nil, err + } + + return &blochHeader, nil + } + + var blockHeader types.BlockHeaderV1 + if err := c.unpack(abi, &blockHeader, input); err != nil { + return nil, err + } + return &blockHeader, nil +} + func (c *consortiumVerifyHeaders) unpack(smcAbi abi.ABI, v interface{}, input []byte) error { // use `getHeader` abi which contains params defined in `BlockHeader` to unmarshal `input` data into `BlockHeader` args := smcAbi.Methods[getHeader].Inputs @@ -492,14 +518,15 @@ func (c *consortiumVerifyHeaders) unpack(smcAbi abi.ABI, v interface{}, input [] return args.Copy(v, output) } -func (c *consortiumVerifyHeaders) getSigner(header types.BlockHeader) (common.Address, error) { - if header.Number == nil || header.Timestamp > uint64(time.Now().Unix()) || len(header.ExtraData) < crypto.SignatureLength { +func (c *consortiumVerifyHeaders) getSigner(blockHeader types.BlockHeader) (common.Address, error) { + header := blockHeader.ToHeader() + if header.Number == nil || header.Time > uint64(time.Now().Unix()) || len(header.Extra) < crypto.SignatureLength { return common.Address{}, errors.New("invalid header") } - signature := header.ExtraData[len(header.ExtraData)-crypto.SignatureLength:] - // Recover the public key and the Ethereum address - pubkey, err := crypto.Ecrecover(SealHash(header.ToHeader(), header.ChainId).Bytes(), signature) + signature := header.Extra[len(header.Extra)-crypto.SignatureLength:] + signedHash := SealHash(blockHeader).Bytes() + pubkey, err := crypto.Ecrecover(signedHash, signature) if err != nil { return common.Address{}, err } @@ -516,20 +543,22 @@ func (c *consortiumVerifyHeaders) getSigner(header types.BlockHeader) (common.Ad return signer, nil } +// verify returns true if 2 blocks has the same signer (consensus address), same block number +// but with different seal hash func (c *consortiumVerifyHeaders) verify(consensusAddr common.Address, header1, header2 types.BlockHeader) bool { var maxOffset *big.Int // c.evm s nil in benchmark, so we skip this check in benchmark - if c.evm != nil && !c.evm.chainConfig.IsConsortiumV2(header1.Number) { + if c.evm != nil && !c.evm.chainConfig.IsConsortiumV2(header1.GetNumber()) { return false } if header1.ToHeader().ParentHash.Hex() != header2.ToHeader().ParentHash.Hex() { return false } - if len(header1.ExtraData) < crypto.SignatureLength || len(header2.ExtraData) < crypto.SignatureLength { + if len(header1.GetExtraData()) < crypto.SignatureLength || len(header2.GetExtraData()) < crypto.SignatureLength { return false } - if bytes.Equal(SealHash(header1.ToHeader(), header1.ChainId).Bytes(), SealHash(header2.ToHeader(), header2.ChainId).Bytes()) { + if bytes.Equal(SealHash(header1).Bytes(), SealHash(header2).Bytes()) { return false } signer1, err := c.getSigner(header1) @@ -545,11 +574,11 @@ func (c *consortiumVerifyHeaders) verify(consensusAddr common.Address, header1, if unmarshalledABIs[GetDoubleSignSlashingConfig] == nil { return false } - methodAbi := *unmarshalledABIs[GetDoubleSignSlashingConfig] if c.test { maxOffset = big.NewInt(doubleSigningOffsetTest) } else { + methodAbi := *unmarshalledABIs[GetDoubleSignSlashingConfig] if c.evm.chainConfig.ConsortiumV2Contracts == nil { return false } else { @@ -576,26 +605,26 @@ func (c *consortiumVerifyHeaders) verify(consensusAddr common.Address, header1, if c.evm != nil { currentBlock := c.evm.Context.BlockNumber // What if current block < header1.Number? - if currentBlock.Cmp(header1.Number) > 0 && new(big.Int).Sub(currentBlock, header1.Number).Cmp(maxOffset) > 0 { + if currentBlock.Cmp(header1.GetNumber()) > 0 && new(big.Int).Sub(currentBlock, header1.GetNumber()).Cmp(maxOffset) > 0 { return false } } return signer1.Hex() == signer2.Hex() && - signer2.Hex() == header2.Benificiary.Hex() && + signer2.Hex() == header2.GetBenificiary().Hex() && bytes.Equal(consensusAddr.Bytes(), signer1.Bytes()) } // SealHash returns the hash of a block prior to it being sealed. -func SealHash(header *types.Header, chainId *big.Int) (hash common.Hash) { +func SealHash(header types.BlockHeader) (hash common.Hash) { hasher := sha3.NewLegacyKeccak256() - encodeSigHeader(hasher, header, chainId) + encodeSigHeader(hasher, header.ToHeader(), header.GetChainId()) hasher.Sum(hash[:0]) return hash } func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) { - err := rlp.Encode(w, []interface{}{ + enc := []interface{}{ chainId, header.ParentHash, header.UncleHash, @@ -612,8 +641,16 @@ func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) { header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short header.MixDigest, header.Nonce, - }) - if err != nil { + } + if header.BaseFee != nil { + enc = append(enc, header.BaseFee) + } + // blob fields are assumed to had been verified + if header.BlobGasUsed != nil { + enc = append(enc, header.BlobGasUsed) + enc = append(enc, header.ExcessBlobGas) + } + if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } } diff --git a/core/vm/consortium_precompiled_contracts_test.go b/core/vm/consortium_precompiled_contracts_test.go index 688109040f..6fdbb6e886 100644 --- a/core/vm/consortium_precompiled_contracts_test.go +++ b/core/vm/consortium_precompiled_contracts_test.go @@ -2,6 +2,7 @@ package vm import ( "bytes" + "crypto/ecdsa" "fmt" "math/big" "math/rand" @@ -18,6 +19,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/bls/blst" blsCommon "github.com/ethereum/go-ethereum/crypto/bls/common" "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/assert" ) /* @@ -746,81 +748,178 @@ func TestConsortiumVerifyHeaders_verify(t *testing.T) { }, test: true, } - if !c.verify(header1.Coinbase, *types.FromHeader(header1, big1), *types.FromHeader(header2, big1)) { + if !c.verify(header1.Coinbase, types.FromHeader(header1, big1, false), types.FromHeader(header2, big1, false)) { t.Fatal("expected true, got false") } // Test the same headers passed into verify - if c.verify(header1.Coinbase, *types.FromHeader(header1, big1), *types.FromHeader(header1, big1)) { + if c.verify(header1.Coinbase, types.FromHeader(header1, big1, false), types.FromHeader(header1, big1, false)) { t.Fatal("expected false, got true") } // Test consensus address is different from header.Coinbase - if c.verify(common.Address{}, *types.FromHeader(header1, big1), *types.FromHeader(header2, big1)) { + if c.verify(common.Address{}, types.FromHeader(header1, big1, false), types.FromHeader(header2, big1, false)) { t.Fatal("expected false, got true") } // Test current block is lower than double signed block c.evm.Context.BlockNumber = new(big.Int).Sub(header1.Number, common.Big1) - if !c.verify(header1.Coinbase, *types.FromHeader(header1, big1), *types.FromHeader(header2, big1)) { + if !c.verify(header1.Coinbase, types.FromHeader(header1, big1, false), types.FromHeader(header2, big1, false)) { t.Fatal("expected true, got false") } // Test current block is higher than signed block but lower than signed block + 28800 c.evm.Context.BlockNumber = new(big.Int).Add(header1.Number, big.NewInt(500)) - if !c.verify(header1.Coinbase, *types.FromHeader(header1, big1), *types.FromHeader(header2, big1)) { + if !c.verify(header1.Coinbase, types.FromHeader(header1, big1, false), types.FromHeader(header2, big1, false)) { t.Fatal("expected true, got false") } // Test current block is higher than signed block + 28800 c.evm.Context.BlockNumber = new(big.Int).Add(header1.Number, big.NewInt(28801)) - if c.verify(header1.Coinbase, *types.FromHeader(header1, big1), *types.FromHeader(header2, big1)) { + if c.verify(header1.Coinbase, types.FromHeader(header1, big1, false), types.FromHeader(header2, big1, false)) { t.Fatal("expected false, got true") } // Test too small header's extra data header1.Extra = nil - if c.verify(header1.Coinbase, *types.FromHeader(header1, big1), *types.FromHeader(header2, big1)) { + if c.verify(header1.Coinbase, types.FromHeader(header1, big1, false), types.FromHeader(header2, big1, false)) { t.Fatal("expected false, got true") } } -// TestConsortiumVerifyHeaders_Run init 2 headers, pack them and call `Run` function directly +// TestConsortiumVerifyHeaders_Run init 2 headers and call `Run` function directly func TestConsortiumVerifyHeaders_Run(t *testing.T) { - var ( - statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - ) - smcAbi := *unmarshalledABIs[VerifyHeaders] + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) evm, err := newEVM(caller, statedb) if err != nil { t.Fatal(err) } - header1, header2, err := prepareHeader(evm.chainConfig.ChainID) - if err != nil { - t.Fatal(err) - } - encodedHeader1, err := types.FromHeader(header1, big1).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) - if err != nil { - t.Fatal(err) - } - encodedHeader2, err := types.FromHeader(header2, big1).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) + type headerGenerators func() (*types.Header, *types.Header, bool) + key, err := crypto.GenerateKey() if err != nil { t.Fatal(err) } - input, err := smcAbi.Pack(verifyHeaders, header1.Coinbase, encodedHeader1, encodedHeader2) - if err != nil { - t.Fatal(err) - } - c := &consortiumVerifyHeaders{evm: evm, caller: AccountRef(caller), test: true} - result, err := c.Run(input) - if err != nil { - t.Fatal(err) - } - if len(result) != 32 { - t.Fatal(fmt.Sprintf("expected len 32 got %d", len(result))) + chainId := big1 + + tests := []struct { + name string + args headerGenerators + want byte + wantErr bool + }{ + { + "Test verify legacy headers should valid", + func() (*types.Header, *types.Header, bool) { + header1, header2, err := prepareHeader(evm.chainConfig.ChainID) + if err != nil { + t.Fatal(err) + } + return header1, header2, false + }, + 1, + false, + }, + { + "Test verify valid headers after Venoki", + func() (*types.Header, *types.Header, bool) { + header1 := mockVenokiHeader(key) + header2 := mockVenokiHeader(key) + header2.Root = common.HexToHash("0x1234") + + extraData := bytes.Repeat([]byte{0x00}, extraVanity) + // append to extraData with validators set + extraData = append(extraData, common.BytesToAddress([]byte("validator1")).Bytes()...) + extraData = append(extraData, common.BytesToAddress([]byte("validator2")).Bytes()...) + // add extra seal space + extraData = append(extraData, make([]byte, crypto.SignatureLength)...) + + header1.Extra = make([]byte, len(extraData)) + copy(header1.Extra[:], extraData) + + header2.Extra = make([]byte, len(extraData)) + copy(header2.Extra[:], extraData) + + signHeader(key, header1, chainId, true) + signHeader(key, header2, chainId, true) + return header1, header2, true + }, + 1, + false, + }, + { + "Test verify headers with invalid baseFee, blobGasUsed, excessBlobGas, extraData", + func() (*types.Header, *types.Header, bool) { + header1 := mockVenokiHeader(key) + header2 := mockVenokiHeader(key) + gas := uint64(100_000) + excessGas := uint64(10_000) + header1.BaseFee = big.NewInt(20_000) + header1.BlobGasUsed = &gas + header1.ExcessBlobGas = &excessGas + header2.BaseFee = big.NewInt(10_000) + header2.BlobGasUsed = &gas + header2.ExcessBlobGas = &excessGas + header2.Coinbase = common.Address{} // invalid coinbase + + extraData := bytes.Repeat([]byte{0x00}, extraVanity) + // append to extraData with validators set + extraData = append(extraData, common.BytesToAddress([]byte("validator1")).Bytes()...) + extraData = append(extraData, common.BytesToAddress([]byte("validator2")).Bytes()...) + // add extra seal space + extraData = append(extraData, make([]byte, crypto.SignatureLength)...) + + header1.Extra = make([]byte, len(extraData)) + copy(header1.Extra[:], extraData) + + header2.Extra = make([]byte, len(extraData)) + copy(header2.Extra[:], extraData) + + signHeader(key, header1, chainId, true) + signHeader(key, header2, chainId, true) + return header1, header2, true + }, + 0, + false, + }, } - if result[len(result)-1] != 1 { - t.Fatal(fmt.Sprintf("expected 1 (true) got %d", result[len(result)-1])) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + smcAbi := *unmarshalledABIs[VerifyHeaders] + header1, header2, isVenoki := tt.args() + abi := rawConsortiumVerifyHeadersAbi + if isVenoki { + abi = rawConsortiumVerifyHeadersV2Abi + } + evm.chainRules.IsVenoki = isVenoki + + blockHeader1 := types.FromHeader(header1, chainId, isVenoki) + blockHeader2 := types.FromHeader(header2, chainId, isVenoki) + + encodedHeader1, err := blockHeader1.Bytes(abi, getHeader) + if err != nil { + t.Fatal(err) + } + encodedHeader2, err := blockHeader2.Bytes(abi, getHeader) + if err != nil { + t.Fatal(err) + } + input, err := smcAbi.Pack(verifyHeaders, header1.Coinbase, encodedHeader1, encodedHeader2) + if err != nil { + t.Fatal(err) + } + c := &consortiumVerifyHeaders{evm: evm, caller: AccountRef(caller), test: true} + result, err := c.Run(input) + if (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(result) != 32 { + t.Fatal(fmt.Sprintf("expected len 32 got %d", len(result))) + } + if result[len(result)-1] != tt.want { + t.Fatal(fmt.Sprintf("expected %v got %d", tt.want, result[len(result)-1])) + } + }) } } @@ -864,7 +963,7 @@ func TestConsortiumVerifyHeaders_malleability(t *testing.T) { } c := &consortiumVerifyHeaders{evm: &EVM{chainConfig: ¶ms.ChainConfig{ChainID: big1}}, test: true} - if c.verify(header1.Coinbase, *types.FromHeader(header1, big1), *types.FromHeader(header2, big1)) { + if c.verify(header1.Coinbase, types.FromHeader(header1, big1, false), types.FromHeader(header2, big1, false)) { t.Fatal("expected false, got true") } } @@ -882,11 +981,11 @@ func TestConsortiumVerifyHeaders_Run2(t *testing.T) { if err != nil { t.Fatal(err) } - encodedHeader1, err := types.FromHeader(header1, big1).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) + encodedHeader1, err := types.FromHeader(header1, big1, false).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) if err != nil { t.Fatal(err) } - encodedHeader2, err := types.FromHeader(header2, big1).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) + encodedHeader2, err := types.FromHeader(header2, big1, false).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) if err != nil { t.Fatal(err) } @@ -930,11 +1029,11 @@ func BenchmarkConsortiumVerifyHeaders(b *testing.B) { if err != nil { b.Fatal(err) } - encodedHeader1, err := types.FromHeader(header1, big1).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) + encodedHeader1, err := types.FromHeader(header1, big1, false).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) if err != nil { b.Fatal(err) } - encodedHeader2, err := types.FromHeader(header2, big1).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) + encodedHeader2, err := types.FromHeader(header2, big1, false).Bytes(rawConsortiumVerifyHeadersAbi, getHeader) if err != nil { b.Fatal(err) } @@ -1810,11 +1909,13 @@ func prepareHeader(chainId *big.Int) (*types.Header, *types.Header, error) { copy(header2.Extra[:], extraData) // signing and add to extraData - sig1, err := crypto.Sign(crypto.Keccak256(consortiumRlp(header1, chainId)), privateKey) + blockHeader1 := types.FromHeader(header1, chainId, false) + sig1, err := crypto.Sign(SealHash(blockHeader1).Bytes(), privateKey) if err != nil { return nil, nil, err } - sig2, err := crypto.Sign(crypto.Keccak256(consortiumRlp(header2, chainId)), privateKey) + blockHeader2 := types.FromHeader(header2, chainId, false) + sig2, err := crypto.Sign(SealHash(blockHeader2).Bytes(), privateKey) if err != nil { return nil, nil, err } @@ -1825,12 +1926,6 @@ func prepareHeader(chainId *big.Int) (*types.Header, *types.Header, error) { return header1, header2, nil } -func consortiumRlp(header *types.Header, chainId *big.Int) []byte { - b := new(bytes.Buffer) - encodeSigHeader(b, header, chainId) - return b.Bytes() -} - func newEVM(caller common.Address, statedb StateDB) (*EVM, error) { evm := &EVM{ Context: BlockContext{ @@ -2162,3 +2257,183 @@ func BenchmarkPrecompiledValidateProofOfPossession(b *testing.B) { benchmarkPrecompiled("6a", test, b) } + +func Test_consortiumVerifyHeaders_getSigner(t *testing.T) { + privateKey, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + + extraData := bytes.Repeat([]byte{0x00}, extraVanity) + // append to extraData with validators set + extraData = append(extraData, common.BytesToAddress([]byte("validator1")).Bytes()...) + extraData = append(extraData, common.BytesToAddress([]byte("validator2")).Bytes()...) + // add extra seal space + extraData = append(extraData, make([]byte, crypto.SignatureLength)...) + + type args struct { + blockHeader *types.Header + chainId *big.Int + isVenoki bool + } + tests := []struct { + name string + args args + want common.Address + wantErr bool + }{ + { + name: "Get signer from header before Venoki", + args: args{ + blockHeader: &types.Header{ + ParentHash: common.BytesToHash([]byte("11")), + UncleHash: common.Hash{}, + Coinbase: crypto.PubkeyToAddress(privateKey.PublicKey), + Root: common.BytesToHash([]byte("123")), + TxHash: common.BytesToHash([]byte("abc")), + ReceiptHash: common.BytesToHash([]byte("def")), + Bloom: types.Bloom{}, + Difficulty: big.NewInt(1000), + Number: big.NewInt(1000), + GasLimit: 100000000, + GasUsed: 0, + Time: 1000, + Extra: make([]byte, len(extraData)), + MixDigest: common.Hash{}, + Nonce: types.EncodeNonce(1000), + }, + chainId: big1, + isVenoki: false, + }, + want: crypto.PubkeyToAddress(privateKey.PublicKey), + wantErr: false, + }, + { + name: "Get signer from header after Venoki", + args: args{ + blockHeader: &types.Header{ + ParentHash: common.BytesToHash([]byte("11")), + UncleHash: common.Hash{}, + Coinbase: crypto.PubkeyToAddress(privateKey.PublicKey), + Root: common.BytesToHash([]byte("123")), + TxHash: common.BytesToHash([]byte("abc")), + ReceiptHash: common.BytesToHash([]byte("def")), + Bloom: types.Bloom{}, + Difficulty: big.NewInt(1000), + Number: big.NewInt(1000), + GasLimit: 100000000, + GasUsed: 0, + Time: 1000, + Extra: make([]byte, len(extraData)), + MixDigest: common.Hash{}, + Nonce: types.EncodeNonce(1000), + BaseFee: big.NewInt(20_000), + }, + chainId: big1, + isVenoki: true, + }, + want: crypto.PubkeyToAddress(privateKey.PublicKey), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &consortiumVerifyHeaders{ + evm: &EVM{ + chainConfig: ¶ms.ChainConfig{ + ChainID: tt.args.chainId, + ConsortiumV2Block: big.NewInt(500), + }, + Context: BlockContext{BlockNumber: big.NewInt(1000)}, + }, + test: true, + } + copy(tt.args.blockHeader.Extra[:], extraData) + blockHeader := types.FromHeader(tt.args.blockHeader, tt.args.chainId, tt.args.isVenoki) + header := blockHeader.ToHeader() + sealHash := SealHash(blockHeader).Bytes() + sig, err := crypto.Sign(sealHash, privateKey) + if err != nil { + t.Fatal(err) + } + + // copy signature to extraData + copy(header.Extra[len(tt.args.blockHeader.Extra)-crypto.SignatureLength:], sig) + got, err := c.getSigner(blockHeader) + if (err != nil) != tt.wantErr { + t.Errorf("getSigner() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.Cmp(tt.want) != 0 { + t.Errorf("getSigner() got = %v, want %v", got.Hex(), tt.want.Hex()) + } + }) + } +} + +func signHeader(privateKey *ecdsa.PrivateKey, header *types.Header, chainId *big.Int, isVenoki bool) { + blockHeader := types.FromHeader(header, chainId, isVenoki) + sealHash := SealHash(blockHeader).Bytes() + sig, err := crypto.Sign(sealHash, privateKey) + if err != nil { + panic(err) + } + copy(header.Extra[len(header.Extra)-crypto.SignatureLength:], sig) +} + +func mockVenokiHeader(privateKey *ecdsa.PrivateKey) *types.Header { + gas := uint64(2000) + return &types.Header{ + ParentHash: common.BytesToHash([]byte("11")), + UncleHash: common.Hash{}, + Coinbase: crypto.PubkeyToAddress(privateKey.PublicKey), + Root: common.BytesToHash([]byte("123")), + TxHash: common.BytesToHash([]byte("abc")), + ReceiptHash: common.BytesToHash([]byte("def")), + Bloom: types.Bloom{}, + Difficulty: big.NewInt(1000), + Number: big.NewInt(1000), + GasLimit: 100000000, + GasUsed: 0, + Time: 1000, + MixDigest: common.Hash{}, + Extra: make([]byte, 0), + Nonce: types.EncodeNonce(1000), + BaseFee: big.NewInt(20_000), + BlobGasUsed: &gas, + ExcessBlobGas: &gas, + } +} + +func Test_consortiumVerifyHeaders_unpackHeader(t *testing.T) { + smcAbi := *unmarshalledABIs[VerifyHeaders] + key, _ := crypto.GenerateKey() + header := mockVenokiHeader(key) + abi := rawConsortiumVerifyHeadersV2Abi + blockHeader1 := types.FromHeader(header, big1, true) + + encodedHeader, err := blockHeader1.Bytes(abi, getHeader) + if err != nil { + t.Fatal(err) + } + input, err := smcAbi.Pack(verifyHeaders, header.Coinbase, encodedHeader, encodedHeader) + if err != nil { + t.Fatal(err) + } + + // get method, args from abi and check if method is valid + smcAbi, _, args, err := loadMethodAndArgs(VerifyHeadersVenoki, input) + if err != nil { + t.Fatal(err) + } + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + evm, err := newEVM(caller, statedb) + c := &consortiumVerifyHeaders{evm: evm, caller: AccountRef(caller), test: true} + recoveredHeader, err := c.unpackHeader(smcAbi, args[1].([]byte), true) + if err != nil { + t.Fatal(err) + } + + rec := recoveredHeader.ToHeader() + assert.EqualValues(t, header, rec) +}