fix (libzkp): check tx num when CCC ErrUnknown error occurred (#505)

* Check tx num for CCC ErrUnknown error.

* Update worker.go

* Small fix.

* Update

* Fix log.

* Update version.

* Update miner/worker.go

Co-authored-by: Péter Garamvölgyi <peter@scroll.io>

* Update according to review.

* Fix to return 0 for uint64.

* Add `tcount` to mock CCC, and function `SetTxNum`.

* refactor

* fix

* add comments

---------

Co-authored-by: Zhang Zhuo <mycinbrin@gmail.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
Co-authored-by: HAOYUatHZ <haoyu@protonmail.com>
This commit is contained in:
Steven 2023-09-08 19:17:02 +08:00 committed by GitHub
parent dade96d416
commit 7d79f27e6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 1 deletions

View file

@ -1145,6 +1145,7 @@ loop:
// However, after `ErrUnknown`, ccc might remain in an
// inconsistent state, so we cannot pack more transactions.
circuitCapacityReached = true
w.checkCurrentTxNumWithCCC(w.current.tcount)
break loop
case (errors.Is(err, circuitcapacitychecker.ErrUnknown) && !tx.IsL1MessageTx()):
@ -1164,6 +1165,7 @@ loop:
// inconsistent state, so we cannot pack more transactions.
w.eth.TxPool().RemoveTx(tx.Hash(), true)
circuitCapacityReached = true
w.checkCurrentTxNumWithCCC(w.current.tcount)
break loop
default:
@ -1208,6 +1210,17 @@ loop:
return false, circuitCapacityReached
}
func (w *worker) checkCurrentTxNumWithCCC(expected int) {
match, got, err := w.circuitCapacityChecker.CheckTxNum(expected)
if err != nil {
log.Error("failed to CheckTxNum in ccc", "err", err)
return
}
if !match {
log.Error("tx count in miner is different with CCC", "w.current.tcount", w.current.tcount, "got", got)
}
}
func (w *worker) collectPendingL1Messages(startIndex uint64) []types.L1MessageTx {
maxCount := w.chainConfig.Scroll.L1Config.NumL1MessagesPerBlock
return rawdb.ReadL1MessagesFrom(w.eth.ChainDb(), startIndex, maxCount)

View file

@ -24,7 +24,7 @@ import (
const (
VersionMajor = 4 // Major version component of the current release
VersionMinor = 4 // Minor version component of the current release
VersionPatch = 2 // Patch version component of the current release
VersionPatch = 3 // Patch version component of the current release
VersionMeta = "sepolia" // Version metadata to append to the version string
)

View file

@ -31,6 +31,7 @@ type CircuitCapacityChecker struct {
ID uint64
}
// NewCircuitCapacityChecker creates a new CircuitCapacityChecker
func NewCircuitCapacityChecker() *CircuitCapacityChecker {
creationMu.Lock()
defer creationMu.Unlock()
@ -39,6 +40,7 @@ func NewCircuitCapacityChecker() *CircuitCapacityChecker {
return &CircuitCapacityChecker{ID: uint64(id)}
}
// Reset resets a CircuitCapacityChecker
func (ccc *CircuitCapacityChecker) Reset() {
ccc.Lock()
defer ccc.Unlock()
@ -46,6 +48,7 @@ func (ccc *CircuitCapacityChecker) Reset() {
C.reset_circuit_capacity_checker(C.uint64_t(ccc.ID))
}
// ApplyTransaction appends a tx's wrapped BlockTrace into the ccc, and return the accumulated RowConsumption
func (ccc *CircuitCapacityChecker) ApplyTransaction(traces *types.BlockTrace) (*types.RowConsumption, error) {
ccc.Lock()
defer ccc.Unlock()
@ -100,6 +103,7 @@ func (ccc *CircuitCapacityChecker) ApplyTransaction(traces *types.BlockTrace) (*
return (*types.RowConsumption)(&result.AccRowUsage.RowUsageDetails), nil
}
// ApplyBlock gets a block's RowConsumption
func (ccc *CircuitCapacityChecker) ApplyBlock(traces *types.BlockTrace) (*types.RowConsumption, error) {
ccc.Lock()
defer ccc.Unlock()
@ -141,3 +145,28 @@ func (ccc *CircuitCapacityChecker) ApplyBlock(traces *types.BlockTrace) (*types.
}
return (*types.RowConsumption)(&result.AccRowUsage.RowUsageDetails), nil
}
// CheckTxNum compares whether the tx_count in ccc match the expected
func (ccc *CircuitCapacityChecker) CheckTxNum(expected int) (bool, unit64, error) {
ccc.Lock()
defer ccc.Unlock()
log.Debug("ccc get_tx_num start", "id", ccc.ID)
rawResult := C.get_tx_num(C.uint64_t(ccc.ID))
defer func() {
C.free(unsafe.Pointer(rawResult))
}()
log.Debug("ccc get_tx_num end", "id", ccc.ID)
result := &WrappedTxNum{}
if err = json.Unmarshal([]byte(C.GoString(rawResult)), result); err != nil {
log.Error("fail to json unmarshal get_tx_num result", "id", ccc.ID, "err", err)
return false, 0, ErrUnknown
}
if result.Error != "" {
log.Error("fail to get_tx_num in CircuitCapacityChecker", "id", ccc.ID, "err", result.Error)
return false, 0, ErrUnknown
}
return result.TxNum == unit64(expected), result.TxNum, nil
}

View file

@ -4,3 +4,4 @@ uint64_t new_circuit_capacity_checker();
void reset_circuit_capacity_checker(uint64_t id);
char* apply_tx(uint64_t id, char *tx_traces);
char* apply_block(uint64_t id, char *block_trace);
char* get_tx_num(uint64_t id);

View file

@ -20,6 +20,12 @@ pub mod checker {
pub error: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TxNumResult {
pub tx_num: u64,
pub error: Option<String>,
}
static mut CHECKERS: OnceCell<HashMap<u64, CircuitCapacityChecker>> = OnceCell::new();
/// # Safety
@ -176,6 +182,50 @@ pub mod checker {
}
}
}
/// # Safety
#[no_mangle]
pub unsafe extern "C" fn get_tx_num(id: u64) -> *const c_char {
let result = get_tx_num_inner(id);
let r = match result {
Ok(tx_num) => {
log::debug!("id: {id}, tx_num: {tx_num}");
TxNumResult {
tx_num,
error: None,
}
}
Err(e) => TxNumResult {
tx_num: 0,
error: Some(format!("{e:?}")),
},
};
serde_json::to_vec(&r).map_or(null(), vec_to_c_char)
}
unsafe fn get_tx_num_inner(id: u64) -> Result<u64, Error> {
log::debug!("ccc get_tx_num raw input, id: {id}");
panic::catch_unwind(|| {
Ok(CHECKERS
.get_mut()
.ok_or(anyhow!(
"fail to get circuit capacity checkers map in get_tx_num"
))?
.get_mut(&id)
.ok_or(anyhow!(
"fail to get circuit capacity checker (id: {id}) in get_tx_num"
))?
.get_tx_num() as u64)
})
.map_or_else(
|e| {
Err(anyhow!(
"circuit capacity checker (id: {id}) error in get_tx_num: {e:?}"
))
},
|result| result,
)
}
}
pub(crate) mod utils {

View file

@ -14,13 +14,17 @@ type CircuitCapacityChecker struct {
nextError *error
}
// NewCircuitCapacityChecker creates a new CircuitCapacityChecker
func NewCircuitCapacityChecker() *CircuitCapacityChecker {
return &CircuitCapacityChecker{ID: rand.Uint64()}
}
// Reset resets a ccc, but need to do nothing in mock_ccc.
func (ccc *CircuitCapacityChecker) Reset() {
}
// ApplyTransaction appends a tx's wrapped BlockTrace into the ccc, and return the accumulated RowConsumption.
// Will only return a dummy value in mock_ccc.
func (ccc *CircuitCapacityChecker) ApplyTransaction(traces *types.BlockTrace) (*types.RowConsumption, error) {
if ccc.nextError != nil {
ccc.countdown--
@ -36,6 +40,8 @@ func (ccc *CircuitCapacityChecker) ApplyTransaction(traces *types.BlockTrace) (*
}}, nil
}
// ApplyBlock gets a block's RowConsumption.
// Will only return a dummy value in mock_ccc.
func (ccc *CircuitCapacityChecker) ApplyBlock(traces *types.BlockTrace) (*types.RowConsumption, error) {
return &types.RowConsumption{types.SubCircuitRowUsage{
Name: "mock",
@ -43,6 +49,13 @@ func (ccc *CircuitCapacityChecker) ApplyBlock(traces *types.BlockTrace) (*types.
}}, nil
}
// CheckTxNum compares whether the tx_count in ccc match the expected.
// Will alway return true in mock_ccc.
func (ccc *CircuitCapacityChecker) CheckTxNum(expected int) (bool, uint64, error) {
return true, uint64(expected), nil
}
// ScheduleError schedules an error for a tx (see `ApplyTransaction`), only used in tests.
func (ccc *CircuitCapacityChecker) ScheduleError(cnt int, err error) {
ccc.countdown = cnt
ccc.nextError = &err

View file

@ -15,3 +15,8 @@ type WrappedRowUsage struct {
AccRowUsage *types.RowUsage `json:"acc_row_usage,omitempty"`
Error string `json:"error,omitempty"`
}
type WrappedTxNum struct {
TxNum uint64 `json:"tx_num"`
Error string `json:"error,omitempty"`
}