mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
fix: update row estimation with scroll-prover v0.7.2 (#475)
* Fix row estimation. * Update libzkp. * more * prepare * finish * upgrade * bump version * fix tests * Update to scroll-prover `v0.7.2`. * fix tests * Update miner/worker.go Co-authored-by: Péter Garamvölgyi <peter@scroll.io> * Update miner/worker.go Co-authored-by: Péter Garamvölgyi <peter@scroll.io> * Reset ccc when skips first tx. * do not unnecessarily skip L1 message * fix ccc reset and improve code readability * seal block on circuitcapacitychecker.ErrUnknown --------- Co-authored-by: HAOYUatHZ <haoyu@protonmail.com> Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
This commit is contained in:
parent
727f88bc5f
commit
6992cfe8c5
8 changed files with 86 additions and 77 deletions
|
|
@ -1068,40 +1068,38 @@ loop:
|
|||
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
|
||||
txs.Pop()
|
||||
|
||||
// Circuit capacity check
|
||||
case errors.Is(err, circuitcapacitychecker.ErrBlockRowConsumptionOverflow):
|
||||
// Circuit capacity check: circuit capacity limit reached in a block,
|
||||
// don't pop or shift, just quit the loop immediately;
|
||||
// though it might still be possible to add some "smaller" txs,
|
||||
// but it's a trade-off between tracing overhead & block usage rate
|
||||
log.Trace("Circuit capacity limit reached in a block", "acc_rows", w.current.accRows, "tx", tx.Hash().String())
|
||||
circuitCapacityReached = true
|
||||
break loop
|
||||
if w.current.tcount >= 1 {
|
||||
// 1. Circuit capacity limit reached in a block, and it's not the first tx:
|
||||
// don't pop or shift, just quit the loop immediately;
|
||||
// though it might still be possible to add some "smaller" txs,
|
||||
// but it's a trade-off between tracing overhead & block usage rate
|
||||
log.Trace("Circuit capacity limit reached in a block", "acc_rows", w.current.accRows, "tx", tx.Hash().String())
|
||||
circuitCapacityReached = true
|
||||
break loop
|
||||
} else {
|
||||
// 2. Circuit capacity limit reached in a block, and it's the first tx: skip the tx
|
||||
log.Trace("Circuit capacity limit reached for a single tx", "tx", tx.Hash().String())
|
||||
|
||||
case (errors.Is(err, circuitcapacitychecker.ErrTxRowConsumptionOverflow) && tx.IsL1MessageTx()):
|
||||
// Circuit capacity check: L1MessageTx row consumption too high, shift to the next from the account,
|
||||
// because we shouldn't skip the entire txs from the same account.
|
||||
// This is also useful for skipping "problematic" L1MessageTxs.
|
||||
queueIndex := tx.AsL1MessageTx().QueueIndex
|
||||
log.Trace("Circuit capacity limit reached for a single tx", "tx", tx.Hash().String(), "queueIndex", queueIndex)
|
||||
log.Info("Skipping L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String(), "block", w.current.header.Number, "reason", "row consumption overflow")
|
||||
w.current.nextL1MsgIndex = queueIndex + 1
|
||||
if tx.IsL1MessageTx() {
|
||||
// Skip L1 message transaction,
|
||||
// shift to the next from the account because we shouldn't skip the entire txs from the same account
|
||||
txs.Shift()
|
||||
|
||||
// after `ErrTxRowConsumptionOverflow`, ccc might not revert updates
|
||||
// associated with this transaction so we cannot pack more transactions.
|
||||
// TODO: fix this in ccc and change these lines back to `txs.Shift()`
|
||||
circuitCapacityReached = true
|
||||
break loop
|
||||
queueIndex := tx.AsL1MessageTx().QueueIndex
|
||||
log.Info("Skipping L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String(), "block", w.current.header.Number, "reason", "row consumption overflow")
|
||||
w.current.nextL1MsgIndex = queueIndex + 1
|
||||
} else {
|
||||
// Skip L2 transaction and all other transactions from the same sender account
|
||||
txs.Pop()
|
||||
}
|
||||
|
||||
case (errors.Is(err, circuitcapacitychecker.ErrTxRowConsumptionOverflow) && !tx.IsL1MessageTx()):
|
||||
// Circuit capacity check: L2MessageTx row consumption too high, skip the account.
|
||||
// This is also useful for skipping "problematic" L2MessageTxs.
|
||||
log.Trace("Circuit capacity limit reached for a single tx", "tx", tx.Hash().String())
|
||||
|
||||
// after `ErrTxRowConsumptionOverflow`, ccc might not revert updates
|
||||
// associated with this transaction so we cannot pack more transactions.
|
||||
// TODO: fix this in ccc and change these lines back to `txs.Pop()`
|
||||
circuitCapacityReached = true
|
||||
break loop
|
||||
// Reset ccc so that we can process other transactions for this block
|
||||
w.circuitCapacityChecker.Reset()
|
||||
log.Trace("Worker reset ccc", "id", w.circuitCapacityChecker.ID)
|
||||
circuitCapacityReached = false
|
||||
}
|
||||
|
||||
case (errors.Is(err, circuitcapacitychecker.ErrUnknown) && tx.IsL1MessageTx()):
|
||||
// Circuit capacity check: unknown circuit capacity checker error for L1MessageTx,
|
||||
|
|
@ -1111,9 +1109,9 @@ loop:
|
|||
log.Info("Skipping L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String(), "block", w.current.header.Number, "reason", "unknown row consumption error")
|
||||
w.current.nextL1MsgIndex = queueIndex + 1
|
||||
|
||||
// after `ErrUnknown`, ccc might not revert updates associated
|
||||
// with this transaction so we cannot pack more transactions.
|
||||
// TODO: fix this in ccc and change these lines back to `txs.Shift()`
|
||||
// Normally we would do `txs.Shift()` here.
|
||||
// However, after `ErrUnknown`, ccc might remain in an
|
||||
// inconsistent state, so we cannot pack more transactions.
|
||||
circuitCapacityReached = true
|
||||
break loop
|
||||
|
||||
|
|
@ -1121,9 +1119,9 @@ loop:
|
|||
// Circuit capacity check: unknown circuit capacity checker error for L2MessageTx, skip the account
|
||||
log.Trace("Unknown circuit capacity checker error for L2MessageTx", "tx", tx.Hash().String())
|
||||
|
||||
// after `ErrUnknown`, ccc might not revert updates associated
|
||||
// with this transaction so we cannot pack more transactions.
|
||||
// TODO: fix this in ccc and change these lines back to `txs.Pop()`
|
||||
// Normally we would do `txs.Pop()` here.
|
||||
// However, after `ErrUnknown`, ccc might remain in an
|
||||
// inconsistent state, so we cannot pack more transactions.
|
||||
circuitCapacityReached = true
|
||||
break loop
|
||||
|
||||
|
|
|
|||
|
|
@ -1010,10 +1010,10 @@ func TestOversizedTxThenNormal(t *testing.T) {
|
|||
switch blockNum {
|
||||
case 0:
|
||||
// schedule to skip 2nd call to ccc
|
||||
w.getCCC().ScheduleError(2, circuitcapacitychecker.ErrTxRowConsumptionOverflow)
|
||||
w.getCCC().ScheduleError(2, circuitcapacitychecker.ErrBlockRowConsumptionOverflow)
|
||||
return false
|
||||
case 1:
|
||||
// include #0, skip #1, then terminate
|
||||
// include #0, fail on #1, then seal the block
|
||||
assert.Equal(1, len(block.Transactions()))
|
||||
|
||||
assert.True(block.Transactions()[0].IsL1MessageTx())
|
||||
|
|
@ -1022,7 +1022,23 @@ func TestOversizedTxThenNormal(t *testing.T) {
|
|||
// db is updated correctly
|
||||
queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(db, block.Hash())
|
||||
assert.NotNil(queueIndex)
|
||||
assert.Equal(uint64(2), *queueIndex)
|
||||
assert.Equal(uint64(1), *queueIndex)
|
||||
|
||||
// schedule to skip next call to ccc
|
||||
w.getCCC().ScheduleError(1, circuitcapacitychecker.ErrBlockRowConsumptionOverflow)
|
||||
|
||||
return false
|
||||
case 2:
|
||||
// skip #1, include #2, then seal the block
|
||||
assert.Equal(1, len(block.Transactions()))
|
||||
|
||||
assert.True(block.Transactions()[0].IsL1MessageTx())
|
||||
assert.Equal(uint64(2), block.Transactions()[0].AsL1MessageTx().QueueIndex)
|
||||
|
||||
// db is updated correctly
|
||||
queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(db, block.Hash())
|
||||
assert.NotNil(queueIndex)
|
||||
assert.Equal(uint64(3), *queueIndex)
|
||||
|
||||
return true
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
const (
|
||||
VersionMajor = 4 // Major version component of the current release
|
||||
VersionMinor = 3 // Minor version component of the current release
|
||||
VersionPatch = 47 // Patch version component of the current release
|
||||
VersionPatch = 48 // Patch version component of the current release
|
||||
VersionMeta = "sepolia" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -87,17 +87,13 @@ func (ccc *CircuitCapacityChecker) ApplyTransaction(traces *types.BlockTrace) (*
|
|||
log.Error("fail to apply_tx in CircuitCapacityChecker", "id", ccc.ID, "TxHash", traces.Transactions[0].TxHash, "err", result.Error)
|
||||
return nil, ErrUnknown
|
||||
}
|
||||
if result.TxRowUsage == nil || result.AccRowUsage == nil {
|
||||
if result.AccRowUsage == nil {
|
||||
log.Error("fail to apply_tx in CircuitCapacityChecker",
|
||||
"id", ccc.ID, "TxHash", traces.Transactions[0].TxHash,
|
||||
"result.TxRowUsage==nil", result.TxRowUsage == nil,
|
||||
"result.AccRowUsage == nil", result.AccRowUsage == nil,
|
||||
"err", "TxRowUsage or AccRowUsage is empty unexpectedly")
|
||||
"err", "AccRowUsage is empty unexpectedly")
|
||||
return nil, ErrUnknown
|
||||
}
|
||||
if !result.TxRowUsage.IsOk {
|
||||
return nil, ErrTxRowConsumptionOverflow
|
||||
}
|
||||
if !result.AccRowUsage.IsOk {
|
||||
return nil, ErrBlockRowConsumptionOverflow
|
||||
}
|
||||
|
|
|
|||
28
rollup/circuitcapacitychecker/libzkp/Cargo.lock
generated
28
rollup/circuitcapacitychecker/libzkp/Cargo.lock
generated
|
|
@ -23,7 +23,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "aggregator"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"ark-std",
|
||||
"env_logger 0.10.0",
|
||||
|
|
@ -397,7 +397,7 @@ checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
|
|||
[[package]]
|
||||
name = "bus-mapping"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"eth-types",
|
||||
"ethers-core",
|
||||
|
|
@ -1061,7 +1061,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "eth-types"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"ethers-core",
|
||||
"ethers-signers",
|
||||
|
|
@ -1238,7 +1238,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "external-tracer"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"eth-types",
|
||||
"geth-utils",
|
||||
|
|
@ -1451,7 +1451,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gadgets"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"digest 0.7.6",
|
||||
"eth-types",
|
||||
|
|
@ -1491,7 +1491,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "geth-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"env_logger 0.9.3",
|
||||
"gobuild 0.1.0-alpha.2 (git+https://github.com/scroll-tech/gobuild.git)",
|
||||
|
|
@ -2088,7 +2088,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "keccak256"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"env_logger 0.9.3",
|
||||
"eth-types",
|
||||
|
|
@ -2288,7 +2288,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mock"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"eth-types",
|
||||
"ethers-core",
|
||||
|
|
@ -2303,7 +2303,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mpt-zktrie"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"bus-mapping",
|
||||
"eth-types",
|
||||
|
|
@ -2769,8 +2769,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "prover"
|
||||
version = "0.6.4"
|
||||
source = "git+https://github.com/scroll-tech/scroll-prover?tag=v0.6.4#e81455aed85873dbf3d2779035ae9068cde0e612"
|
||||
version = "0.7.2"
|
||||
source = "git+https://github.com/scroll-tech/scroll-prover?tag=v0.7.2#a29cbaae9cb52b0eb61a4418caf6fbb6eb5d28f4"
|
||||
dependencies = [
|
||||
"aggregator",
|
||||
"anyhow",
|
||||
|
|
@ -4010,8 +4010,8 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
|||
|
||||
[[package]]
|
||||
name = "types"
|
||||
version = "0.6.4"
|
||||
source = "git+https://github.com/scroll-tech/scroll-prover?tag=v0.6.4#e81455aed85873dbf3d2779035ae9068cde0e612"
|
||||
version = "0.7.2"
|
||||
source = "git+https://github.com/scroll-tech/scroll-prover?tag=v0.7.2#a29cbaae9cb52b0eb61a4418caf6fbb6eb5d28f4"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"blake2",
|
||||
|
|
@ -4487,7 +4487,7 @@ checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
|||
[[package]]
|
||||
name = "zkevm-circuits"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.6.4#5af677cdd3e3b52afc9672ddb8bdedb3e21c7d82"
|
||||
source = "git+https://github.com/scroll-tech/zkevm-circuits.git?tag=v0.7.2#194216272f813944a7013f14d73d1de19375d2ce"
|
||||
dependencies = [
|
||||
"array-init",
|
||||
"bus-mapping",
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ maingate = { git = "https://github.com/scroll-tech/halo2wrong", branch = "halo2-
|
|||
halo2curves = { git = "https://github.com/scroll-tech/halo2curves.git", branch = "0.3.1-derive-serde" }
|
||||
|
||||
[dependencies]
|
||||
prover = { git = "https://github.com/scroll-tech/scroll-prover", tag = "v0.6.4" }
|
||||
types = { git = "https://github.com/scroll-tech/scroll-prover", tag = "v0.6.4" }
|
||||
prover = { git = "https://github.com/scroll-tech/scroll-prover", tag = "v0.7.2" }
|
||||
types = { git = "https://github.com/scroll-tech/scroll-prover", tag = "v0.7.2" }
|
||||
|
||||
log = "0.4"
|
||||
env_logger = "0.9.0"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ pub mod checker {
|
|||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RowUsageResult {
|
||||
pub acc_row_usage: Option<RowUsage>,
|
||||
pub tx_row_usage: Option<RowUsage>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +58,11 @@ pub mod checker {
|
|||
#[no_mangle]
|
||||
pub unsafe extern "C" fn apply_tx(id: u64, tx_traces: *const c_char) -> *const c_char {
|
||||
let result = panic::catch_unwind(|| {
|
||||
log::debug!("ccc apply_tx raw input, id: {:?}, tx_traces: {:?}", id, c_char_to_str(tx_traces));
|
||||
log::debug!(
|
||||
"ccc apply_tx raw input, id: {:?}, tx_traces: {:?}",
|
||||
id,
|
||||
c_char_to_str(tx_traces)
|
||||
);
|
||||
let tx_traces_vec = c_char_to_vec(tx_traces);
|
||||
let traces = serde_json::from_slice::<BlockTrace>(&tx_traces_vec)
|
||||
.unwrap_or_else(|_| panic!("id: {id:?}, fail to deserialize tx_traces"));
|
||||
|
|
@ -88,22 +91,19 @@ pub mod checker {
|
|||
})
|
||||
});
|
||||
let r = match result {
|
||||
Ok((acc_row_usage, tx_row_usage)) => {
|
||||
Ok(acc_row_usage) => {
|
||||
log::debug!(
|
||||
"id: {:?}, acc_row_usage: {:?}, tx_row_usage: {:?}",
|
||||
"id: {:?}, acc_row_usage: {:?}",
|
||||
id,
|
||||
acc_row_usage.row_number,
|
||||
tx_row_usage.row_number
|
||||
);
|
||||
RowUsageResult {
|
||||
acc_row_usage: Some(acc_row_usage),
|
||||
tx_row_usage: Some(tx_row_usage),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
Err(e) => RowUsageResult {
|
||||
acc_row_usage: None,
|
||||
tx_row_usage: None,
|
||||
error: Some(format!("{e:?}")),
|
||||
},
|
||||
};
|
||||
|
|
@ -114,7 +114,11 @@ pub mod checker {
|
|||
#[no_mangle]
|
||||
pub unsafe extern "C" fn apply_block(id: u64, block_trace: *const c_char) -> *const c_char {
|
||||
let result = panic::catch_unwind(|| {
|
||||
log::debug!("ccc apply_block raw input, id: {:?}, block_trace: {:?}", id, c_char_to_str(block_trace));
|
||||
log::debug!(
|
||||
"ccc apply_block raw input, id: {:?}, block_trace: {:?}",
|
||||
id,
|
||||
c_char_to_str(block_trace)
|
||||
);
|
||||
let block_trace = c_char_to_vec(block_trace);
|
||||
let traces = serde_json::from_slice::<BlockTrace>(&block_trace)
|
||||
.unwrap_or_else(|_| panic!("id: {id:?}, fail to deserialize block_trace"));
|
||||
|
|
@ -136,22 +140,19 @@ pub mod checker {
|
|||
})
|
||||
});
|
||||
let r = match result {
|
||||
Ok((acc_row_usage, tx_row_usage)) => {
|
||||
Ok(acc_row_usage) => {
|
||||
log::debug!(
|
||||
"id: {:?}, acc_row_usage: {:?}, tx_row_usage: {:?}",
|
||||
"id: {:?}, acc_row_usage: {:?}",
|
||||
id,
|
||||
acc_row_usage.row_number,
|
||||
tx_row_usage.row_number
|
||||
);
|
||||
RowUsageResult {
|
||||
acc_row_usage: Some(acc_row_usage),
|
||||
tx_row_usage: Some(tx_row_usage),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
Err(e) => RowUsageResult {
|
||||
acc_row_usage: None,
|
||||
tx_row_usage: None,
|
||||
error: Some(format!("{e:?}")),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,12 +8,10 @@ import (
|
|||
|
||||
var (
|
||||
ErrUnknown = errors.New("unknown circuit capacity checker error")
|
||||
ErrTxRowConsumptionOverflow = errors.New("tx row consumption oveflow")
|
||||
ErrBlockRowConsumptionOverflow = errors.New("block row consumption oveflow")
|
||||
)
|
||||
|
||||
type WrappedRowUsage struct {
|
||||
AccRowUsage *types.RowUsage `json:"acc_row_usage,omitempty"`
|
||||
TxRowUsage *types.RowUsage `json:"tx_row_usage,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue