Skip to content

Commit b91ecb4

Browse files
authored
Merge pull request #422 from bane-labs/govreward-burn-gas
antimev: require gas burning in envelope transaction
2 parents 54bad8f + 53405db commit b91ecb4

File tree

28 files changed

+341
-27
lines changed

28 files changed

+341
-27
lines changed

antimev/envelope.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package antimev
33
import (
44
"bytes"
55
"crypto/aes"
6+
"encoding/binary"
67

78
"github.com/ethereum/go-ethereum/common"
89
"github.com/ethereum/go-ethereum/core/systemcontracts"
@@ -21,9 +22,13 @@ var (
2122
EncryptedDataPrefixLen = len(EncryptedDataPrefix)
2223

2324
// EncryptedDataRoundLen is the amount of bytes that encoded DKG round of transaction
24-
// encryption takes in the Envelope's data byte slice (the size of Uint64).
25+
// encryption takes in the Envelope's data byte slice (the size of Uint32).
2526
EncryptedDataRoundLen = 4
2627

28+
// EncryptedDataGasLen is the amount of bytes that encoded gas space to reserve for
29+
// the decrypted transaction in the block (the size of Uint32).
30+
EncryptedDataGasLen = 4
31+
2732
// EncryptedDataHashLen is the amount of bytes that represents the hash of an
2833
// encrypted transaction.
2934
EncryptedDataHashLen = common.HashLength
@@ -34,7 +39,7 @@ var (
3439
// a simple gas transfer with 1 gwei (105 bytes) is taken as a reference point
3540
// for evaluation of variable-length part; it is padded to be even to the AES
3641
// block size as required by AES encryption rules.
37-
minEncryptedDataSize = EncryptedDataPrefixLen + EncryptedDataRoundLen + EncryptedDataHashLen + tpke.CipherTextSize + 105 + (aes.BlockSize - 105%aes.BlockSize)
42+
minEncryptedDataSize = EncryptedDataPrefixLen + EncryptedDataRoundLen + EncryptedDataGasLen + EncryptedDataHashLen + tpke.CipherTextSize + 105 + (aes.BlockSize - 105%aes.BlockSize)
3843
)
3944

4045
// IsEnvelope checks whether a transaction is an Envelope transaction. The criteria
@@ -64,6 +69,13 @@ func IsEnvelopeData(data []byte) bool {
6469
// GetEncryptedHash returns the hash of inner encrypted transaction specified in an
6570
// unencrypted part of Envelope data. Passing non-Envelope as an argument is a no-op.
6671
func GetEncryptedHash(envelope *types.Transaction) common.Hash {
67-
hashOffset := EncryptedDataPrefixLen + EncryptedDataRoundLen
72+
hashOffset := EncryptedDataPrefixLen + EncryptedDataRoundLen + EncryptedDataGasLen
6873
return common.Hash(envelope.Data()[hashOffset : hashOffset+EncryptedDataHashLen])
6974
}
75+
76+
// GetEncryptedGas returns the gas limit of inner encrypted transaction specified in an
77+
// unencrypted part of Envelope data. Passing non-Envelope as an argument is a no-op.
78+
func GetEncryptedGas(envelope *types.Transaction) uint32 {
79+
gasOffset := EncryptedDataPrefixLen + EncryptedDataRoundLen
80+
return binary.BigEndian.Uint32(envelope.Data()[gasOffset : gasOffset+EncryptedDataGasLen])
81+
}

antimev/tpke_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ func TestTPKE(t *testing.T) {
8282

8383
// Generate an example envelope for privnet verification
8484
var envelopeData = EncryptedDataPrefix
85-
envelopeData = binary.LittleEndian.AppendUint32(envelopeData, 0)
85+
envelopeData = binary.BigEndian.AppendUint32(envelopeData, 0)
86+
envelopeData = binary.BigEndian.AppendUint32(envelopeData, 0)
8687
envelopeData = append(envelopeData, common.MaxHash[:]...)
8788
envelopeData = append(envelopeData, encryptedKey.ToBytes()...)
8889
envelopeData = append(envelopeData, encryptedMsg...)
@@ -154,7 +155,8 @@ func TestGenerateEncryptedTx(t *testing.T) {
154155
}
155156
// Generate envelope.
156157
var envelopeData = EncryptedDataPrefix
157-
envelopeData = binary.LittleEndian.AppendUint32(envelopeData, epoch)
158+
envelopeData = binary.BigEndian.AppendUint32(envelopeData, epoch)
159+
envelopeData = binary.BigEndian.AppendUint32(envelopeData, uint32(tx.Gas()))
158160
envelopeData = append(envelopeData, tx.Hash().Bytes()...)
159161
envelopeData = append(envelopeData, encryptedKey.ToBytes()...)
160162
envelopeData = append(envelopeData, encryptedMsg...)

consensus/dbft/amev.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ type envelopeData struct {
3131
func decodeEnvelopeData(buf []byte) (envelopeData, error) {
3232
var (
3333
key = new(tpke.CipherText)
34-
keyOffset = antimev.EncryptedDataPrefixLen + antimev.EncryptedDataRoundLen + antimev.EncryptedDataHashLen
34+
keyOffset = antimev.EncryptedDataPrefixLen + antimev.EncryptedDataRoundLen + antimev.EncryptedDataGasLen + antimev.EncryptedDataHashLen
3535
cipherTextOffset = keyOffset + tpke.CipherTextSize
3636
)
3737
// It's guaranteed by Envelope definition that buf has a proper length.
3838
_, err := key.FromBytes(buf[keyOffset:cipherTextOffset])
3939
if err != nil {
4040
return envelopeData{}, fmt.Errorf("failed to decode TPKE cipher text: %w", err)
4141
}
42-
round := binary.LittleEndian.Uint32(buf[antimev.EncryptedDataPrefixLen:keyOffset])
42+
round := binary.BigEndian.Uint32(buf[antimev.EncryptedDataPrefixLen:keyOffset])
4343
if round == 0 {
4444
return envelopeData{}, fmt.Errorf("invalid TPKE cipher text: invalid round %d", round)
4545
}

consensus/dbft/dbft.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ func (c *DBFT) processPreBlockCb(b dbft.PreBlock[common.Hash]) error {
11421142
break
11431143
}
11441144
}
1145-
err = c.validateDecryptedTx(parent, decryptedTx, pre.transactions[i])
1145+
err = c.validateDecryptedTx(parent, decryptedTx, pre.transactions[i], pre.finalReceipts[i])
11461146
if err != nil {
11471147
if fallbackToEnvelope(i, true, fmt.Sprintf("decrypted transaction verification failed: %s", err)) {
11481148
continue
@@ -1232,7 +1232,7 @@ func (c *DBFT) newLocalPool(parent *types.Header) *legacypool.LegacyPool {
12321232
}
12331233

12341234
// validateDecryptedTx checks the validity of the transaction to determine whether the outer envelope transaction should be replaced.
1235-
func (c *DBFT) validateDecryptedTx(head *types.Header, decryptedTx *types.Transaction, envelope *types.Transaction) error {
1235+
func (c *DBFT) validateDecryptedTx(head *types.Header, decryptedTx *types.Transaction, envelope *types.Transaction, envelopeReceipt *types.Receipt) error {
12361236
// Make sure the transaction is signed properly and has the same sender and nonce with envelope
12371237
if decryptedTx.Nonce() != envelope.Nonce() {
12381238
return fmt.Errorf("decryptedTx nonce mismatch: decryptedNonce %v, envelopeNonce %v", decryptedTx.Nonce(), envelope.Nonce())
@@ -1262,6 +1262,15 @@ func (c *DBFT) validateDecryptedTx(head *types.Header, decryptedTx *types.Transa
12621262
if decryptedTx.Hash().Cmp(expectedH) != 0 {
12631263
return fmt.Errorf("decryptedTx hash mismatch: expected %s, got %s", expectedH, decryptedTx.Hash())
12641264
}
1265+
// Ensure decrypted gas limit is the same as the envelope declared
1266+
expectedG := antimev.GetEncryptedGas(envelope)
1267+
if decryptedTx.Gas() != uint64(expectedG) {
1268+
return fmt.Errorf("decryptedTx gas limit mismatch: expected %v, got %v", expectedG, decryptedTx.Gas())
1269+
}
1270+
// Ensure decrypted gas limit has been allocated by Envelope
1271+
if decryptedTx.Gas() > envelopeReceipt.GasUsed {
1272+
return fmt.Errorf("decryptedTx gas limit not allocated: needed %v, got %v", decryptedTx.Gas(), envelopeReceipt.GasUsed)
1273+
}
12651274

12661275
return nil
12671276
}

contracts/solidity/GovReward.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.25;
33

44
import {Errors} from "./libraries/Errors.sol";
5+
import {Bytes} from "./libraries/Bytes.sol";
56
import {IGovReward} from "./interfaces/IGovReward.sol";
67
import {IGovernance} from "./interfaces/IGovernance.sol";
78
import {ERC1967Utils, GovProxyUpgradeable} from "./base/GovProxyUpgradeable.sol";
@@ -17,6 +18,17 @@ contract GovReward is IGovReward, GovProxyUpgradeable {
1718
if (msg.sig != bytes4(0xffffffff)) {
1819
revert Errors.InvalidSelector();
1920
}
21+
// burn required gas amount internally
22+
uint32 requiredSpace = Bytes.decodeUint32(msg.data[8:12]);
23+
if (requiredSpace < 21000) {
24+
revert Errors.InvalidGasLimit();
25+
}
26+
(bool success, bytes memory data) = address(this).staticcall{
27+
gas: requiredSpace - 21000
28+
}(abi.encodeWithSelector(this._wasteGas.selector));
29+
if (success || data.length > 0) {
30+
revert Errors.UnexpectedGasBurn();
31+
}
2032
}
2133

2234
modifier onlyGov() {
@@ -58,4 +70,8 @@ contract GovReward is IGovReward, GovProxyUpgradeable {
5870
(bool success, ) = to.call{value: value}(new bytes(0));
5971
if (!success) revert Errors.TransferFailed();
6072
}
73+
74+
function _wasteGas() external pure {
75+
while (true) {}
76+
}
6177
}

contracts/solidity/Policy.sol

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
1818
uint256 public baseFee;
1919
uint256 internal candidateLimit;
2020
uint256 public envelopeFee;
21+
uint256 public maxEnvelopesPerBlock;
22+
uint256 public maxEnvelopeGasLimit;
2123

2224
// Only for precompiled uups implementation in genesis file, need to be removed when upgrading the contract.
2325
// This override is added because "immutable __self" in UUPSUpgradeable is not avaliable in precompiled contract.
@@ -39,6 +41,15 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
3941
}
4042
}
4143

44+
// Only for Policy upgrading for the new Envelope transactions
45+
function setInitialEnvelopeLimits(
46+
uint256 _maxEnvelopesPerBlock,
47+
uint256 _maxEnvelopeGasLimit
48+
) external onlyAdmin {
49+
maxEnvelopesPerBlock = _maxEnvelopesPerBlock;
50+
maxEnvelopeGasLimit = _maxEnvelopeGasLimit;
51+
}
52+
4253
function addBlackList(
4354
address _addr
4455
)
@@ -148,4 +159,38 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
148159
envelopeFee = _fee;
149160
emit SetEnvelopeFee(_fee);
150161
}
162+
163+
function setMaxEnvelopesPerBlock(
164+
uint256 _number
165+
)
166+
external
167+
needVote(
168+
bytes32(
169+
// keccak256("setMaxEnvelopesPerBlock")
170+
0x468ff90b65b7330769fd5fa1950650ac15948bbbaaff84f86b3c11a5dc7842d1
171+
),
172+
keccak256(abi.encode(_number))
173+
)
174+
{
175+
if (_number <= 0) revert Errors.InvalidMaxEnvelopesPerBlock();
176+
maxEnvelopesPerBlock = _number;
177+
emit SetMaxEnvelopesPerBlock(_number);
178+
}
179+
180+
function setMaxEnvelopeGasLimit(
181+
uint256 _gaslimit
182+
)
183+
external
184+
needVote(
185+
bytes32(
186+
// keccak256("setMaxEnvelopeGasLimit")
187+
0xf48e9af07262cd1c77a4209ac59848c8bf3f8ec29817678aa7f1b67dd990501c
188+
),
189+
keccak256(abi.encode(_gaslimit))
190+
)
191+
{
192+
if (_gaslimit < 21000) revert Errors.InvalidMaxEnvelopeGasLimit();
193+
maxEnvelopeGasLimit = _gaslimit;
194+
emit SetMaxEnvelopeGasLimit(_gaslimit);
195+
}
151196
}

contracts/solidity/interfaces/IPolicy.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ interface IPolicy {
88
event SetBaseFee(uint256 baseFee);
99
event SetCandidateLimit(uint256 candidateLimit);
1010
event SetEnvelopeFee(uint256 envelopeFee);
11+
event SetMaxEnvelopesPerBlock(uint256 maxEnvelopesPerBlock);
12+
event SetMaxEnvelopeGasLimit(uint256 setMaxEnvelopeGasLimit);
1113

1214
// add an address to blacklist policy
1315
function addBlackList(address _addr) external;
@@ -35,4 +37,16 @@ interface IPolicy {
3537

3638
// return the value of candidate limit policy
3739
function getCandidateLimit() external view returns (uint256);
40+
41+
// set the maximum number of envelope transactions to be packed in each block
42+
function setMaxEnvelopesPerBlock(uint256 _number) external;
43+
44+
// get the value of envelope transaction number policy
45+
function maxEnvelopesPerBlock() external view returns (uint256);
46+
47+
// set the maximum value of envelope transaction gas limit
48+
function setMaxEnvelopeGasLimit(uint256 _gaslimit) external;
49+
50+
// get the value of envelope transaction gas limit policy
51+
function maxEnvelopeGasLimit() external view returns (uint256);
3852
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.25;
3+
4+
library Bytes {
5+
function toBytes4(
6+
bytes memory data
7+
) internal pure returns (bytes4) {
8+
bytes4 out;
9+
for (uint256 i = 0; i < 4; i++) {
10+
out |= bytes4(data[i] & 0xFF) >> (i * 8);
11+
}
12+
return out;
13+
}
14+
15+
function decodeUint32(bytes memory data) internal pure returns (uint32) {
16+
require(data.length == 4, "Bad data");
17+
return uint32(toBytes4(data));
18+
}
19+
}

contracts/solidity/libraries/Errors.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ library Errors {
1313

1414
// GovReward Errors
1515
error InvalidSelector();
16+
error InvalidGasLimit();
17+
error UnexpectedGasBurn();
1618

1719
// Policy Errors
1820
error BlacklistExists();
@@ -21,6 +23,8 @@ library Errors {
2123
error InvalidBaseFee();
2224
error InvalidCandidateLimit();
2325
error InvalidEnvelopeFee();
26+
error InvalidMaxEnvelopesPerBlock();
27+
error InvalidMaxEnvelopeGasLimit();
2428

2529
// Governance Errors
2630
error SideCallNotAllowed();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
contract MockFallback {
5+
function call_fallback(address addr, bytes calldata data) public payable {
6+
bool success;
7+
assembly {
8+
calldatacopy(0, data.offset, calldatasize())
9+
success := call(
10+
gas(),
11+
addr,
12+
0,
13+
0,
14+
calldatasize(),
15+
0,
16+
returndatasize()
17+
)
18+
switch success
19+
case 0 {
20+
invalid()
21+
}
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)