diff --git a/config/default.go b/config/default.go index 9b7ec56291..1d41806e4e 100644 --- a/config/default.go +++ b/config/default.go @@ -105,7 +105,15 @@ TrustedSequencerURL = "" # If it is empty or not specified, then the value is re SyncBlockProtection = "safe" # latest, finalized, safe L1SynchronizationMode = "sequential" L1SyncCheckL2BlockHash = true -L1SyncCheckL2BlockNumberhModulus = 30 +L1SyncCheckL2BlockNumberhModulus = 600 + [Synchronizer.L1BlockCheck] + Enable = true + L1SafeBlockPoint = "finalized" + L1SafeBlockOffset = 0 + ForceCheckBeforeStart = true + PreCheckEnable = true + L1PreSafeBlockPoint = "safe" + L1PreSafeBlockOffset = 0 [Synchronizer.L1ParallelSynchronization] MaxClients = 10 MaxPendingNoProcessedBlocks = 25 diff --git a/config/environments/local/local.genesis.config.json b/config/environments/local/local.genesis.config.json index 93620d4f9a..f0e5ecf89b 100644 --- a/config/environments/local/local.genesis.config.json +++ b/config/environments/local/local.genesis.config.json @@ -14,8 +14,8 @@ "contractName": "PolygonZkEVMDeployer", "balance": "0", "nonce": "4", - "address": "0x51dbd54FCCb6b3A07738fd3E156D588e71f79973", - "bytecode": "0x6080604052600436106100705760003560e01c8063715018a61161004e578063715018a6146100e65780638da5cb5b146100fb578063e11ae6cb14610126578063f2fde38b1461013957600080fd5b80632b79805a146100755780634a94d4871461008a5780636d07dbf81461009d575b600080fd5b610088610083366004610927565b610159565b005b6100886100983660046109c7565b6101cb565b3480156100a957600080fd5b506100bd6100b8366004610a1e565b61020d565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100f257600080fd5b50610088610220565b34801561010757600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166100bd565b610088610134366004610a40565b610234565b34801561014557600080fd5b50610088610154366004610a90565b61029b565b610161610357565b600061016e8585856103d8565b905061017a8183610537565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a15050505050565b6101d3610357565b6101de83838361057b565b506040517f25adb19089b6a549831a273acdf7908cff8b7ee5f551f8d1d37996cf01c5df5b90600090a1505050565b600061021983836105a9565b9392505050565b610228610357565b61023260006105b6565b565b61023c610357565b60006102498484846103d8565b60405173ffffffffffffffffffffffffffffffffffffffff821681529091507fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a150505050565b6102a3610357565b73ffffffffffffffffffffffffffffffffffffffff811661034b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610354816105b6565b50565b60005473ffffffffffffffffffffffffffffffffffffffff163314610232576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610342565b600083471015610444576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e63650000006044820152606401610342565b81516000036104af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f6044820152606401610342565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116610219576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f79000000000000006044820152606401610342565b6060610219838360006040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c6564000081525061062b565b60606105a1848484604051806060016040528060298152602001610b3d6029913961062b565b949350505050565b6000610219838330610744565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060824710156106bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610342565b6000808673ffffffffffffffffffffffffffffffffffffffff1685876040516106e69190610acf565b60006040518083038185875af1925050503d8060008114610723576040519150601f19603f3d011682016040523d82523d6000602084013e610728565b606091505b50915091506107398783838761076e565b979650505050505050565b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b606083156108045782516000036107fd5773ffffffffffffffffffffffffffffffffffffffff85163b6107fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610342565b50816105a1565b6105a183838151156108195781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103429190610aeb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261088d57600080fd5b813567ffffffffffffffff808211156108a8576108a861084d565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156108ee576108ee61084d565b8160405283815286602085880101111561090757600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806000806080858703121561093d57600080fd5b8435935060208501359250604085013567ffffffffffffffff8082111561096357600080fd5b61096f8883890161087c565b9350606087013591508082111561098557600080fd5b506109928782880161087c565b91505092959194509250565b803573ffffffffffffffffffffffffffffffffffffffff811681146109c257600080fd5b919050565b6000806000606084860312156109dc57600080fd5b6109e58461099e565b9250602084013567ffffffffffffffff811115610a0157600080fd5b610a0d8682870161087c565b925050604084013590509250925092565b60008060408385031215610a3157600080fd5b50508035926020909101359150565b600080600060608486031215610a5557600080fd5b8335925060208401359150604084013567ffffffffffffffff811115610a7a57600080fd5b610a868682870161087c565b9150509250925092565b600060208284031215610aa257600080fd5b6102198261099e565b60005b83811015610ac6578181015183820152602001610aae565b50506000910152565b60008251610ae1818460208701610aab565b9190910192915050565b6020815260008251806020840152610b0a816040850160208701610aab565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c6564a2646970667358221220964619cee0e0baf94c6f8763f013be157da5d54c89e5cff4a8caf4266e13f13a64736f6c63430008140033", + "address": "0xFbD07134824dDEa24E4ae414c18ecbFa98169A24", + "bytecode": "0x60806040526004361061006e575f3560e01c8063715018a61161004c578063715018a6146100e25780638da5cb5b146100f6578063e11ae6cb1461011f578063f2fde38b14610132575f80fd5b80632b79805a146100725780634a94d487146100875780636d07dbf81461009a575b5f80fd5b610085610080366004610908565b610151565b005b6100856100953660046109a2565b6101c2565b3480156100a5575f80fd5b506100b96100b43660046109f5565b610203565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100ed575f80fd5b50610085610215565b348015610101575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff166100b9565b61008561012d366004610a15565b610228565b34801561013d575f80fd5b5061008561014c366004610a61565b61028e565b61015961034a565b5f6101658585856103ca565b90506101718183610527565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a15050505050565b6101ca61034a565b6101d583838361056a565b506040517f25adb19089b6a549831a273acdf7908cff8b7ee5f551f8d1d37996cf01c5df5b905f90a1505050565b5f61020e8383610598565b9392505050565b61021d61034a565b6102265f6105a4565b565b61023061034a565b5f61023c8484846103ca565b60405173ffffffffffffffffffffffffffffffffffffffff821681529091507fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a150505050565b61029661034a565b73ffffffffffffffffffffffffffffffffffffffff811661033e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610347816105a4565b50565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610226576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610335565b5f83471015610435576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e63650000006044820152606401610335565b81515f0361049f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f6044820152606401610335565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff811661020e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f79000000000000006044820152606401610335565b606061020e83835f6040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c65640000815250610618565b6060610590848484604051806060016040528060298152602001610b0860299139610618565b949350505050565b5f61020e83833061072d565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060824710156106aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610335565b5f808673ffffffffffffffffffffffffffffffffffffffff1685876040516106d29190610a9c565b5f6040518083038185875af1925050503d805f811461070c576040519150601f19603f3d011682016040523d82523d5f602084013e610711565b606091505b509150915061072287838387610756565b979650505050505050565b5f604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b606083156107eb5782515f036107e45773ffffffffffffffffffffffffffffffffffffffff85163b6107e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610335565b5081610590565b61059083838151156108005781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103359190610ab7565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f82601f830112610870575f80fd5b813567ffffffffffffffff8082111561088b5761088b610834565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156108d1576108d1610834565b816040528381528660208588010111156108e9575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f805f806080858703121561091b575f80fd5b8435935060208501359250604085013567ffffffffffffffff80821115610940575f80fd5b61094c88838901610861565b93506060870135915080821115610961575f80fd5b5061096e87828801610861565b91505092959194509250565b803573ffffffffffffffffffffffffffffffffffffffff8116811461099d575f80fd5b919050565b5f805f606084860312156109b4575f80fd5b6109bd8461097a565b9250602084013567ffffffffffffffff8111156109d8575f80fd5b6109e486828701610861565b925050604084013590509250925092565b5f8060408385031215610a06575f80fd5b50508035926020909101359150565b5f805f60608486031215610a27575f80fd5b8335925060208401359150604084013567ffffffffffffffff811115610a4b575f80fd5b610a5786828701610861565b9150509250925092565b5f60208284031215610a71575f80fd5b61020e8261097a565b5f5b83811015610a94578181015183820152602001610a7c565b50505f910152565b5f8251610aad818460208701610a7a565b9190910192915050565b602081525f8251806020840152610ad5816040850160208701610a7a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c6564a2646970667358221220330b94dc698c4d290bf55c23f13b473cde6a6bae0030cb902de18af54e35839f64736f6c63430008140033", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" } @@ -24,8 +24,8 @@ "contractName": "ProxyAdmin", "balance": "0", "nonce": "1", - "address": "0xe34Fe58DDa5b8c6D547E4857E987633aa86a5e90", - "bytecode": "0x60806040526004361061007b5760003560e01c80639623609d1161004e5780639623609d1461012b57806399a88ec41461013e578063f2fde38b1461015e578063f3b7dead1461017e57600080fd5b8063204e1c7a14610080578063715018a6146100c95780637eff275e146100e05780638da5cb5b14610100575b600080fd5b34801561008c57600080fd5b506100a061009b366004610608565b61019e565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100d557600080fd5b506100de610255565b005b3480156100ec57600080fd5b506100de6100fb36600461062c565b610269565b34801561010c57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166100a0565b6100de610139366004610694565b6102f7565b34801561014a57600080fd5b506100de61015936600461062c565b61038c565b34801561016a57600080fd5b506100de610179366004610608565b6103e8565b34801561018a57600080fd5b506100a0610199366004610608565b6104a4565b60008060008373ffffffffffffffffffffffffffffffffffffffff166040516101ea907f5c60da1b00000000000000000000000000000000000000000000000000000000815260040190565b600060405180830381855afa9150503d8060008114610225576040519150601f19603f3d011682016040523d82523d6000602084013e61022a565b606091505b50915091508161023957600080fd5b8080602001905181019061024d9190610788565b949350505050565b61025d6104f0565b6102676000610571565b565b6102716104f0565b6040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690638f283970906024015b600060405180830381600087803b1580156102db57600080fd5b505af11580156102ef573d6000803e3d6000fd5b505050505050565b6102ff6104f0565b6040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841690634f1ef28690349061035590869086906004016107a5565b6000604051808303818588803b15801561036e57600080fd5b505af1158015610382573d6000803e3d6000fd5b5050505050505050565b6103946104f0565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690633659cfe6906024016102c1565b6103f06104f0565b73ffffffffffffffffffffffffffffffffffffffff8116610498576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b6104a181610571565b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff166040516101ea907ff851a44000000000000000000000000000000000000000000000000000000000815260040190565b60005473ffffffffffffffffffffffffffffffffffffffff163314610267576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161048f565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff811681146104a157600080fd5b60006020828403121561061a57600080fd5b8135610625816105e6565b9392505050565b6000806040838503121561063f57600080fd5b823561064a816105e6565b9150602083013561065a816105e6565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156106a957600080fd5b83356106b4816105e6565b925060208401356106c4816105e6565b9150604084013567ffffffffffffffff808211156106e157600080fd5b818601915086601f8301126106f557600080fd5b81358181111561070757610707610665565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561074d5761074d610665565b8160405282815289602084870101111561076657600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b60006020828403121561079a57600080fd5b8151610625816105e6565b73ffffffffffffffffffffffffffffffffffffffff8316815260006020604081840152835180604085015260005b818110156107ef578581018301518582016060015282016107d3565b5060006060828601015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010192505050939250505056fea2646970667358221220c9867ffac53151bdb1305d8f5e3e883cd83e5270c7ec09cdc24e837b2e65239064736f6c63430008140033", + "address": "0xfADB60b5059e31614e02083fF6C021a24C31c891", + "bytecode": "0x608060405260043610610079575f3560e01c80639623609d1161004c5780639623609d1461012357806399a88ec414610136578063f2fde38b14610155578063f3b7dead14610174575f80fd5b8063204e1c7a1461007d578063715018a6146100c55780637eff275e146100db5780638da5cb5b146100fa575b5f80fd5b348015610088575f80fd5b5061009c6100973660046105e8565b610193565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100d0575f80fd5b506100d9610244565b005b3480156100e6575f80fd5b506100d96100f536600461060a565b610257565b348015610105575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff1661009c565b6100d961013136600461066e565b6102e0565b348015610141575f80fd5b506100d961015036600461060a565b610371565b348015610160575f80fd5b506100d961016f3660046105e8565b6103cd565b34801561017f575f80fd5b5061009c61018e3660046105e8565b610489565b5f805f8373ffffffffffffffffffffffffffffffffffffffff166040516101dd907f5c60da1b00000000000000000000000000000000000000000000000000000000815260040190565b5f60405180830381855afa9150503d805f8114610215576040519150601f19603f3d011682016040523d82523d5f602084013e61021a565b606091505b509150915081610228575f80fd5b8080602001905181019061023c919061075b565b949350505050565b61024c6104d3565b6102555f610553565b565b61025f6104d3565b6040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690638f283970906024015b5f604051808303815f87803b1580156102c6575f80fd5b505af11580156102d8573d5f803e3d5ffd5b505050505050565b6102e86104d3565b6040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841690634f1ef28690349061033e9086908690600401610776565b5f604051808303818588803b158015610355575f80fd5b505af1158015610367573d5f803e3d5ffd5b5050505050505050565b6103796104d3565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690633659cfe6906024016102af565b6103d56104d3565b73ffffffffffffffffffffffffffffffffffffffff811661047d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b61048681610553565b50565b5f805f8373ffffffffffffffffffffffffffffffffffffffff166040516101dd907ff851a44000000000000000000000000000000000000000000000000000000000815260040190565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610255576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610474565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff81168114610486575f80fd5b5f602082840312156105f8575f80fd5b8135610603816105c7565b9392505050565b5f806040838503121561061b575f80fd5b8235610626816105c7565b91506020830135610636816105c7565b809150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f805f60608486031215610680575f80fd5b833561068b816105c7565b9250602084013561069b816105c7565b9150604084013567ffffffffffffffff808211156106b7575f80fd5b818601915086601f8301126106ca575f80fd5b8135818111156106dc576106dc610641565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561072257610722610641565b8160405282815289602084870101111561073a575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f6020828403121561076b575f80fd5b8151610603816105c7565b73ffffffffffffffffffffffffffffffffffffffff831681525f602060408184015283518060408501525f5b818110156107be578581018301518582016060015282016107a2565b505f6060828601015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010192505050939250505056fea26469706673582212203083a4ccc2e42eed60bd19037f2efa77ed086dc7a5403f75bebb995dcba2221c64736f6c63430008140033", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f" } @@ -62,7 +62,7 @@ "address": "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa", "bytecode": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122012bb4f564f73959a03513dc74fc3c6e40e8386e6f02c16b78d6db00ce0aa16af64736f6c63430008090033", "storage": { - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000e34fe58dda5b8c6d547e4857e987633aa86a5e90", + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000fadb60b5059e31614e02083ff6c021a24c31c891", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000dc64a140aa3e981100a9beca4e685f962f0cf6c9" } }, @@ -89,7 +89,7 @@ "accountName": "keyless Deployer", "balance": "0", "nonce": "1", - "address": "0x28BB4e66addE1f042B77E04cf7D3784C1dcDBbA3" + "address": "0x694AB5383a002a4796f95530c14Cf0C25ec3EA03" }, { "accountName": "deployer", diff --git a/db/migrations/state/0019.sql b/db/migrations/state/0019.sql new file mode 100644 index 0000000000..bd982feab1 --- /dev/null +++ b/db/migrations/state/0019.sql @@ -0,0 +1,25 @@ +-- +migrate Up + +-- the update below fix the wrong receipt TX indexes +WITH map_fix_tx_index AS ( + SELECT t.l2_block_num AS block_num + , t.hash AS tx_hash + , r.tx_index AS current_index + , (ROW_NUMBER() OVER (PARTITION BY t.l2_block_num ORDER BY r.tx_index))-1 AS correct_index + FROM state.receipt r + INNER JOIN state."transaction" t + ON t.hash = r.tx_hash +) +UPDATE state.receipt AS r + SET tx_index = m.correct_index + FROM map_fix_tx_index m + WHERE m.block_num = r.block_num + AND m.tx_hash = r.tx_hash + AND m.current_index = r.tx_index + AND m.current_index != m.correct_index; + + +-- +migrate Down + +-- no action is needed, the data fixed by the +-- migrate up must remain fixed \ No newline at end of file diff --git a/db/migrations/state/0019_test.go b/db/migrations/state/0019_test.go new file mode 100644 index 0000000000..7205998d60 --- /dev/null +++ b/db/migrations/state/0019_test.go @@ -0,0 +1,145 @@ +package migrations_test + +import ( + "database/sql" + "testing" + + "github.com/0xPolygonHermez/zkevm-node/hex" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +type migrationTest0019TestCase struct { + Name string + Block migrationTest0019TestCaseBlock +} + +type migrationTest0019TestCaseBlock struct { + Transactions []migrationTest0019TestCaseTransaction +} + +type migrationTest0019TestCaseTransaction struct { + CurrentIndex uint +} + +type migrationTest0019 struct { + TestCases []migrationTest0019TestCase +} + +func (m migrationTest0019) InsertData(db *sql.DB) error { + const addBlock0 = "INSERT INTO state.block (block_num, received_at, block_hash) VALUES (0, now(), '0x0')" + if _, err := db.Exec(addBlock0); err != nil { + return err + } + + const addBatch0 = ` + INSERT INTO state.batch (batch_num, global_exit_root, local_exit_root, acc_input_hash, state_root, timestamp, coinbase, raw_txs_data, forced_batch_num, wip) + VALUES (0,'0x0000', '0x0000', '0x0000', '0x0000', now(), '0x0000', null, null, true)` + if _, err := db.Exec(addBatch0); err != nil { + return err + } + + const addL2Block = "INSERT INTO state.l2block (block_num, block_hash, header, uncles, parent_hash, state_root, received_at, batch_num, created_at) VALUES ($1, $2, '{}', '{}', '0x0', '0x0', now(), 0, now())" + const addTransaction = "INSERT INTO state.transaction (hash, encoded, decoded, l2_block_num, effective_percentage, l2_hash) VALUES ($1, 'ABCDEF', '{}', $2, 255, $1)" + const addReceipt = "INSERT INTO state.receipt (tx_hash, type, post_state, status, cumulative_gas_used, gas_used, effective_gas_price, block_num, tx_index, contract_address) VALUES ($1, 1, null, 1, 1234, 1234, 1, $2, $3, '')" + + txUnique := 0 + for tci, testCase := range m.TestCases { + blockNum := uint64(tci + 1) + blockHash := common.HexToHash(hex.EncodeUint64(blockNum)).String() + if _, err := db.Exec(addL2Block, blockNum, blockHash); err != nil { + return err + } + for _, tx := range testCase.Block.Transactions { + txUnique++ + txHash := common.HexToHash(hex.EncodeUint64(uint64(txUnique))).String() + if _, err := db.Exec(addTransaction, txHash, blockNum); err != nil { + return err + } + if _, err := db.Exec(addReceipt, txHash, blockNum, tx.CurrentIndex); err != nil { + return err + } + } + } + + return nil +} + +func (m migrationTest0019) RunAssertsAfterMigrationUp(t *testing.T, db *sql.DB) { + const getReceiptsByBlock = "SELECT r.tx_index FROM state.receipt r WHERE r.block_num = $1 ORDER BY r.tx_index" + + for tci := range m.TestCases { + blockNum := uint64(tci + 1) + + rows, err := db.Query(getReceiptsByBlock, blockNum) + require.NoError(t, err) + + var expectedIndex = uint(0) + var txIndex uint + for rows.Next() { + err := rows.Scan(&txIndex) + require.NoError(t, err) + require.Equal(t, expectedIndex, txIndex) + expectedIndex++ + } + } +} + +func (m migrationTest0019) RunAssertsAfterMigrationDown(t *testing.T, db *sql.DB) { + m.RunAssertsAfterMigrationUp(t, db) +} + +func TestMigration0019(t *testing.T) { + runMigrationTest(t, 19, migrationTest0019{ + TestCases: []migrationTest0019TestCase{ + { + Name: "single tx with correct index", + Block: migrationTest0019TestCaseBlock{ + Transactions: []migrationTest0019TestCaseTransaction{ + {CurrentIndex: 0}, + }, + }, + }, + { + Name: "multiple txs indexes are correct", + Block: migrationTest0019TestCaseBlock{ + Transactions: []migrationTest0019TestCaseTransaction{ + {CurrentIndex: 0}, + {CurrentIndex: 1}, + {CurrentIndex: 2}, + }, + }, + }, + { + Name: "single tx with wrong tx index", + Block: migrationTest0019TestCaseBlock{ + Transactions: []migrationTest0019TestCaseTransaction{ + {CurrentIndex: 3}, + }, + }, + }, + { + Name: "multiple txs missing 0 index", + Block: migrationTest0019TestCaseBlock{ + Transactions: []migrationTest0019TestCaseTransaction{ + {CurrentIndex: 1}, + {CurrentIndex: 2}, + {CurrentIndex: 3}, + {CurrentIndex: 4}, + }, + }, + }, + { + Name: "multiple has index 0 but also txs index gap", + Block: migrationTest0019TestCaseBlock{ + Transactions: []migrationTest0019TestCaseTransaction{ + {CurrentIndex: 0}, + {CurrentIndex: 2}, + {CurrentIndex: 4}, + {CurrentIndex: 6}, + }, + }, + }, + }, + }) +} diff --git a/db/migrations/state/0020.sql b/db/migrations/state/0020.sql new file mode 100644 index 0000000000..1068b6f8da --- /dev/null +++ b/db/migrations/state/0020.sql @@ -0,0 +1,28 @@ +-- +migrate Up + +-- This migration will delete all empty blocks +DELETE FROM state.block +WHERE NOT EXISTS (SELECT * + FROM state.virtual_batch + WHERE state.virtual_batch.block_num = state.block.block_num) + AND NOT EXISTS (SELECT * + FROM state.verified_batch + WHERE state.verified_batch.block_num = state.block.block_num) + AND NOT EXISTS (SELECT * + FROM state.forced_batch + WHERE state.forced_batch.block_num = state.block.block_num) + AND NOT EXISTS (SELECT * + FROM state.exit_root + WHERE state.exit_root.block_num = state.block.block_num) + AND NOT EXISTS (SELECT * + FROM state.monitored_txs + WHERE state.monitored_txs.block_num = state.block.block_num) + AND NOT EXISTS (SELECT * + FROM state.fork_id + WHERE state.fork_id.block_num = state.block.block_num); + + + +-- +migrate Down + +-- no action is needed, the data must remain deleted as it is useless \ No newline at end of file diff --git a/db/migrations/state/0020_test.go b/db/migrations/state/0020_test.go new file mode 100644 index 0000000000..e58ea23381 --- /dev/null +++ b/db/migrations/state/0020_test.go @@ -0,0 +1,99 @@ +package migrations_test + +import ( + "database/sql" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +// this migration changes length of the token name +type migrationTest0020 struct{} + +func (m migrationTest0020) InsertData(db *sql.DB) error { + addBlocks := ` + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(1, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b20', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50fe', '2024-03-11 02:52:23.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(2, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b21', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f1', '2024-03-11 02:52:24.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(3, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b22', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f2', '2024-03-11 02:52:25.000', false); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(4, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b23', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f3', '2024-03-11 02:52:26.000', false); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(5, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b24', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f4', '2024-03-11 02:52:27.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(6, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b25', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f5', '2024-03-11 02:52:28.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(7, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b26', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f6', '2024-03-11 02:52:29.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(8, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b27', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f7', '2024-03-11 02:52:30.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(9, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b28', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f8', '2024-03-11 02:52:31.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(10, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b29', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50f9', '2024-03-11 02:52:32.000', true); + INSERT INTO state.block + (block_num, block_hash, parent_hash, received_at, checked) + VALUES(11, '0x013be63487a53c874614dd1ae0434cf211e393b2e386c8fde74da203b5469b2a', '0x0328698ebeda498df8c63040e2a4771d24722ab2c1e8291226b9215c7eec50fa', '2024-03-11 02:52:33.000', true); + INSERT INTO state.batch + (batch_num, global_exit_root, local_exit_root, state_root, acc_input_hash, "timestamp", coinbase, raw_txs_data, forced_batch_num, batch_resources, closing_reason, wip, checked) + VALUES(1, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', '0x3f86b09b43e3e49a41fc20a07579b79eba044253367817d5c241d23c0e2bc5c9', '0xa5bd7311fe00707809dd3aa718be2ea0cb363626b9db44172098515f07acf940', '2023-03-24 16:35:27.000', '0x148Ee7dAF16574cD020aFa34CC658f8F3fbd2800', decode('','hex'), NULL, '{"Bytes": 0, "ZKCounters": {"GasUsed": 0, "UsedSteps": 0, "UsedBinaries": 0, "UsedMemAligns": 0, "UsedArithmetics": 0, "UsedKeccakHashes": 0, "UsedPoseidonHashes": 0, "UsedSha256Hashes_V2": 0, "UsedPoseidonPaddings": 0}}'::jsonb, '', false, true); + INSERT INTO state.virtual_batch + (batch_num, tx_hash, coinbase, block_num, sequencer_addr, timestamp_batch_etrog, l1_info_root) + VALUES(1, '0x4314ed5d8ad4812e88895942b2b4642af176d80a97c5489a16a7a5aeb08b51a6', '0x148Ee7dAF16574cD020aFa34CC658f8F3fbd2800', 2, '0x148Ee7dAF16574cD020aFa34CC658f8F3fbd2800', '2024-04-09 16:26:45.000', '0xcdb4258d7ccd8fd41c4a26fd8d9d1fadbc9c506e64d489170525a65e2ad3580b'); + INSERT INTO state.verified_batch + (batch_num, tx_hash, aggregator, state_root, block_num, is_trusted) + VALUES(1, '0x28e82f15ab7bac043598623c65a838c315d00ecb5d6e013c406d6bb889680592', '0x6329Fe417621925C81c16F9F9a18c203C21Af7ab', '0x80bd488b1e150b9b42611d038c7fdfa43a3e95b3a02e5c2d57074e73b583f8fd', 3, true); + INSERT INTO state.fork_id + (fork_id, from_batch_num, to_batch_num, "version", block_num) + VALUES(5, 813267, 1228916, 'v2.0.0-RC1-fork.5', 5); + INSERT INTO state.monitored_txs + ("owner", id, from_addr, to_addr, nonce, value, "data", gas, gas_price, status, history, block_num, created_at, updated_at, gas_offset) + VALUES('sequencer', 'sequence-from-2006249-to-2006252', '0x148Ee7dAF16574cD020aFa34CC658f8F3fbd2800', '0x519E42c24163192Dca44CD3fBDCEBF6be9130987', 58056, NULL, 'def57e540000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000006614ec3100000000000000000000000000000000000000000000000000000000001e9ce8000000000000000000000000148ee7da0000000300000000ee8306089a84ae0baa0082520894417a7ba2d8d0060ae6c54fd098590db854b9c1d58609184e72a0008082044d80802787e068e6fe23cda64eb868cefb7231a17449d508a77919f6c5408814aaab5f259d43a62eb50df0b2d5740552d3f95176a1f0e31cade590facf70b01c1129151bab0b00000003000000000b00000003000000000b00000003000000000b00000003000000000b0000000300000000000000000000000000000000000000', 1474265, 25212431373, 'done', '{0x44423d538d6fc2f2e882fcd0d1952a735d81c824827b83936e6a5e52268a7d8e}', 7, '2024-04-09 09:26:36.235', '2024-04-09 09:38:24.377', 150000); + INSERT INTO state.exit_root + (id, block_num, "timestamp", mainnet_exit_root, rollup_exit_root, global_exit_root, prev_block_hash, l1_info_root, l1_info_tree_index) + VALUES(379599, 8, '2024-04-09 09:43:59.000', decode('C90DCBC69719971625800AD619E5EEEFD0378317E26F0DDE9B30B3C7C84DBD78','hex'), decode('514D72BBF7C2AD8E4D15EC1186EBF077E98208479651B1C30C5AC7DA11BAB209','hex'), decode('B20FACBED4A2774CE33A0F68D9B6F9B4D9AD553DACD73705503910B141D2102E','hex'), decode('845E01F723E5C77DBE5A4889F299860FBECD8353BFD423D366851F3A90496334','hex'), decode('EDB0EF9C80E947C411FD9B8B23318708132F8A3BD15CD366499866EF91748FC8','hex'), 8032); + INSERT INTO state.forced_batch + (block_num, forced_batch_num, global_exit_root, timestamp, raw_txs_data, coinbase) + VALUES(10, 1, '0x3f86b09b43e3e49a41fc20a07579b79eba044253367817d5c241d23c0e2bc5ca', '2024-04-09 09:26:36.235', '0x3f86b09b', '0x3f86b09b43e3e49a41fc20a07579b79eba044253367817d5c241d23c0e2bc5c9'); + ` + if _, err := db.Exec(addBlocks); err != nil { + return err + } + blockCount := `SELECT count(*) FROM state.block` + var count int + err := db.QueryRow(blockCount).Scan(&count) + if err != nil { + return err + } + if count != 11 { + return fmt.Errorf("error: initial wrong number of blocks") + } + return nil +} + +func (m migrationTest0020) RunAssertsAfterMigrationUp(t *testing.T, db *sql.DB) { + blockCount := `SELECT count(*) FROM state.block` + var count int + err := db.QueryRow(blockCount).Scan(&count) + assert.NoError(t, err) + assert.Equal(t, 6, count) +} + +func (m migrationTest0020) RunAssertsAfterMigrationDown(t *testing.T, db *sql.DB) { +} + +func TestMigration0020(t *testing.T) { + runMigrationTest(t, 20, migrationTest0020{}) +} diff --git a/docs/config-file/node-config-doc.html b/docs/config-file/node-config-doc.html index 34bd2d4ed1..e21ad83563 100644 --- a/docs/config-file/node-config-doc.html +++ b/docs/config-file/node-config-doc.html @@ -16,7 +16,7 @@
"300ms"
 

Default: 500Type: number

MaxRequestsPerIPAndSecond defines how much requests a single IP can
send within a single second


Default: ""Type: string

SequencerNodeURI is used allow Non-Sequencer nodes
to relay transactions to the Sequencer node


Default: 0Type: integer

MaxCumulativeGasUsed is the max gas allowed per batch


WebSockets configuration
Default: trueType: boolean

Enabled defines if the WebSocket requests are enabled or disabled


Default: "0.0.0.0"Type: string

Host defines the network adapter that will be used to serve the WS requests


Default: 8546Type: integer

Port defines the port to serve the endpoints via WS


Default: 104857600Type: integer

ReadLimit defines the maximum size of a message read from the client (in bytes)


Default: trueType: boolean

EnableL2SuggestedGasPricePolling enables polling of the L2 gas price to block tx in the RPC with lower gas price.


Default: falseType: boolean

BatchRequestsEnabled defines if the Batch requests are enabled or disabled


Default: 20Type: integer

BatchRequestsLimit defines the limit of requests that can be incorporated into each batch request


Type: array of integer

L2Coinbase defines which address is going to receive the fees

Must contain a minimum of 20 items

Must contain a maximum of 20 items

Each item of this array must be:

Type: integer

Default: 10000Type: integer

MaxLogsCount is a configuration to set the max number of logs that can be returned
in a single call to the state, if zero it means no limit


Default: 10000Type: integer

MaxLogsBlockRange is a configuration to set the max range for block number when querying TXs
logs in a single call to the state, if zero it means no limit


Default: 60000Type: integer

MaxNativeBlockHashBlockRange is a configuration to set the max range for block number when querying
native block hashes in a single call to the state, if zero it means no limit


Default: trueType: boolean

EnableHttpLog allows the user to enable or disable the logs related to the HTTP
requests to be captured by the server.


ZKCountersLimits defines the ZK Counter limits
Default: 0Type: integer

Default: 0Type: integer

Default: 0Type: integer

Default: 0Type: integer

Default: 0Type: integer

Default: 0Type: integer

Default: 0Type: integer

Default: 0Type: integer

Configuration of service `Syncrhonizer`. For this service is also really important the value of `IsTrustedSequencer` because depending of this values is going to ask to a trusted node for trusted transactions or not
Default: "1s"Type: string

SyncInterval is the delay interval between reading new rollup information


Examples:

"1m"
 
"300ms"
-

Default: 100Type: integer

SyncChunkSize is the number of blocks to sync on each chunk


Default: ""Type: string

TrustedSequencerURL is the rpc url to connect and sync the trusted state


Default: "safe"Type: string

SyncBlockProtection specify the state to sync (lastest, finalized or safe)


Default: trueType: boolean

L1SyncCheckL2BlockHash if is true when a batch is closed is force to check L2Block hash against trustedNode (only apply for permissionless)


Default: 30Type: integer

L1SyncCheckL2BlockNumberhModulus is the modulus used to choose the l2block to check
a modules 5, for instance, means check all l2block multiples of 5 (10,15,20,...)


Default: "sequential"Type: enum (of string)

L1SynchronizationMode define how to synchronize with L1:
- parallel: Request data to L1 in parallel, and process sequentially. The advantage is that executor is not blocked waiting for L1 data
- sequential: Request data to L1 and execute

Must be one of:

  • "sequential"
  • "parallel"

L1ParallelSynchronization Configuration for parallel mode (if L1SynchronizationMode equal to 'parallel')
Default: 10Type: integer

MaxClients Number of clients used to synchronize with L1


Default: 25Type: integer

MaxPendingNoProcessedBlocks Size of the buffer used to store rollup information from L1, must be >= to NumberOfEthereumClientsToSync
sugested twice of NumberOfParallelOfEthereumClients


Default: "5s"Type: string

RequestLastBlockPeriod is the time to wait to request the
last block to L1 to known if we need to retrieve more data.
This value only apply when the system is synchronized


Examples:

"1m"
+

Default: 100Type: integer

SyncChunkSize is the number of blocks to sync on each chunk


Default: ""Type: string

TrustedSequencerURL is the rpc url to connect and sync the trusted state


Default: "safe"Type: string

SyncBlockProtection specify the state to sync (lastest, finalized or safe)


Default: trueType: boolean

L1SyncCheckL2BlockHash if is true when a batch is closed is force to check L2Block hash against trustedNode (only apply for permissionless)


Default: 600Type: integer

L1SyncCheckL2BlockNumberhModulus is the modulus used to choose the l2block to check
a modules 5, for instance, means check all l2block multiples of 5 (10,15,20,...)


Default: trueType: boolean

Enable if is true then the check l1 Block Hash is active


Default: "finalized"Type: enum (of string)

L1SafeBlockPoint is the point that a block is considered safe enough to be checked
it can be: finalized, safe,pending or latest

Must be one of:

  • "finalized"
  • "safe"
  • "latest"

Default: 0Type: integer

L1SafeBlockOffset is the offset to add to L1SafeBlockPoint as a safe point
it can be positive or negative
Example: L1SafeBlockPoint= finalized, L1SafeBlockOffset= -10, then the safe block ten blocks before the finalized block


Default: trueType: boolean

ForceCheckBeforeStart if is true then the first time the system is started it will force to check all pending blocks


Default: trueType: boolean

PreCheckEnable if is true then the pre-check is active, will check blocks between L1SafeBlock and L1PreSafeBlock


Default: "safe"Type: enum (of string)

L1PreSafeBlockPoint is the point that a block is considered safe enough to be checked
it can be: finalized, safe,pending or latest

Must be one of:

  • "finalized"
  • "safe"
  • "latest"

Default: 0Type: integer

L1PreSafeBlockOffset is the offset to add to L1PreSafeBlockPoint as a safe point
it can be positive or negative
Example: L1PreSafeBlockPoint= finalized, L1PreSafeBlockOffset= -10, then the safe block ten blocks before the finalized block


Default: "sequential"Type: enum (of string)

L1SynchronizationMode define how to synchronize with L1:
- parallel: Request data to L1 in parallel, and process sequentially. The advantage is that executor is not blocked waiting for L1 data
- sequential: Request data to L1 and execute

Must be one of:

  • "sequential"
  • "parallel"

L1ParallelSynchronization Configuration for parallel mode (if L1SynchronizationMode equal to 'parallel')
Default: 10Type: integer

MaxClients Number of clients used to synchronize with L1


Default: 25Type: integer

MaxPendingNoProcessedBlocks Size of the buffer used to store rollup information from L1, must be >= to NumberOfEthereumClientsToSync
sugested twice of NumberOfParallelOfEthereumClients


Default: "5s"Type: string

RequestLastBlockPeriod is the time to wait to request the
last block to L1 to known if we need to retrieve more data.
This value only apply when the system is synchronized


Examples:

"1m"
 
"300ms"
 

Consumer Configuration for the consumer of rollup information from L1
Default: "5s"Type: string

AceptableInacctivityTime is the expected maximum time that the consumer
could wait until new data is produced. If the time is greater it emmit a log to warn about
that. The idea is keep working the consumer as much as possible, so if the producer is not
fast enought then you could increse the number of parallel clients to sync with L1


Examples:

"1m"
 
"300ms"
diff --git a/docs/config-file/node-config-doc.md b/docs/config-file/node-config-doc.md
index 046008f0af..925ac0f95c 100644
--- a/docs/config-file/node-config-doc.md
+++ b/docs/config-file/node-config-doc.md
@@ -1342,6 +1342,7 @@ because depending of this values is going to ask to a trusted node for trusted t
 | - [SyncBlockProtection](#Synchronizer_SyncBlockProtection )                           | No      | string           | No         | -          | SyncBlockProtection specify the state to sync (lastest, finalized or safe)                                                                                                                                                                              |
 | - [L1SyncCheckL2BlockHash](#Synchronizer_L1SyncCheckL2BlockHash )                     | No      | boolean          | No         | -          | L1SyncCheckL2BlockHash if is true when a batch is closed is force to check  L2Block hash against trustedNode (only apply for permissionless)                                                                                                            |
 | - [L1SyncCheckL2BlockNumberhModulus](#Synchronizer_L1SyncCheckL2BlockNumberhModulus ) | No      | integer          | No         | -          | L1SyncCheckL2BlockNumberhModulus is the modulus used to choose the l2block to check
a modules 5, for instance, means check all l2block multiples of 5 (10,15,20,...) | +| - [L1BlockCheck](#Synchronizer_L1BlockCheck ) | No | object | No | - | - | | - [L1SynchronizationMode](#Synchronizer_L1SynchronizationMode ) | No | enum (of string) | No | - | L1SynchronizationMode define how to synchronize with L1:
- parallel: Request data to L1 in parallel, and process sequentially. The advantage is that executor is not blocked waiting for L1 data
- sequential: Request data to L1 and execute | | - [L1ParallelSynchronization](#Synchronizer_L1ParallelSynchronization ) | No | object | No | - | L1ParallelSynchronization Configuration for parallel mode (if L1SynchronizationMode equal to 'parallel') | | - [L2Synchronization](#Synchronizer_L2Synchronization ) | No | object | No | - | L2Synchronization Configuration for L2 synchronization | @@ -1432,18 +1433,146 @@ L1SyncCheckL2BlockHash=true **Type:** : `integer` -**Default:** `30` +**Default:** `600` **Description:** L1SyncCheckL2BlockNumberhModulus is the modulus used to choose the l2block to check a modules 5, for instance, means check all l2block multiples of 5 (10,15,20,...) -**Example setting the default value** (30): +**Example setting the default value** (600): ``` [Synchronizer] -L1SyncCheckL2BlockNumberhModulus=30 +L1SyncCheckL2BlockNumberhModulus=600 ``` -### 9.7. `Synchronizer.L1SynchronizationMode` +### 9.7. `[Synchronizer.L1BlockCheck]` + +**Type:** : `object` + +| Property | Pattern | Type | Deprecated | Definition | Title/Description | +| ---------------------------------------------------------------------------- | ------- | ---------------- | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| - [Enable](#Synchronizer_L1BlockCheck_Enable ) | No | boolean | No | - | Enable if is true then the check l1 Block Hash is active | +| - [L1SafeBlockPoint](#Synchronizer_L1BlockCheck_L1SafeBlockPoint ) | No | enum (of string) | No | - | L1SafeBlockPoint is the point that a block is considered safe enough to be checked
it can be: finalized, safe,pending or latest | +| - [L1SafeBlockOffset](#Synchronizer_L1BlockCheck_L1SafeBlockOffset ) | No | integer | No | - | L1SafeBlockOffset is the offset to add to L1SafeBlockPoint as a safe point
it can be positive or negative
Example: L1SafeBlockPoint= finalized, L1SafeBlockOffset= -10, then the safe block ten blocks before the finalized block | +| - [ForceCheckBeforeStart](#Synchronizer_L1BlockCheck_ForceCheckBeforeStart ) | No | boolean | No | - | ForceCheckBeforeStart if is true then the first time the system is started it will force to check all pending blocks | +| - [PreCheckEnable](#Synchronizer_L1BlockCheck_PreCheckEnable ) | No | boolean | No | - | PreCheckEnable if is true then the pre-check is active, will check blocks between L1SafeBlock and L1PreSafeBlock | +| - [L1PreSafeBlockPoint](#Synchronizer_L1BlockCheck_L1PreSafeBlockPoint ) | No | enum (of string) | No | - | L1PreSafeBlockPoint is the point that a block is considered safe enough to be checked
it can be: finalized, safe,pending or latest | +| - [L1PreSafeBlockOffset](#Synchronizer_L1BlockCheck_L1PreSafeBlockOffset ) | No | integer | No | - | L1PreSafeBlockOffset is the offset to add to L1PreSafeBlockPoint as a safe point
it can be positive or negative
Example: L1PreSafeBlockPoint= finalized, L1PreSafeBlockOffset= -10, then the safe block ten blocks before the finalized block | + +#### 9.7.1. `Synchronizer.L1BlockCheck.Enable` + +**Type:** : `boolean` + +**Default:** `true` + +**Description:** Enable if is true then the check l1 Block Hash is active + +**Example setting the default value** (true): +``` +[Synchronizer.L1BlockCheck] +Enable=true +``` + +#### 9.7.2. `Synchronizer.L1BlockCheck.L1SafeBlockPoint` + +**Type:** : `enum (of string)` + +**Default:** `"finalized"` + +**Description:** L1SafeBlockPoint is the point that a block is considered safe enough to be checked +it can be: finalized, safe,pending or latest + +**Example setting the default value** ("finalized"): +``` +[Synchronizer.L1BlockCheck] +L1SafeBlockPoint="finalized" +``` + +Must be one of: +* "finalized" +* "safe" +* "latest" + +#### 9.7.3. `Synchronizer.L1BlockCheck.L1SafeBlockOffset` + +**Type:** : `integer` + +**Default:** `0` + +**Description:** L1SafeBlockOffset is the offset to add to L1SafeBlockPoint as a safe point +it can be positive or negative +Example: L1SafeBlockPoint= finalized, L1SafeBlockOffset= -10, then the safe block ten blocks before the finalized block + +**Example setting the default value** (0): +``` +[Synchronizer.L1BlockCheck] +L1SafeBlockOffset=0 +``` + +#### 9.7.4. `Synchronizer.L1BlockCheck.ForceCheckBeforeStart` + +**Type:** : `boolean` + +**Default:** `true` + +**Description:** ForceCheckBeforeStart if is true then the first time the system is started it will force to check all pending blocks + +**Example setting the default value** (true): +``` +[Synchronizer.L1BlockCheck] +ForceCheckBeforeStart=true +``` + +#### 9.7.5. `Synchronizer.L1BlockCheck.PreCheckEnable` + +**Type:** : `boolean` + +**Default:** `true` + +**Description:** PreCheckEnable if is true then the pre-check is active, will check blocks between L1SafeBlock and L1PreSafeBlock + +**Example setting the default value** (true): +``` +[Synchronizer.L1BlockCheck] +PreCheckEnable=true +``` + +#### 9.7.6. `Synchronizer.L1BlockCheck.L1PreSafeBlockPoint` + +**Type:** : `enum (of string)` + +**Default:** `"safe"` + +**Description:** L1PreSafeBlockPoint is the point that a block is considered safe enough to be checked +it can be: finalized, safe,pending or latest + +**Example setting the default value** ("safe"): +``` +[Synchronizer.L1BlockCheck] +L1PreSafeBlockPoint="safe" +``` + +Must be one of: +* "finalized" +* "safe" +* "latest" + +#### 9.7.7. `Synchronizer.L1BlockCheck.L1PreSafeBlockOffset` + +**Type:** : `integer` + +**Default:** `0` + +**Description:** L1PreSafeBlockOffset is the offset to add to L1PreSafeBlockPoint as a safe point +it can be positive or negative +Example: L1PreSafeBlockPoint= finalized, L1PreSafeBlockOffset= -10, then the safe block ten blocks before the finalized block + +**Example setting the default value** (0): +``` +[Synchronizer.L1BlockCheck] +L1PreSafeBlockOffset=0 +``` + +### 9.8. `Synchronizer.L1SynchronizationMode` **Type:** : `enum (of string)` @@ -1463,7 +1592,7 @@ Must be one of: * "sequential" * "parallel" -### 9.8. `[Synchronizer.L1ParallelSynchronization]` +### 9.9. `[Synchronizer.L1ParallelSynchronization]` **Type:** : `object` **Description:** L1ParallelSynchronization Configuration for parallel mode (if L1SynchronizationMode equal to 'parallel') @@ -1481,7 +1610,7 @@ Must be one of: | - [RollupInfoRetriesSpacing](#Synchronizer_L1ParallelSynchronization_RollupInfoRetriesSpacing ) | No | string | No | - | Duration | | - [FallbackToSequentialModeOnSynchronized](#Synchronizer_L1ParallelSynchronization_FallbackToSequentialModeOnSynchronized ) | No | boolean | No | - | FallbackToSequentialModeOnSynchronized if true switch to sequential mode if the system is synchronized | -#### 9.8.1. `Synchronizer.L1ParallelSynchronization.MaxClients` +#### 9.9.1. `Synchronizer.L1ParallelSynchronization.MaxClients` **Type:** : `integer` @@ -1495,7 +1624,7 @@ Must be one of: MaxClients=10 ``` -#### 9.8.2. `Synchronizer.L1ParallelSynchronization.MaxPendingNoProcessedBlocks` +#### 9.9.2. `Synchronizer.L1ParallelSynchronization.MaxPendingNoProcessedBlocks` **Type:** : `integer` @@ -1510,7 +1639,7 @@ sugested twice of NumberOfParallelOfEthereumClients MaxPendingNoProcessedBlocks=25 ``` -#### 9.8.3. `Synchronizer.L1ParallelSynchronization.RequestLastBlockPeriod` +#### 9.9.3. `Synchronizer.L1ParallelSynchronization.RequestLastBlockPeriod` **Title:** Duration @@ -1538,7 +1667,7 @@ This value only apply when the system is synchronized RequestLastBlockPeriod="5s" ``` -#### 9.8.4. `[Synchronizer.L1ParallelSynchronization.PerformanceWarning]` +#### 9.9.4. `[Synchronizer.L1ParallelSynchronization.PerformanceWarning]` **Type:** : `object` **Description:** Consumer Configuration for the consumer of rollup information from L1 @@ -1548,7 +1677,7 @@ RequestLastBlockPeriod="5s" | - [AceptableInacctivityTime](#Synchronizer_L1ParallelSynchronization_PerformanceWarning_AceptableInacctivityTime ) | No | string | No | - | Duration | | - [ApplyAfterNumRollupReceived](#Synchronizer_L1ParallelSynchronization_PerformanceWarning_ApplyAfterNumRollupReceived ) | No | integer | No | - | ApplyAfterNumRollupReceived is the number of iterations to
start checking the time waiting for new rollup info data | -##### 9.8.4.1. `Synchronizer.L1ParallelSynchronization.PerformanceWarning.AceptableInacctivityTime` +##### 9.9.4.1. `Synchronizer.L1ParallelSynchronization.PerformanceWarning.AceptableInacctivityTime` **Title:** Duration @@ -1577,7 +1706,7 @@ fast enought then you could increse the number of parallel clients to sync with AceptableInacctivityTime="5s" ``` -##### 9.8.4.2. `Synchronizer.L1ParallelSynchronization.PerformanceWarning.ApplyAfterNumRollupReceived` +##### 9.9.4.2. `Synchronizer.L1ParallelSynchronization.PerformanceWarning.ApplyAfterNumRollupReceived` **Type:** : `integer` @@ -1592,7 +1721,7 @@ start checking the time waiting for new rollup info data ApplyAfterNumRollupReceived=10 ``` -#### 9.8.5. `Synchronizer.L1ParallelSynchronization.RequestLastBlockTimeout` +#### 9.9.5. `Synchronizer.L1ParallelSynchronization.RequestLastBlockTimeout` **Title:** Duration @@ -1618,7 +1747,7 @@ ApplyAfterNumRollupReceived=10 RequestLastBlockTimeout="5s" ``` -#### 9.8.6. `Synchronizer.L1ParallelSynchronization.RequestLastBlockMaxRetries` +#### 9.9.6. `Synchronizer.L1ParallelSynchronization.RequestLastBlockMaxRetries` **Type:** : `integer` @@ -1632,7 +1761,7 @@ RequestLastBlockTimeout="5s" RequestLastBlockMaxRetries=3 ``` -#### 9.8.7. `Synchronizer.L1ParallelSynchronization.StatisticsPeriod` +#### 9.9.7. `Synchronizer.L1ParallelSynchronization.StatisticsPeriod` **Title:** Duration @@ -1658,7 +1787,7 @@ RequestLastBlockMaxRetries=3 StatisticsPeriod="5m0s" ``` -#### 9.8.8. `Synchronizer.L1ParallelSynchronization.TimeOutMainLoop` +#### 9.9.8. `Synchronizer.L1ParallelSynchronization.TimeOutMainLoop` **Title:** Duration @@ -1684,7 +1813,7 @@ StatisticsPeriod="5m0s" TimeOutMainLoop="5m0s" ``` -#### 9.8.9. `Synchronizer.L1ParallelSynchronization.RollupInfoRetriesSpacing` +#### 9.9.9. `Synchronizer.L1ParallelSynchronization.RollupInfoRetriesSpacing` **Title:** Duration @@ -1710,7 +1839,7 @@ TimeOutMainLoop="5m0s" RollupInfoRetriesSpacing="5s" ``` -#### 9.8.10. `Synchronizer.L1ParallelSynchronization.FallbackToSequentialModeOnSynchronized` +#### 9.9.10. `Synchronizer.L1ParallelSynchronization.FallbackToSequentialModeOnSynchronized` **Type:** : `boolean` @@ -1724,7 +1853,7 @@ RollupInfoRetriesSpacing="5s" FallbackToSequentialModeOnSynchronized=false ``` -### 9.9. `[Synchronizer.L2Synchronization]` +### 9.10. `[Synchronizer.L2Synchronization]` **Type:** : `object` **Description:** L2Synchronization Configuration for L2 synchronization @@ -1735,7 +1864,7 @@ FallbackToSequentialModeOnSynchronized=false | - [ReprocessFullBatchOnClose](#Synchronizer_L2Synchronization_ReprocessFullBatchOnClose ) | No | boolean | No | - | ReprocessFullBatchOnClose if is true when a batch is closed is force to reprocess again | | - [CheckLastL2BlockHashOnCloseBatch](#Synchronizer_L2Synchronization_CheckLastL2BlockHashOnCloseBatch ) | No | boolean | No | - | CheckLastL2BlockHashOnCloseBatch if is true when a batch is closed is force to check the last L2Block hash | -#### 9.9.1. `Synchronizer.L2Synchronization.AcceptEmptyClosedBatches` +#### 9.10.1. `Synchronizer.L2Synchronization.AcceptEmptyClosedBatches` **Type:** : `boolean` @@ -1750,7 +1879,7 @@ if true, the synchronizer will accept empty batches and process them. AcceptEmptyClosedBatches=false ``` -#### 9.9.2. `Synchronizer.L2Synchronization.ReprocessFullBatchOnClose` +#### 9.10.2. `Synchronizer.L2Synchronization.ReprocessFullBatchOnClose` **Type:** : `boolean` @@ -1764,7 +1893,7 @@ AcceptEmptyClosedBatches=false ReprocessFullBatchOnClose=false ``` -#### 9.9.3. `Synchronizer.L2Synchronization.CheckLastL2BlockHashOnCloseBatch` +#### 9.10.3. `Synchronizer.L2Synchronization.CheckLastL2BlockHashOnCloseBatch` **Type:** : `boolean` diff --git a/docs/config-file/node-config-schema.json b/docs/config-file/node-config-schema.json index 916aaf18b1..517a846e4f 100644 --- a/docs/config-file/node-config-schema.json +++ b/docs/config-file/node-config-schema.json @@ -530,7 +530,58 @@ "L1SyncCheckL2BlockNumberhModulus": { "type": "integer", "description": "L1SyncCheckL2BlockNumberhModulus is the modulus used to choose the l2block to check\na modules 5, for instance, means check all l2block multiples of 5 (10,15,20,...)", - "default": 30 + "default": 600 + }, + "L1BlockCheck": { + "properties": { + "Enable": { + "type": "boolean", + "description": "Enable if is true then the check l1 Block Hash is active", + "default": true + }, + "L1SafeBlockPoint": { + "type": "string", + "enum": [ + "finalized", + "safe", + "latest" + ], + "description": "L1SafeBlockPoint is the point that a block is considered safe enough to be checked\nit can be: finalized, safe,pending or latest", + "default": "finalized" + }, + "L1SafeBlockOffset": { + "type": "integer", + "description": "L1SafeBlockOffset is the offset to add to L1SafeBlockPoint as a safe point\nit can be positive or negative\nExample: L1SafeBlockPoint= finalized, L1SafeBlockOffset= -10, then the safe block ten blocks before the finalized block", + "default": 0 + }, + "ForceCheckBeforeStart": { + "type": "boolean", + "description": "ForceCheckBeforeStart if is true then the first time the system is started it will force to check all pending blocks", + "default": true + }, + "PreCheckEnable": { + "type": "boolean", + "description": "PreCheckEnable if is true then the pre-check is active, will check blocks between L1SafeBlock and L1PreSafeBlock", + "default": true + }, + "L1PreSafeBlockPoint": { + "type": "string", + "enum": [ + "finalized", + "safe", + "latest" + ], + "description": "L1PreSafeBlockPoint is the point that a block is considered safe enough to be checked\nit can be: finalized, safe,pending or latest", + "default": "safe" + }, + "L1PreSafeBlockOffset": { + "type": "integer", + "description": "L1PreSafeBlockOffset is the offset to add to L1PreSafeBlockPoint as a safe point\nit can be positive or negative\nExample: L1PreSafeBlockPoint= finalized, L1PreSafeBlockOffset= -10, then the safe block ten blocks before the finalized block", + "default": 0 + } + }, + "additionalProperties": false, + "type": "object" }, "L1SynchronizationMode": { "type": "string", diff --git a/docs/running_local.md b/docs/running_local.md index 1fd926bea8..49a4eb1a98 100644 --- a/docs/running_local.md +++ b/docs/running_local.md @@ -192,7 +192,7 @@ To configure your Metamask to use your local environment, follow these steps: | Address | Description | |---|---| | 0x8dAF17A20c9DBA35f005b6324F493785D239719d | Polygon ZKEVM | -| 0x40E0576c0A7dff9dc460B29ba73e79aBf73dD2a9 | Polygon Bridge | +| 0xFe12ABaa190Ef0c8638Ee0ba9F828BF41368Ca0E | Polygon Bridge | | 0x5FbDB2315678afecb367f032d93F642f64180aa3 | Pol token | | 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318 | Polygon GlobalExitRootManager | | 0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e | Polygon RollupManager | diff --git a/etherman/etherman.go b/etherman/etherman.go index 75c50e1ddc..0d48318f3d 100644 --- a/etherman/etherman.go +++ b/etherman/etherman.go @@ -282,7 +282,7 @@ func NewClient(cfg Config, l1Config L1Config, da dataavailability.BatchDataProvi rollupID, err := rollupManager.RollupAddressToID(&bind.CallOpts{Pending: false}, l1Config.ZkEVMAddr) if err != nil { log.Debugf("error rollupManager.RollupAddressToID(%s). Error: %w", l1Config.RollupManagerAddr, err) - // TODO return error after the upgrade + return nil, err } log.Debug("rollupID: ", rollupID) @@ -1635,7 +1635,7 @@ func (etherMan *Client) forceSequencedBatchesEvent(ctx context.Context, vLog typ if err != nil { return err } - // TODO completar los datos de forcedBlockHas, forcedGer y forcedTimestamp + // TODO complete data forcedBlockHash, forcedGer y forcedTimestamp // Read the tx for this batch. tx, err := etherMan.EthClient.TransactionInBlock(ctx, vLog.BlockHash, vLog.TxIndex) @@ -1940,6 +1940,17 @@ func (etherMan *Client) EstimateGas(ctx context.Context, from common.Address, to }) } +// DepositCount returns deposits count +func (etherman *Client) DepositCount(ctx context.Context, blockNumber *uint64) (*big.Int, error) { + var opts *bind.CallOpts + if blockNumber != nil { + opts = new(bind.CallOpts) + opts.BlockNumber = new(big.Int).SetUint64(*blockNumber) + } + + return etherman.GlobalExitRootManager.DepositCount(opts) +} + // CheckTxWasMined check if a tx was already mined func (etherMan *Client) CheckTxWasMined(ctx context.Context, txHash common.Hash) (bool, *types.Receipt, error) { receipt, err := etherMan.EthClient.TransactionReceipt(ctx, txHash) diff --git a/go.mod b/go.mod index e3b99a3a5f..aae887f746 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/0xPolygonHermez/zkevm-node go 1.21 require ( - github.com/0xPolygonHermez/zkevm-data-streamer v0.1.18 + github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-0.20240422135400-0df0d27226b3 github.com/didip/tollbooth/v6 v6.1.2 github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 github.com/ethereum/go-ethereum v1.13.11 @@ -175,7 +175,6 @@ require ( github.com/0xPolygon/agglayer v0.0.1 github.com/0xPolygon/cdk-data-availability v0.0.5 github.com/fatih/color v1.16.0 - github.com/joho/godotenv v1.5.1 github.com/prometheus/client_golang v1.18.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa ) diff --git a/go.sum b/go.sum index aa214a13e8..28077173da 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/0xPolygon/agglayer v0.0.1 h1:J6/DUo9rNUncDifquanouRCo2g7g069yvz0aFtu7 github.com/0xPolygon/agglayer v0.0.1/go.mod h1:UYp5O8THULoXVrUfzkRjVBzxHR5DxBdUN/Iq0EgxNxM= github.com/0xPolygon/cdk-data-availability v0.0.5 h1://vg1oR/5tw2XfEIorpP+wIxLfNUmoKrdmX8YZvBKX4= github.com/0xPolygon/cdk-data-availability v0.0.5/go.mod h1:aGwqHiJhL+mJbdepl3s58wsY18EuViDa9vZCpPuIYGw= -github.com/0xPolygonHermez/zkevm-data-streamer v0.1.18 h1:InqeTcHrNbfj1OUfn2aFplFay7ibd7KhYqvmMZYZfn0= -github.com/0xPolygonHermez/zkevm-data-streamer v0.1.18/go.mod h1:0QkAXcFa92mFJrCbN3UPUJGJYes851yEgYHLONnaosE= +github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-0.20240422135400-0df0d27226b3 h1:g5IMJalQxVRNfnXrzQG7bx2COktaFBf1mNuF4SLuQss= +github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-0.20240422135400-0df0d27226b3/go.mod h1:0QkAXcFa92mFJrCbN3UPUJGJYes851yEgYHLONnaosE= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -492,8 +492,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= diff --git a/jsonrpc/endpoints_eth.go b/jsonrpc/endpoints_eth.go index aadb844b22..2a3ba32b86 100644 --- a/jsonrpc/endpoints_eth.go +++ b/jsonrpc/endpoints_eth.go @@ -68,8 +68,6 @@ func (e *EthEndpoints) Call(arg *types.TxArgs, blockArg *types.BlockNumberOrHash return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) { if arg == nil { return RPCErrorResponse(types.InvalidParamsErrorCode, "missing value for required argument 0", nil, false) - } else if blockArg == nil { - return RPCErrorResponse(types.InvalidParamsErrorCode, "missing value for required argument 1", nil, false) } block, respErr := e.getBlockByArg(ctx, blockArg, dbTx) if respErr != nil { @@ -113,6 +111,9 @@ func (e *EthEndpoints) Call(arg *types.TxArgs, blockArg *types.BlockNumberOrHash if result.Reverted() { data := make([]byte, len(result.ReturnValue)) copy(data, result.ReturnValue) + if len(data) == 0 { + return nil, types.NewRPCError(types.DefaultErrorCode, result.Err.Error()) + } return nil, types.NewRPCErrorWithData(types.RevertedErrorCode, result.Err.Error(), data) } else if result.Failed() { return nil, types.NewRPCError(types.DefaultErrorCode, result.Err.Error()) @@ -191,6 +192,9 @@ func (e *EthEndpoints) EstimateGas(arg *types.TxArgs, blockArg *types.BlockNumbe if errors.Is(err, runtime.ErrExecutionReverted) { data := make([]byte, len(returnValue)) copy(data, returnValue) + if len(data) == 0 { + return nil, types.NewRPCError(types.DefaultErrorCode, err.Error()) + } return nil, types.NewRPCErrorWithData(types.RevertedErrorCode, err.Error(), data) } else if err != nil { return nil, types.NewRPCError(types.DefaultErrorCode, err.Error()) diff --git a/jsonrpc/endpoints_eth_test.go b/jsonrpc/endpoints_eth_test.go index ec50c6ea86..5da1d6a380 100644 --- a/jsonrpc/endpoints_eth_test.go +++ b/jsonrpc/endpoints_eth_test.go @@ -504,7 +504,7 @@ func TestCall(t *testing.T) { latest, }, expectedResult: nil, - expectedError: types.NewRPCError(types.RevertedErrorCode, "execution reverted"), + expectedError: types.NewRPCError(types.DefaultErrorCode, "execution reverted"), setupMocks: func(c Config, m *mocksWrapper, testCase *testCase) { nonce := uint64(7) m.DbTx.On("Rollback", context.Background()).Return(nil).Once() diff --git a/sequencer/addrqueue.go b/sequencer/addrqueue.go index 177521c449..9c0d8d996e 100644 --- a/sequencer/addrqueue.go +++ b/sequencer/addrqueue.go @@ -211,6 +211,10 @@ func (a *addrQueue) updateCurrentNonceBalance(nonce *uint64, balance *big.Int) ( if oldReadyTx != nil && oldReadyTx.Nonce > a.currentNonce { log.Infof("set readyTx %s as notReadyTx from addrQueue %s", oldReadyTx.HashStr, a.fromStr) a.notReadyTxs[oldReadyTx.Nonce] = oldReadyTx + } else if oldReadyTx != nil { // if oldReadyTx doesn't have a valid nonce then we add it to the txsToDelete + reason := runtime.ErrIntrinsicInvalidNonce.Error() + oldReadyTx.FailedReason = &reason + txsToDelete = append(txsToDelete, oldReadyTx) } return a.readyTx, oldReadyTx, txsToDelete diff --git a/sequencer/datastreamer.go b/sequencer/datastreamer.go index 700b8b3e02..bbbfe14496 100644 --- a/sequencer/datastreamer.go +++ b/sequencer/datastreamer.go @@ -1,6 +1,7 @@ package sequencer import ( + "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/state" ) @@ -42,6 +43,7 @@ func (f *finalizer) DSSendL2Block(batchNumber uint64, blockResponse *state.Proce l2Transactions = append(l2Transactions, l2Transaction) } + log.Infof("sending l2block %d to datastream channel", blockResponse.BlockNumber) f.dataToStream <- state.DSL2FullBlock{ DSL2Block: l2Block, Txs: l2Transactions, diff --git a/sequencer/finalizer.go b/sequencer/finalizer.go index 46a0d7cb53..48ebf80d6f 100644 --- a/sequencer/finalizer.go +++ b/sequencer/finalizer.go @@ -10,6 +10,7 @@ import ( "time" "github.com/0xPolygonHermez/zkevm-data-streamer/datastreamer" + ethermanTypes "github.com/0xPolygonHermez/zkevm-node/etherman" "github.com/0xPolygonHermez/zkevm-node/event" "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/log" @@ -38,7 +39,7 @@ type finalizer struct { workerIntf workerInterface poolIntf txPool stateIntf stateInterface - etherman etherman + etherman ethermanInterface wipBatch *Batch wipL2Block *L2Block batchConstraints state.BatchConstraintsCfg @@ -87,7 +88,7 @@ func newFinalizer( workerIntf workerInterface, poolIntf txPool, stateIntf stateInterface, - etherman etherman, + etherman ethermanInterface, sequencerAddr common.Address, isSynced func(ctx context.Context) bool, batchConstraints state.BatchConstraintsCfg, @@ -220,18 +221,95 @@ func (f *finalizer) updateFlushIDs(newPendingFlushID, newStoredFlushID uint64) { f.storedFlushIDCond.L.Unlock() } -func (f *finalizer) checkL1InfoTreeUpdate(ctx context.Context) { - firstL1InfoRootUpdate := true - skipFirstSleep := true +func (f *finalizer) checkValidL1InfoRoot(ctx context.Context, l1InfoRoot state.L1InfoTreeExitRootStorageEntry) (bool, error) { + // Check L1 block hash matches + l1BlockState, err := f.stateIntf.GetBlockByNumber(ctx, l1InfoRoot.BlockNumber, nil) + if err != nil { + return false, fmt.Errorf("error getting L1 block %d from the state, error: %v", l1InfoRoot.BlockNumber, err) + } + + l1BlockEth, err := f.etherman.HeaderByNumber(ctx, new(big.Int).SetUint64(l1InfoRoot.BlockNumber)) + if err != nil { + return false, fmt.Errorf("error getting L1 block %d from ethereum, error: %v", l1InfoRoot.BlockNumber, err) + } + + if l1BlockState.BlockHash != l1BlockEth.Hash() { + warnmsg := fmt.Sprintf("invalid l1InfoRoot %s, index: %d, GER: %s, l1Block: %d. L1 block hash %s doesn't match block hash on ethereum %s (L1 reorg?)", + l1InfoRoot.L1InfoTreeRoot, l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.GlobalExitRoot.GlobalExitRoot, l1InfoRoot.BlockNumber, l1BlockState.BlockHash, l1BlockEth.Hash()) + log.Warnf(warnmsg) + f.LogEvent(ctx, event.Level_Critical, event.EventID_InvalidInfoRoot, warnmsg, nil) + + return false, nil + } + + // Check l1InfoRootIndex and GER matches. We retrieve the info of the last l1InfoTree event in the block, since in the case we have several l1InfoTree events + // in the same block, the function checkL1InfoTreeUpdate retrieves only the last one and skips the others + log.Debugf("getting l1InfoRoot events for L1 block %d, hash: %s", l1InfoRoot.BlockNumber, l1BlockState.BlockHash) + blocks, eventsOrder, err := f.etherman.GetRollupInfoByBlockRange(ctx, l1InfoRoot.BlockNumber, &l1InfoRoot.BlockNumber) + if err != nil { + return false, err + } + + //Get L1InfoTree events of the L1 block where the l1InforRoot we need to check was synced + lastGER := state.ZeroHash + for _, block := range blocks { + blockEventsOrder := eventsOrder[block.BlockHash] + for _, order := range blockEventsOrder { + if order.Name == ethermanTypes.L1InfoTreeOrder { + lastGER = block.L1InfoTree[order.Pos].GlobalExitRoot + log.Debugf("l1InfoTree event, pos: %d, GER: %s", order.Pos, lastGER) + } + } + } - if f.cfg.L1InfoTreeCheckInterval.Duration.Seconds() == 999999 { //nolint:gomnd + // Get the deposit count in the moment when the L1InfoRoot was synced + depositCount, err := f.etherman.DepositCount(ctx, &l1InfoRoot.BlockNumber) + if err != nil { + return false, err + } + // l1InfoTree index starts at 0, therefore we need to subtract 1 to the depositCount to get the last index at that moment + index := uint32(depositCount.Uint64()) + if index > 0 { // we check this as protection, but depositCount should be greater that 0 in this context + index-- + } else { + warnmsg := fmt.Sprintf("invalid l1InfoRoot %s, index: %d, GER: %s, blockNum: %d. DepositCount value returned by the smartcontrat is 0 and that isn't possible in this context", + l1InfoRoot.L1InfoTreeRoot, l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.GlobalExitRoot.GlobalExitRoot, l1InfoRoot.BlockNumber) + log.Warn(warnmsg) + f.LogEvent(ctx, event.Level_Critical, event.EventID_InvalidInfoRoot, warnmsg, nil) + + return false, nil + } + + log.Debugf("checking valid l1InfoRoot, index: %d, GER: %s, l1Block: %d, scIndex: %d, scGER: %s", + l1InfoRoot.BlockNumber, l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.GlobalExitRoot.GlobalExitRoot, index, lastGER) + + if (l1InfoRoot.GlobalExitRoot.GlobalExitRoot != lastGER) || (l1InfoRoot.L1InfoTreeIndex != index) { + warnmsg := fmt.Sprintf("invalid l1InfoRoot %s, index: %d, GER: %s, blockNum: %d. It doesn't match with smartcontract l1InfoRoot, index: %d, GER: %s", + l1InfoRoot.L1InfoTreeRoot, l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.GlobalExitRoot.GlobalExitRoot, l1InfoRoot.BlockNumber, index, lastGER) + log.Warn(warnmsg) + f.LogEvent(ctx, event.Level_Critical, event.EventID_InvalidInfoRoot, warnmsg, nil) + + return false, nil + } + + return true, nil +} + +func (f *finalizer) checkL1InfoTreeUpdate(ctx context.Context) { + broadcastL1InfoTreeValid := func() { if !f.lastL1InfoTreeValid { f.lastL1InfoTreeCond.L.Lock() f.lastL1InfoTreeValid = true f.lastL1InfoTreeCond.Broadcast() f.lastL1InfoTreeCond.L.Unlock() } + } + + firstL1InfoRootUpdate := true + skipFirstSleep := true + if f.cfg.L1InfoTreeCheckInterval.Duration.Seconds() == 0 { //nolint:gomnd + broadcastL1InfoTreeValid() return } @@ -255,7 +333,7 @@ func (f *finalizer) checkL1InfoTreeUpdate(ctx context.Context) { l1InfoRoot, err := f.stateIntf.GetLatestL1InfoRoot(ctx, maxBlockNumber) if err != nil { - log.Errorf("error checking latest L1InfoRoot, error: %v", err) + log.Errorf("error getting latest l1InfoRoot, error: %v", err) continue } @@ -265,27 +343,18 @@ func (f *finalizer) checkL1InfoTreeUpdate(ctx context.Context) { } if firstL1InfoRootUpdate || l1InfoRoot.L1InfoTreeIndex > f.lastL1InfoTree.L1InfoTreeIndex { - log.Infof("received new L1InfoRoot, l1InfoTreeIndex: %d, l1InfoTreeRoot: %s, l1Block: %d", - l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.L1InfoTreeRoot, l1InfoRoot.BlockNumber) + log.Infof("received new l1InfoRoot %s, index: %d, l1Block: %d", l1InfoRoot.L1InfoTreeRoot, l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.BlockNumber) - // Sanity check l1BlockState (l1InfoRoot.BlockNumber) blockhash matches blockhash on ethereum. We skip it if l1InfoRoot.BlockNumber == 0 (empty tree) - if l1InfoRoot.BlockNumber > 0 { - l1BlockState, err := f.stateIntf.GetBlockByNumber(ctx, l1InfoRoot.BlockNumber, nil) + // Check if new l1InfoRoot is valid. We skip it if l1InfoTreeIndex is 0 (it's a special case) + if l1InfoRoot.L1InfoTreeIndex > 0 { + valid, err := f.checkValidL1InfoRoot(ctx, l1InfoRoot) if err != nil { - log.Errorf("error getting L1 block %d from the state, error: %v", l1InfoRoot.BlockNumber, err) + log.Errorf("error validating new l1InfoRoot, index: %d, error: %v", l1InfoRoot.L1InfoTreeIndex, err) continue } - l1BlockEth, err := f.etherman.HeaderByNumber(ctx, new(big.Int).SetUint64(l1InfoRoot.BlockNumber)) - if err != nil { - log.Errorf("error getting L1 block %d from ethereum, error: %v", l1InfoRoot.BlockNumber, err) - continue - } - if l1BlockState.BlockHash != l1BlockEth.Hash() { - warnmsg := fmt.Sprintf("invalid l1InfoTreeIndex %d, L1 block %d blockhash %s doesn't match blockhash on ethereum %s (L1 reorg?). Stopping syncing l1IntroTreeIndex", - l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.BlockNumber, l1BlockState.BlockHash, l1BlockEth.Hash()) - log.Warn(warnmsg) - f.LogEvent(ctx, event.Level_Critical, event.EventID_InvalidInfoRoot, warnmsg, nil) + if !valid { + log.Warnf("invalid l1InfoRoot %s, index: %d, l1Block: %d. Stopping syncing l1InfoTreeIndex", l1InfoRoot.L1InfoTreeRoot, l1InfoRoot.L1InfoTreeIndex, l1InfoRoot.BlockNumber) return } } @@ -296,12 +365,7 @@ func (f *finalizer) checkL1InfoTreeUpdate(ctx context.Context) { f.lastL1InfoTree = l1InfoRoot f.lastL1InfoTreeMux.Unlock() - if !f.lastL1InfoTreeValid { - f.lastL1InfoTreeCond.L.Lock() - f.lastL1InfoTreeValid = true - f.lastL1InfoTreeCond.Broadcast() - f.lastL1InfoTreeCond.L.Unlock() - } + broadcastL1InfoTreeValid() } } } diff --git a/sequencer/interfaces.go b/sequencer/interfaces.go index afe49bceb9..10c58980ac 100644 --- a/sequencer/interfaces.go +++ b/sequencer/interfaces.go @@ -5,6 +5,7 @@ import ( "math/big" "time" + ethermanTypes "github.com/0xPolygonHermez/zkevm-node/etherman" "github.com/0xPolygonHermez/zkevm-node/pool" "github.com/0xPolygonHermez/zkevm-node/state" "github.com/ethereum/go-ethereum/common" @@ -30,12 +31,14 @@ type txPool interface { GetEarliestProcessedTx(ctx context.Context) (common.Hash, error) } -// etherman contains the methods required to interact with ethereum. -type etherman interface { +// ethermanInterface contains the methods required to interact with ethereum. +type ethermanInterface interface { TrustedSequencer() (common.Address, error) GetLatestBatchNumber() (uint64, error) GetLatestBlockNumber(ctx context.Context) (uint64, error) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + GetRollupInfoByBlockRange(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]ethermanTypes.Block, map[common.Hash][]ethermanTypes.Order, error) + DepositCount(ctx context.Context, blockNumber *uint64) (*big.Int, error) } // stateInterface gathers the methods required to interact with the state. diff --git a/sequencer/l2block.go b/sequencer/l2block.go index c674bf4a18..3b5f10ca02 100644 --- a/sequencer/l2block.go +++ b/sequencer/l2block.go @@ -413,6 +413,9 @@ func (f *finalizer) storeL2Block(ctx context.Context, l2Block *L2Block) error { return err } + //TODO: remove this log + log.Infof("l2 block %d [%d] stored in statedb", blockResponse.BlockNumber, l2Block.trackingNum) + // Update txs status in the pool for _, txResponse := range blockResponse.TransactionResponses { // Change Tx status to selected @@ -422,6 +425,9 @@ func (f *finalizer) storeL2Block(ctx context.Context, l2Block *L2Block) error { } } + //TODO: remove this log + log.Infof("l2 block %d [%d] transactions updated as selected in the pooldb", blockResponse.BlockNumber, l2Block.trackingNum) + // Send L2 block to data streamer err = f.DSSendL2Block(f.wipBatch.batchNumber, blockResponse, l2Block.getL1InfoTreeIndex()) if err != nil { @@ -429,6 +435,9 @@ func (f *finalizer) storeL2Block(ctx context.Context, l2Block *L2Block) error { log.Errorf("error sending L2 block %d [%d] to data streamer, error: %v", blockResponse.BlockNumber, l2Block.trackingNum, err) } + //TODO: remove this log + log.Infof("l2 block %d [%d] sent to datastream", blockResponse.BlockNumber, l2Block.trackingNum) + for _, tx := range l2Block.transactions { // Delete the tx from the pending list in the worker (addrQueue) f.workerIntf.DeletePendingTxToStore(tx.Hash, tx.From) diff --git a/sequencer/mock_etherman.go b/sequencer/mock_etherman.go index 3169ca6f3f..f51eb11d09 100644 --- a/sequencer/mock_etherman.go +++ b/sequencer/mock_etherman.go @@ -8,16 +8,48 @@ import ( common "github.com/ethereum/go-ethereum/common" + etherman "github.com/0xPolygonHermez/zkevm-node/etherman" + mock "github.com/stretchr/testify/mock" types "github.com/ethereum/go-ethereum/core/types" ) -// EthermanMock is an autogenerated mock type for the etherman type +// EthermanMock is an autogenerated mock type for the ethermanInterface type type EthermanMock struct { mock.Mock } +// DepositCount provides a mock function with given fields: ctx, blockNumber +func (_m *EthermanMock) DepositCount(ctx context.Context, blockNumber *uint64) (*big.Int, error) { + ret := _m.Called(ctx, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for DepositCount") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *uint64) (*big.Int, error)); ok { + return rf(ctx, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, *uint64) *big.Int); ok { + r0 = rf(ctx, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *uint64) error); ok { + r1 = rf(ctx, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetLatestBatchNumber provides a mock function with given fields: func (_m *EthermanMock) GetLatestBatchNumber() (uint64, error) { ret := _m.Called() @@ -74,6 +106,45 @@ func (_m *EthermanMock) GetLatestBlockNumber(ctx context.Context) (uint64, error return r0, r1 } +// GetRollupInfoByBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *EthermanMock) GetRollupInfoByBlockRange(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetRollupInfoByBlockRange") + } + + var r0 []etherman.Block + var r1 map[common.Hash][]etherman.Order + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64) []etherman.Block); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]etherman.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, *uint64) map[common.Hash][]etherman.Order); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(map[common.Hash][]etherman.Order) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64, *uint64) error); ok { + r2 = rf(ctx, fromBlock, toBlock) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // HeaderByNumber provides a mock function with given fields: ctx, number func (_m *EthermanMock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { ret := _m.Called(ctx, number) diff --git a/sequencer/sequencer.go b/sequencer/sequencer.go index e98c367fc1..22201776ce 100644 --- a/sequencer/sequencer.go +++ b/sequencer/sequencer.go @@ -27,7 +27,7 @@ type Sequencer struct { pool txPool stateIntf stateInterface eventLog *event.EventLog - etherman etherman + etherman ethermanInterface worker *Worker finalizer *finalizer @@ -42,7 +42,7 @@ type Sequencer struct { } // New init sequencer -func New(cfg Config, batchCfg state.BatchConfig, poolCfg pool.Config, txPool txPool, stateIntf stateInterface, etherman etherman, eventLog *event.EventLog) (*Sequencer, error) { +func New(cfg Config, batchCfg state.BatchConfig, poolCfg pool.Config, txPool txPool, stateIntf stateInterface, etherman ethermanInterface, eventLog *event.EventLog) (*Sequencer, error) { addr, err := etherman.TrustedSequencer() if err != nil { return nil, fmt.Errorf("failed to get trusted sequencer address, error: %v", err) @@ -256,6 +256,8 @@ func (s *Sequencer) sendDataToStreamer(chainID uint64) { case state.DSL2FullBlock: l2Block := data + //TODO: remove this log + log.Infof("start atomic op for l2block %d", l2Block.L2BlockNumber) err = s.streamServer.StartAtomicOp() if err != nil { log.Errorf("failed to start atomic op for l2block %d, error: %v ", l2Block.L2BlockNumber, err) @@ -267,6 +269,8 @@ func (s *Sequencer) sendDataToStreamer(chainID uint64) { Value: l2Block.L2BlockNumber, } + //TODO: remove this log + log.Infof("add stream bookmark for l2block %d", l2Block.L2BlockNumber) _, err = s.streamServer.AddStreamBookmark(bookMark.Encode()) if err != nil { log.Errorf("failed to add stream bookmark for l2block %d, error: %v", l2Block.L2BlockNumber, err) @@ -281,6 +285,8 @@ func (s *Sequencer) sendDataToStreamer(chainID uint64) { Value: l2Block.L2BlockNumber - 1, } + //TODO: remove this log + log.Infof("get previous l2block %d", l2Block.L2BlockNumber-1) previousL2BlockEntry, err := s.streamServer.GetFirstEventAfterBookmark(bookMark.Encode()) if err != nil { log.Errorf("failed to get previous l2block %d, error: %v", l2Block.L2BlockNumber-1, err) @@ -303,12 +309,16 @@ func (s *Sequencer) sendDataToStreamer(chainID uint64) { ChainID: uint32(chainID), } + //TODO: remove this log + log.Infof("add l2blockStart stream entry for l2block %d", l2Block.L2BlockNumber) _, err = s.streamServer.AddStreamEntry(state.EntryTypeL2BlockStart, blockStart.Encode()) if err != nil { log.Errorf("failed to add stream entry for l2block %d, error: %v", l2Block.L2BlockNumber, err) continue } + //TODO: remove this log + log.Infof("adding l2tx stream entries for l2block %d", l2Block.L2BlockNumber) for _, l2Transaction := range l2Block.Txs { _, err = s.streamServer.AddStreamEntry(state.EntryTypeL2Tx, l2Transaction.Encode()) if err != nil { @@ -323,18 +333,25 @@ func (s *Sequencer) sendDataToStreamer(chainID uint64) { StateRoot: l2Block.StateRoot, } + //TODO: remove this log + log.Infof("add l2blockEnd stream entry for l2block %d", l2Block.L2BlockNumber) _, err = s.streamServer.AddStreamEntry(state.EntryTypeL2BlockEnd, blockEnd.Encode()) if err != nil { log.Errorf("failed to add stream entry for l2block %d, error: %v", l2Block.L2BlockNumber, err) continue } + //TODO: remove this log + log.Infof("commit atomic op for l2block %d", l2Block.L2BlockNumber) err = s.streamServer.CommitAtomicOp() if err != nil { log.Errorf("failed to commit atomic op for l2block %d, error: %v ", l2Block.L2BlockNumber, err) continue } + //TODO: remove this log + log.Infof("l2block %d sent to datastream", l2Block.L2BlockNumber) + // Stream a bookmark case state.DSBookMark: bookmark := data diff --git a/sequencer/worker.go b/sequencer/worker.go index a99f956fed..0d0b378872 100644 --- a/sequencer/worker.go +++ b/sequencer/worker.go @@ -23,6 +23,7 @@ type Worker struct { state stateInterface batchConstraints state.BatchConstraintsCfg readyTxsCond *timeoutCond + wipTx *TxTracker } // NewWorker creates an init a worker @@ -60,6 +61,12 @@ func (w *Worker) AddTxTracker(ctx context.Context, tx *TxTracker) (replacedTx *T return nil, pool.ErrOutOfCounters } + if (w.wipTx != nil) && (w.wipTx.FromStr == tx.FromStr) && (w.wipTx.Nonce == tx.Nonce) { + log.Infof("adding tx %s (nonce %d) from address %s that matches current processing tx %s (nonce %d), rejecting it as duplicated nonce", tx.Hash, tx.Nonce, tx.From, w.wipTx.Hash, w.wipTx.Nonce) + w.workerMutex.Unlock() + return nil, ErrDuplicatedNonce + } + addr, found := w.pool[tx.FromStr] if !found { // Unlock the worker to let execute other worker functions while creating the new AddrQueue @@ -174,6 +181,8 @@ func (w *Worker) MoveTxToNotReady(txHash common.Hash, from common.Address, actua defer w.workerMutex.Unlock() log.Debugf("move tx %s to notReady (from: %s, actualNonce: %d, actualBalance: %s)", txHash.String(), from.String(), actualNonce, actualBalance.String()) + w.resetWipTx(txHash) + addrQueue, found := w.pool[from.String()] if found { // Sanity check. The txHash must be the readyTx @@ -195,6 +204,8 @@ func (w *Worker) DeleteTx(txHash common.Hash, addr common.Address) { w.workerMutex.Lock() defer w.workerMutex.Unlock() + w.resetWipTx(txHash) + addrQueue, found := w.pool[addr.String()] if found { deletedReadyTx := addrQueue.deleteTx(txHash) @@ -293,6 +304,8 @@ func (w *Worker) GetBestFittingTx(resources state.BatchResources) (*TxTracker, e w.workerMutex.Lock() defer w.workerMutex.Unlock() + w.wipTx = nil + if w.txSortedList.len() == 0 { return nil, ErrTransactionsListEmpty } @@ -342,6 +355,7 @@ func (w *Worker) GetBestFittingTx(resources state.BatchResources) (*TxTracker, e if foundAt != -1 { log.Debugf("best fitting tx %s found at index %d with gasPrice %d", tx.HashStr, foundAt, tx.GasPrice) + w.wipTx = tx return tx, nil } else { return nil, ErrNoFittingTransaction @@ -382,3 +396,9 @@ func (w *Worker) addTxToSortedList(readyTx *TxTracker) { w.readyTxsCond.L.Unlock() } } + +func (w *Worker) resetWipTx(txHash common.Hash) { + if (w.wipTx != nil) && (w.wipTx.Hash == txHash) { + w.wipTx = nil + } +} diff --git a/state/interfaces.go b/state/interfaces.go index 1524553240..cc3c96d8fd 100644 --- a/state/interfaces.go +++ b/state/interfaces.go @@ -25,6 +25,7 @@ type storage interface { GetLastBlock(ctx context.Context, dbTx pgx.Tx) (*Block, error) GetPreviousBlock(ctx context.Context, offset uint64, dbTx pgx.Tx) (*Block, error) GetFirstUncheckedBlock(ctx context.Context, fromBlockNumber uint64, dbTx pgx.Tx) (*Block, error) + GetUncheckedBlocks(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx) ([]*Block, error) UpdateCheckedBlockByNumber(ctx context.Context, blockNumber uint64, newCheckedStatus bool, dbTx pgx.Tx) error AddGlobalExitRoot(ctx context.Context, exitRoot *GlobalExitRoot, dbTx pgx.Tx) error GetLatestGlobalExitRoot(ctx context.Context, maxBlockNumber uint64, dbTx pgx.Tx) (GlobalExitRoot, time.Time, error) @@ -164,4 +165,5 @@ type storage interface { UpdateBatchAsChecked(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) error GetNotCheckedBatches(ctx context.Context, dbTx pgx.Tx) ([]*Batch, error) GetLastL2BlockByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*L2Block, error) + GetPreviousBlockToBlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*Block, error) } diff --git a/state/l1infotree.go b/state/l1infotree.go index 8cac9ea5d7..8c081f880c 100644 --- a/state/l1infotree.go +++ b/state/l1infotree.go @@ -3,6 +3,7 @@ package state import ( "context" "errors" + "fmt" "github.com/0xPolygonHermez/zkevm-node/l1infotree" "github.com/0xPolygonHermez/zkevm-node/log" @@ -54,6 +55,14 @@ func (s *State) buildL1InfoTreeCacheIfNeed(ctx context.Context, dbTx pgx.Tx) err // AddL1InfoTreeLeaf adds a new leaf to the L1InfoTree and returns the entry and error func (s *State) AddL1InfoTreeLeaf(ctx context.Context, l1InfoTreeLeaf *L1InfoTreeLeaf, dbTx pgx.Tx) (*L1InfoTreeExitRootStorageEntry, error) { + var stateTx *StateTx + if dbTx != nil { + var ok bool + stateTx, ok = dbTx.(*StateTx) + if !ok { + return nil, fmt.Errorf("error casting dbTx to stateTx") + } + } var newIndex uint32 gerIndex, err := s.GetLatestIndex(ctx, dbTx) if err != nil && !errors.Is(err, ErrNotFound) { @@ -73,6 +82,9 @@ func (s *State) AddL1InfoTreeLeaf(ctx context.Context, l1InfoTreeLeaf *L1InfoTre log.Error("error add new leaf to the L1InfoTree. Error: ", err) return nil, err } + if stateTx != nil { + stateTx.SetL1InfoTreeModified() + } entry := L1InfoTreeExitRootStorageEntry{ L1InfoTreeLeaf: *l1InfoTreeLeaf, L1InfoTreeRoot: root, diff --git a/state/mocks/mock_storage.go b/state/mocks/mock_storage.go index 24b5f7abef..696294d465 100644 --- a/state/mocks/mock_storage.go +++ b/state/mocks/mock_storage.go @@ -5729,6 +5729,66 @@ func (_c *StorageMock_GetPreviousBlock_Call) RunAndReturn(run func(context.Conte return _c } +// GetPreviousBlockToBlockNumber provides a mock function with given fields: ctx, blockNumber, dbTx +func (_m *StorageMock) GetPreviousBlockToBlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) { + ret := _m.Called(ctx, blockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetPreviousBlockToBlockNumber") + } + + var r0 *state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (*state.Block, error)); ok { + return rf(ctx, blockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.Block); ok { + r0 = rf(ctx, blockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, blockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StorageMock_GetPreviousBlockToBlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviousBlockToBlockNumber' +type StorageMock_GetPreviousBlockToBlockNumber_Call struct { + *mock.Call +} + +// GetPreviousBlockToBlockNumber is a helper method to define mock.On call +// - ctx context.Context +// - blockNumber uint64 +// - dbTx pgx.Tx +func (_e *StorageMock_Expecter) GetPreviousBlockToBlockNumber(ctx interface{}, blockNumber interface{}, dbTx interface{}) *StorageMock_GetPreviousBlockToBlockNumber_Call { + return &StorageMock_GetPreviousBlockToBlockNumber_Call{Call: _e.mock.On("GetPreviousBlockToBlockNumber", ctx, blockNumber, dbTx)} +} + +func (_c *StorageMock_GetPreviousBlockToBlockNumber_Call) Run(run func(ctx context.Context, blockNumber uint64, dbTx pgx.Tx)) *StorageMock_GetPreviousBlockToBlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(pgx.Tx)) + }) + return _c +} + +func (_c *StorageMock_GetPreviousBlockToBlockNumber_Call) Return(_a0 *state.Block, _a1 error) *StorageMock_GetPreviousBlockToBlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StorageMock_GetPreviousBlockToBlockNumber_Call) RunAndReturn(run func(context.Context, uint64, pgx.Tx) (*state.Block, error)) *StorageMock_GetPreviousBlockToBlockNumber_Call { + _c.Call.Return(run) + return _c +} + // GetProcessingContext provides a mock function with given fields: ctx, batchNumber, dbTx func (_m *StorageMock) GetProcessingContext(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.ProcessingContext, error) { ret := _m.Called(ctx, batchNumber, dbTx) @@ -7012,6 +7072,67 @@ func (_c *StorageMock_GetTxsOlderThanNL1BlocksUntilTxHash_Call) RunAndReturn(run return _c } +// GetUncheckedBlocks provides a mock function with given fields: ctx, fromBlockNumber, toBlockNumber, dbTx +func (_m *StorageMock) GetUncheckedBlocks(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx) ([]*state.Block, error) { + ret := _m.Called(ctx, fromBlockNumber, toBlockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetUncheckedBlocks") + } + + var r0 []*state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, pgx.Tx) ([]*state.Block, error)); ok { + return rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, pgx.Tx) []*state.Block); ok { + r0 = rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StorageMock_GetUncheckedBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUncheckedBlocks' +type StorageMock_GetUncheckedBlocks_Call struct { + *mock.Call +} + +// GetUncheckedBlocks is a helper method to define mock.On call +// - ctx context.Context +// - fromBlockNumber uint64 +// - toBlockNumber uint64 +// - dbTx pgx.Tx +func (_e *StorageMock_Expecter) GetUncheckedBlocks(ctx interface{}, fromBlockNumber interface{}, toBlockNumber interface{}, dbTx interface{}) *StorageMock_GetUncheckedBlocks_Call { + return &StorageMock_GetUncheckedBlocks_Call{Call: _e.mock.On("GetUncheckedBlocks", ctx, fromBlockNumber, toBlockNumber, dbTx)} +} + +func (_c *StorageMock_GetUncheckedBlocks_Call) Run(run func(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx)) *StorageMock_GetUncheckedBlocks_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64), args[3].(pgx.Tx)) + }) + return _c +} + +func (_c *StorageMock_GetUncheckedBlocks_Call) Return(_a0 []*state.Block, _a1 error) *StorageMock_GetUncheckedBlocks_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StorageMock_GetUncheckedBlocks_Call) RunAndReturn(run func(context.Context, uint64, uint64, pgx.Tx) ([]*state.Block, error)) *StorageMock_GetUncheckedBlocks_Call { + _c.Call.Return(run) + return _c +} + // GetVerifiedBatch provides a mock function with given fields: ctx, batchNumber, dbTx func (_m *StorageMock) GetVerifiedBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.VerifiedBatch, error) { ret := _m.Called(ctx, batchNumber, dbTx) diff --git a/state/pgstatestorage/block.go b/state/pgstatestorage/block.go index 768b384df1..7c657a6e3b 100644 --- a/state/pgstatestorage/block.go +++ b/state/pgstatestorage/block.go @@ -63,6 +63,35 @@ func (p *PostgresStorage) GetFirstUncheckedBlock(ctx context.Context, fromBlockN return &block, err } +func (p *PostgresStorage) GetUncheckedBlocks(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx) ([]*state.Block, error) { + const getUncheckedBlocksSQL = "SELECT block_num, block_hash, parent_hash, received_at, checked FROM state.block WHERE block_num>=$1 AND block_num<=$2 AND checked=false ORDER BY block_num" + + q := p.getExecQuerier(dbTx) + + rows, err := q.Query(ctx, getUncheckedBlocksSQL, fromBlockNumber, toBlockNumber) + if err != nil { + return nil, err + } + defer rows.Close() + + var blocks []*state.Block + for rows.Next() { + var ( + blockHash string + parentHash string + block state.Block + ) + err := rows.Scan(&block.BlockNumber, &blockHash, &parentHash, &block.ReceivedAt, &block.Checked) + if err != nil { + return nil, err + } + block.BlockHash = common.HexToHash(blockHash) + block.ParentHash = common.HexToHash(parentHash) + blocks = append(blocks, &block) + } + return blocks, nil +} + // GetPreviousBlock gets the offset previous L1 block respect to latest. func (p *PostgresStorage) GetPreviousBlock(ctx context.Context, offset uint64, dbTx pgx.Tx) (*state.Block, error) { var ( @@ -83,6 +112,26 @@ func (p *PostgresStorage) GetPreviousBlock(ctx context.Context, offset uint64, d return &block, err } +// GetPreviousBlockToBlockNumber gets the previous L1 block respect blockNumber. +func (p *PostgresStorage) GetPreviousBlockToBlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) { + var ( + blockHash string + parentHash string + block state.Block + ) + const getPreviousBlockSQL = "SELECT block_num, block_hash, parent_hash, received_at,checked FROM state.block WHERE block_num < $1 ORDER BY block_num DESC LIMIT 1 " + + q := p.getExecQuerier(dbTx) + + err := q.QueryRow(ctx, getPreviousBlockSQL, blockNumber).Scan(&block.BlockNumber, &blockHash, &parentHash, &block.ReceivedAt, &block.Checked) + if errors.Is(err, pgx.ErrNoRows) { + return nil, state.ErrNotFound + } + block.BlockHash = common.HexToHash(blockHash) + block.ParentHash = common.HexToHash(parentHash) + return &block, err +} + // GetBlockByNumber returns the L1 block with the given number. func (p *PostgresStorage) GetBlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) { var ( diff --git a/state/pgstatestorage/l1infotree.go b/state/pgstatestorage/l1infotree.go index ed3fe2dd38..83c869cf5a 100644 --- a/state/pgstatestorage/l1infotree.go +++ b/state/pgstatestorage/l1infotree.go @@ -53,7 +53,7 @@ func (p *PostgresStorage) GetLatestL1InfoRoot(ctx context.Context, maxBlockNumbe const getL1InfoRootSQL = `SELECT block_num, timestamp, mainnet_exit_root, rollup_exit_root, global_exit_root, prev_block_hash, l1_info_root, l1_info_tree_index FROM state.exit_root WHERE l1_info_tree_index IS NOT NULL AND block_num <= $1 - ORDER BY l1_info_tree_index DESC` + ORDER BY l1_info_tree_index DESC LIMIT 1` entry := state.L1InfoTreeExitRootStorageEntry{} diff --git a/state/pgstatestorage/pgstatestorage_test.go b/state/pgstatestorage/pgstatestorage_test.go index 7502dae736..df62bdc44f 100644 --- a/state/pgstatestorage/pgstatestorage_test.go +++ b/state/pgstatestorage/pgstatestorage_test.go @@ -1795,3 +1795,61 @@ func TestUpdateCheckedBlockByNumber(t *testing.T) { require.NoError(t, err) require.False(t, b1.Checked) } + +func TestGetFirstUncheckedBlock(t *testing.T) { + var err error + blockNumber := uint64(51001) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber, Checked: true}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 1, Checked: false}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 2, Checked: true}, nil) + require.NoError(t, err) + + block, err := testState.GetFirstUncheckedBlock(context.Background(), blockNumber, nil) + require.NoError(t, err) + require.Equal(t, uint64(blockNumber+1), block.BlockNumber) +} + +func TestUpdateCheckedBlockByNumber(t *testing.T) { + var err error + blockNumber := uint64(54001) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber, Checked: true}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 1, Checked: false}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 2, Checked: true}, nil) + require.NoError(t, err) + + b1, err := testState.GetBlockByNumber(context.Background(), uint64(blockNumber), nil) + require.NoError(t, err) + require.True(t, b1.Checked) + + err = testState.UpdateCheckedBlockByNumber(context.Background(), uint64(blockNumber), false, nil) + require.NoError(t, err) + + b1, err = testState.GetBlockByNumber(context.Background(), uint64(blockNumber), nil) + require.NoError(t, err) + require.False(t, b1.Checked) +} + +func TestGetUncheckedBlocks(t *testing.T) { + var err error + blockNumber := uint64(61001) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber, Checked: true}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 1, Checked: false}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 2, Checked: true}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 3, Checked: false}, nil) + require.NoError(t, err) + err = testState.AddBlock(context.Background(), &state.Block{BlockNumber: blockNumber + 4, Checked: false}, nil) + require.NoError(t, err) + + blocks, err := testState.GetUncheckedBlocks(context.Background(), blockNumber, blockNumber+3, nil) + require.NoError(t, err) + require.Equal(t, 2, len(blocks)) + require.Equal(t, uint64(blockNumber+1), blocks[0].BlockNumber) + require.Equal(t, uint64(blockNumber+3), blocks[1].BlockNumber) +} diff --git a/state/reset.go b/state/reset.go index 655f5f3dd1..e1c5a72675 100644 --- a/state/reset.go +++ b/state/reset.go @@ -18,10 +18,15 @@ func (s *State) Reset(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) erro log.Error("error resetting L1BlockNumber. Error: ", err) return err } + s.ResetL1InfoTree() + return nil +} + +// ResetL1InfoTree resets the L1InfoTree +func (s *State) ResetL1InfoTree() { // Discard L1InfoTree cache // We can't rebuild cache, because we are inside a transaction, so we dont known // is going to be a commit or a rollback. So is going to be rebuild on the next // request that needs it. s.l1InfoTree = nil - return nil } diff --git a/state/state.go b/state/state.go index a1a754242f..4c662035f8 100644 --- a/state/state.go +++ b/state/state.go @@ -62,13 +62,37 @@ func NewState(cfg Config, storage storage, executorClient executor.ExecutorServi return state } +// StateTx is the state transaction that extends the database tx +type StateTx struct { + pgx.Tx + stateInstance *State + L1InfoTreeModified bool +} + // BeginStateTransaction starts a state transaction func (s *State) BeginStateTransaction(ctx context.Context) (pgx.Tx, error) { tx, err := s.Begin(ctx) if err != nil { return nil, err } - return tx, nil + res := &StateTx{ + Tx: tx, + stateInstance: s, + } + return res, nil +} + +// Rollback do the dbTx rollback + modifications in cache mechanism +func (tx *StateTx) Rollback(ctx context.Context) error { + if tx.L1InfoTreeModified { + tx.stateInstance.ResetL1InfoTree() + } + return tx.Tx.Rollback(ctx) +} + +// SetL1InfoTreeModified sets the flag to true to save that the L1InfoTree has been modified +func (tx *StateTx) SetL1InfoTreeModified() { + tx.L1InfoTreeModified = true } // GetBalance from a given address diff --git a/state/transaction.go b/state/transaction.go index faa063731d..f3045ebe40 100644 --- a/state/transaction.go +++ b/state/transaction.go @@ -141,7 +141,7 @@ func (s *State) StoreTransactions(ctx context.Context, batchNumber uint64, proce } // firstTxToInsert := len(existingTxs) - + txIndex := 0 for i := 0; i < len(processedTxs); i++ { processedTx := processedTxs[i] // if the transaction has an intrinsic invalid tx error it means @@ -169,7 +169,7 @@ func (s *State) StoreTransactions(ctx context.Context, batchNumber uint64, proce header.BlockInfoRoot = processedBlock.BlockInfoRoot transactions := []*types.Transaction{&processedTx.Tx} - receipt := GenerateReceipt(header.Number, processedTx, uint(i), forkID) + receipt := GenerateReceipt(header.Number, processedTx, uint(txIndex), forkID) if !CheckLogOrder(receipt.Logs) { return fmt.Errorf("error: logs received from executor are not in order") } @@ -193,6 +193,7 @@ func (s *State) StoreTransactions(ctx context.Context, batchNumber uint64, proce if err := s.AddL2Block(ctx, batchNumber, l2Block, receipts, txsL2Hash, storeTxsEGPData, imStateRoots, dbTx); err != nil { return err } + txIndex++ } } return nil diff --git a/synchronizer/actions/check_l2block.go b/synchronizer/actions/check_l2block.go index 14c9e5cb19..ce08c82655 100644 --- a/synchronizer/actions/check_l2block.go +++ b/synchronizer/actions/check_l2block.go @@ -36,13 +36,16 @@ type CheckL2BlockHash struct { func NewCheckL2BlockHash(state stateGetL2Block, trustedClient trustedRPCGetL2Block, initialL2BlockNumber uint64, - modulusBlockNumber uint64) *CheckL2BlockHash { + modulusBlockNumber uint64) (*CheckL2BlockHash, error) { + if modulusBlockNumber == 0 { + return nil, fmt.Errorf("error: modulusBlockNumber is zero") + } return &CheckL2BlockHash{ state: state, trustedClient: trustedClient, lastL2BlockChecked: initialL2BlockNumber, modulusL2BlockToCheck: modulusBlockNumber, - } + }, nil } // CheckL2Block checks the L2Block hash between the local and the trusted @@ -74,6 +77,9 @@ func (p *CheckL2BlockHash) GetNextL2BlockToCheck(lastLocalL2BlockNumber, minL2Bl log.Infof("checkL2block: skip check L2block (next to check: %d) currently LastL2BlockNumber: %d", minL2BlockNumberToCheck, lastLocalL2BlockNumber) return false, 0 } + if l2BlockNumber%p.modulusL2BlockToCheck != 0 { + return false, 0 + } return true, l2BlockNumber } diff --git a/synchronizer/actions/check_l2block_test.go b/synchronizer/actions/check_l2block_test.go index 28a8a503b7..cdbf61a981 100644 --- a/synchronizer/actions/check_l2block_test.go +++ b/synchronizer/actions/check_l2block_test.go @@ -32,12 +32,15 @@ func TestCheckL2BlockHash_GetMinimumL2BlockToCheck(t *testing.T) { {1, 10, 10}, {9, 10, 10}, {10, 10, 20}, - {0, 0, 1}, - {1, 0, 2}, + {0, 1, 1}, + {1, 1, 2}, } + _, err := actions.NewCheckL2BlockHash(nil, nil, 1, 0) + require.Error(t, err) for _, data := range values { // Call the GetNextL2BlockToCheck method - checkL2Block := actions.NewCheckL2BlockHash(nil, nil, data.initial, data.modulus) + checkL2Block, err := actions.NewCheckL2BlockHash(nil, nil, data.initial, data.modulus) + require.NoError(t, err) nextL2Block := checkL2Block.GetMinimumL2BlockToCheck() // Assert the expected result @@ -58,7 +61,9 @@ func newCheckL2BlocksTestData(t *testing.T, initialL2Block, modulus uint64) Chec mockState: mock_syncinterfaces.NewStateFullInterface(t), zKEVMClient: mock_syncinterfaces.NewZKEVMClientEthereumCompatibleInterface(t), } - res.sut = actions.NewCheckL2BlockHash(res.mockState, res.zKEVMClient, initialL2Block, modulus) + var err error + res.sut, err = actions.NewCheckL2BlockHash(res.mockState, res.zKEVMClient, initialL2Block, modulus) + require.NoError(t, err) return res } func TestCheckL2BlockHash_GetNextL2BlockToCheck(t *testing.T) { @@ -77,7 +82,8 @@ func TestCheckL2BlockHash_GetNextL2BlockToCheck(t *testing.T) { } for _, data := range values { - checkL2Block := actions.NewCheckL2BlockHash(nil, nil, 0, 0) + checkL2Block, err := actions.NewCheckL2BlockHash(nil, nil, 0, 1) + require.NoError(t, err) shouldCheck, nextL2Block := checkL2Block.GetNextL2BlockToCheck(data.lastLocalL2BlockNumber, data.minL2BlockNumberToCheck) assert.Equal(t, data.expectedShouldCheck, shouldCheck, data) @@ -86,7 +92,7 @@ func TestCheckL2BlockHash_GetNextL2BlockToCheck(t *testing.T) { } func TestCheckL2BlockHashMatch(t *testing.T) { - data := newCheckL2BlocksTestData(t, 1, 10) + data := newCheckL2BlocksTestData(t, 1, 14) lastL2Block := uint64(14) lastL2BlockBigInt := big.NewInt(int64(lastL2Block)) gethHeader := types.Header{ @@ -113,7 +119,7 @@ func TestCheckL2BlockHashMatch(t *testing.T) { } func TestCheckL2BlockHashMismatch(t *testing.T) { - data := newCheckL2BlocksTestData(t, 1, 10) + data := newCheckL2BlocksTestData(t, 1, 14) lastL2Block := uint64(14) lastL2BlockBigInt := big.NewInt(int64(lastL2Block)) gethHeader := types.Header{ diff --git a/synchronizer/common/reorg_error.go b/synchronizer/common/reorg_error.go new file mode 100644 index 0000000000..e60dcfb22c --- /dev/null +++ b/synchronizer/common/reorg_error.go @@ -0,0 +1,44 @@ +package common + +import "fmt" + +// ReorgError is an error that is raised when a reorg is detected +type ReorgError struct { + // BlockNumber is the block number that caused the reorg + BlockNumber uint64 + Err error +} + +// NewReorgError creates a new ReorgError +func NewReorgError(blockNumber uint64, err error) *ReorgError { + return &ReorgError{ + BlockNumber: blockNumber, + Err: err, + } +} + +func (e *ReorgError) Error() string { + return fmt.Sprintf("%s blockNumber: %d", e.Err.Error(), e.BlockNumber) +} + +// IsReorgError checks if an error is a ReorgError +func IsReorgError(err error) bool { + _, ok := err.(*ReorgError) + return ok +} + +// GetReorgErrorBlockNumber returns the block number that caused the reorg +func GetReorgErrorBlockNumber(err error) uint64 { + if reorgErr, ok := err.(*ReorgError); ok { + return reorgErr.BlockNumber + } + return 0 +} + +// GetReorgError returns the error that caused the reorg +func GetReorgError(err error) error { + if reorgErr, ok := err.(*ReorgError); ok { + return reorgErr.Err + } + return nil +} diff --git a/synchronizer/common/syncinterfaces/async_l1_block_checker.go b/synchronizer/common/syncinterfaces/async_l1_block_checker.go new file mode 100644 index 0000000000..b95903901a --- /dev/null +++ b/synchronizer/common/syncinterfaces/async_l1_block_checker.go @@ -0,0 +1,40 @@ +package syncinterfaces + +import ( + "context" + "fmt" + + "github.com/0xPolygonHermez/zkevm-node/state" +) + +type IterationResult struct { + Err error + ReorgDetected bool + BlockNumber uint64 + ReorgMessage string +} + +func (ir *IterationResult) String() string { + if ir.Err == nil { + if ir.ReorgDetected { + return fmt.Sprintf("IterationResult{ReorgDetected: %v, BlockNumber: %d ReorgMessage:%s}", ir.ReorgDetected, ir.BlockNumber, ir.ReorgMessage) + } else { + return "IterationResult{None}" + } + } else { + return fmt.Sprintf("IterationResult{Err: %s, ReorgDetected: %v, BlockNumber: %d ReorgMessage:%s}", ir.Err.Error(), ir.ReorgDetected, ir.BlockNumber, ir.ReorgMessage) + } +} + +type AsyncL1BlockChecker interface { + Run(ctx context.Context, onFinish func()) + RunSynchronous(ctx context.Context) IterationResult + Stop() + GetResult() *IterationResult +} + +type L1BlockCheckerIntegrator interface { + OnStart(ctx context.Context) error + OnResetState(ctx context.Context) + CheckReorgWrapper(ctx context.Context, reorgFirstBlockOk *state.Block, errReportedByReorgFunc error) (*state.Block, error) +} diff --git a/synchronizer/common/syncinterfaces/etherman.go b/synchronizer/common/syncinterfaces/etherman.go index 24e5dbda69..fdbdd669f8 100644 --- a/synchronizer/common/syncinterfaces/etherman.go +++ b/synchronizer/common/syncinterfaces/etherman.go @@ -14,10 +14,12 @@ type EthermanFullInterface interface { HeaderByNumber(ctx context.Context, number *big.Int) (*ethTypes.Header, error) GetRollupInfoByBlockRange(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error) EthBlockByNumber(ctx context.Context, blockNumber uint64) (*ethTypes.Block, error) - GetLatestBatchNumber() (uint64, error) GetTrustedSequencerURL() (string, error) VerifyGenBlockNumber(ctx context.Context, genBlockNumber uint64) (bool, error) GetLatestVerifiedBatchNum() (uint64, error) + + EthermanGetLatestBatchNumber + GetFinalizedBlockNumber(ctx context.Context) (uint64, error) } type EthermanGetLatestBatchNumber interface { diff --git a/synchronizer/common/syncinterfaces/mocks/async_l1_block_checker.go b/synchronizer/common/syncinterfaces/mocks/async_l1_block_checker.go new file mode 100644 index 0000000000..67b38de348 --- /dev/null +++ b/synchronizer/common/syncinterfaces/mocks/async_l1_block_checker.go @@ -0,0 +1,196 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_syncinterfaces + +import ( + context "context" + + syncinterfaces "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces" + mock "github.com/stretchr/testify/mock" +) + +// AsyncL1BlockChecker is an autogenerated mock type for the AsyncL1BlockChecker type +type AsyncL1BlockChecker struct { + mock.Mock +} + +type AsyncL1BlockChecker_Expecter struct { + mock *mock.Mock +} + +func (_m *AsyncL1BlockChecker) EXPECT() *AsyncL1BlockChecker_Expecter { + return &AsyncL1BlockChecker_Expecter{mock: &_m.Mock} +} + +// GetResult provides a mock function with given fields: +func (_m *AsyncL1BlockChecker) GetResult() *syncinterfaces.IterationResult { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetResult") + } + + var r0 *syncinterfaces.IterationResult + if rf, ok := ret.Get(0).(func() *syncinterfaces.IterationResult); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*syncinterfaces.IterationResult) + } + } + + return r0 +} + +// AsyncL1BlockChecker_GetResult_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResult' +type AsyncL1BlockChecker_GetResult_Call struct { + *mock.Call +} + +// GetResult is a helper method to define mock.On call +func (_e *AsyncL1BlockChecker_Expecter) GetResult() *AsyncL1BlockChecker_GetResult_Call { + return &AsyncL1BlockChecker_GetResult_Call{Call: _e.mock.On("GetResult")} +} + +func (_c *AsyncL1BlockChecker_GetResult_Call) Run(run func()) *AsyncL1BlockChecker_GetResult_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AsyncL1BlockChecker_GetResult_Call) Return(_a0 *syncinterfaces.IterationResult) *AsyncL1BlockChecker_GetResult_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AsyncL1BlockChecker_GetResult_Call) RunAndReturn(run func() *syncinterfaces.IterationResult) *AsyncL1BlockChecker_GetResult_Call { + _c.Call.Return(run) + return _c +} + +// Run provides a mock function with given fields: ctx, onFinish +func (_m *AsyncL1BlockChecker) Run(ctx context.Context, onFinish func()) { + _m.Called(ctx, onFinish) +} + +// AsyncL1BlockChecker_Run_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Run' +type AsyncL1BlockChecker_Run_Call struct { + *mock.Call +} + +// Run is a helper method to define mock.On call +// - ctx context.Context +// - onFinish func() +func (_e *AsyncL1BlockChecker_Expecter) Run(ctx interface{}, onFinish interface{}) *AsyncL1BlockChecker_Run_Call { + return &AsyncL1BlockChecker_Run_Call{Call: _e.mock.On("Run", ctx, onFinish)} +} + +func (_c *AsyncL1BlockChecker_Run_Call) Run(run func(ctx context.Context, onFinish func())) *AsyncL1BlockChecker_Run_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(func())) + }) + return _c +} + +func (_c *AsyncL1BlockChecker_Run_Call) Return() *AsyncL1BlockChecker_Run_Call { + _c.Call.Return() + return _c +} + +func (_c *AsyncL1BlockChecker_Run_Call) RunAndReturn(run func(context.Context, func())) *AsyncL1BlockChecker_Run_Call { + _c.Call.Return(run) + return _c +} + +// RunSynchronous provides a mock function with given fields: ctx +func (_m *AsyncL1BlockChecker) RunSynchronous(ctx context.Context) syncinterfaces.IterationResult { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for RunSynchronous") + } + + var r0 syncinterfaces.IterationResult + if rf, ok := ret.Get(0).(func(context.Context) syncinterfaces.IterationResult); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(syncinterfaces.IterationResult) + } + + return r0 +} + +// AsyncL1BlockChecker_RunSynchronous_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunSynchronous' +type AsyncL1BlockChecker_RunSynchronous_Call struct { + *mock.Call +} + +// RunSynchronous is a helper method to define mock.On call +// - ctx context.Context +func (_e *AsyncL1BlockChecker_Expecter) RunSynchronous(ctx interface{}) *AsyncL1BlockChecker_RunSynchronous_Call { + return &AsyncL1BlockChecker_RunSynchronous_Call{Call: _e.mock.On("RunSynchronous", ctx)} +} + +func (_c *AsyncL1BlockChecker_RunSynchronous_Call) Run(run func(ctx context.Context)) *AsyncL1BlockChecker_RunSynchronous_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *AsyncL1BlockChecker_RunSynchronous_Call) Return(_a0 syncinterfaces.IterationResult) *AsyncL1BlockChecker_RunSynchronous_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AsyncL1BlockChecker_RunSynchronous_Call) RunAndReturn(run func(context.Context) syncinterfaces.IterationResult) *AsyncL1BlockChecker_RunSynchronous_Call { + _c.Call.Return(run) + return _c +} + +// Stop provides a mock function with given fields: +func (_m *AsyncL1BlockChecker) Stop() { + _m.Called() +} + +// AsyncL1BlockChecker_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' +type AsyncL1BlockChecker_Stop_Call struct { + *mock.Call +} + +// Stop is a helper method to define mock.On call +func (_e *AsyncL1BlockChecker_Expecter) Stop() *AsyncL1BlockChecker_Stop_Call { + return &AsyncL1BlockChecker_Stop_Call{Call: _e.mock.On("Stop")} +} + +func (_c *AsyncL1BlockChecker_Stop_Call) Run(run func()) *AsyncL1BlockChecker_Stop_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AsyncL1BlockChecker_Stop_Call) Return() *AsyncL1BlockChecker_Stop_Call { + _c.Call.Return() + return _c +} + +func (_c *AsyncL1BlockChecker_Stop_Call) RunAndReturn(run func()) *AsyncL1BlockChecker_Stop_Call { + _c.Call.Return(run) + return _c +} + +// NewAsyncL1BlockChecker creates a new instance of AsyncL1BlockChecker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAsyncL1BlockChecker(t interface { + mock.TestingT + Cleanup(func()) +}) *AsyncL1BlockChecker { + mock := &AsyncL1BlockChecker{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go b/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go index fe6e6c3df6..a904419575 100644 --- a/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go +++ b/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go @@ -87,6 +87,62 @@ func (_c *EthermanFullInterface_EthBlockByNumber_Call) RunAndReturn(run func(con return _c } +// GetFinalizedBlockNumber provides a mock function with given fields: ctx +func (_m *EthermanFullInterface) GetFinalizedBlockNumber(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetFinalizedBlockNumber") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthermanFullInterface_GetFinalizedBlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFinalizedBlockNumber' +type EthermanFullInterface_GetFinalizedBlockNumber_Call struct { + *mock.Call +} + +// GetFinalizedBlockNumber is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthermanFullInterface_Expecter) GetFinalizedBlockNumber(ctx interface{}) *EthermanFullInterface_GetFinalizedBlockNumber_Call { + return &EthermanFullInterface_GetFinalizedBlockNumber_Call{Call: _e.mock.On("GetFinalizedBlockNumber", ctx)} +} + +func (_c *EthermanFullInterface_GetFinalizedBlockNumber_Call) Run(run func(ctx context.Context)) *EthermanFullInterface_GetFinalizedBlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthermanFullInterface_GetFinalizedBlockNumber_Call) Return(_a0 uint64, _a1 error) *EthermanFullInterface_GetFinalizedBlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthermanFullInterface_GetFinalizedBlockNumber_Call) RunAndReturn(run func(context.Context) (uint64, error)) *EthermanFullInterface_GetFinalizedBlockNumber_Call { + _c.Call.Return(run) + return _c +} + // GetLatestBatchNumber provides a mock function with given fields: func (_m *EthermanFullInterface) GetLatestBatchNumber() (uint64, error) { ret := _m.Called() diff --git a/synchronizer/common/syncinterfaces/mocks/l1_block_checker_integrator.go b/synchronizer/common/syncinterfaces/mocks/l1_block_checker_integrator.go new file mode 100644 index 0000000000..0248874f26 --- /dev/null +++ b/synchronizer/common/syncinterfaces/mocks/l1_block_checker_integrator.go @@ -0,0 +1,176 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_syncinterfaces + +import ( + context "context" + + state "github.com/0xPolygonHermez/zkevm-node/state" + mock "github.com/stretchr/testify/mock" +) + +// L1BlockCheckerIntegrator is an autogenerated mock type for the L1BlockCheckerIntegrator type +type L1BlockCheckerIntegrator struct { + mock.Mock +} + +type L1BlockCheckerIntegrator_Expecter struct { + mock *mock.Mock +} + +func (_m *L1BlockCheckerIntegrator) EXPECT() *L1BlockCheckerIntegrator_Expecter { + return &L1BlockCheckerIntegrator_Expecter{mock: &_m.Mock} +} + +// CheckReorgWrapper provides a mock function with given fields: ctx, reorgFirstBlockOk, errReportedByReorgFunc +func (_m *L1BlockCheckerIntegrator) CheckReorgWrapper(ctx context.Context, reorgFirstBlockOk *state.Block, errReportedByReorgFunc error) (*state.Block, error) { + ret := _m.Called(ctx, reorgFirstBlockOk, errReportedByReorgFunc) + + if len(ret) == 0 { + panic("no return value specified for CheckReorgWrapper") + } + + var r0 *state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *state.Block, error) (*state.Block, error)); ok { + return rf(ctx, reorgFirstBlockOk, errReportedByReorgFunc) + } + if rf, ok := ret.Get(0).(func(context.Context, *state.Block, error) *state.Block); ok { + r0 = rf(ctx, reorgFirstBlockOk, errReportedByReorgFunc) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *state.Block, error) error); ok { + r1 = rf(ctx, reorgFirstBlockOk, errReportedByReorgFunc) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1BlockCheckerIntegrator_CheckReorgWrapper_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckReorgWrapper' +type L1BlockCheckerIntegrator_CheckReorgWrapper_Call struct { + *mock.Call +} + +// CheckReorgWrapper is a helper method to define mock.On call +// - ctx context.Context +// - reorgFirstBlockOk *state.Block +// - errReportedByReorgFunc error +func (_e *L1BlockCheckerIntegrator_Expecter) CheckReorgWrapper(ctx interface{}, reorgFirstBlockOk interface{}, errReportedByReorgFunc interface{}) *L1BlockCheckerIntegrator_CheckReorgWrapper_Call { + return &L1BlockCheckerIntegrator_CheckReorgWrapper_Call{Call: _e.mock.On("CheckReorgWrapper", ctx, reorgFirstBlockOk, errReportedByReorgFunc)} +} + +func (_c *L1BlockCheckerIntegrator_CheckReorgWrapper_Call) Run(run func(ctx context.Context, reorgFirstBlockOk *state.Block, errReportedByReorgFunc error)) *L1BlockCheckerIntegrator_CheckReorgWrapper_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*state.Block), args[2].(error)) + }) + return _c +} + +func (_c *L1BlockCheckerIntegrator_CheckReorgWrapper_Call) Return(_a0 *state.Block, _a1 error) *L1BlockCheckerIntegrator_CheckReorgWrapper_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1BlockCheckerIntegrator_CheckReorgWrapper_Call) RunAndReturn(run func(context.Context, *state.Block, error) (*state.Block, error)) *L1BlockCheckerIntegrator_CheckReorgWrapper_Call { + _c.Call.Return(run) + return _c +} + +// OnResetState provides a mock function with given fields: ctx +func (_m *L1BlockCheckerIntegrator) OnResetState(ctx context.Context) { + _m.Called(ctx) +} + +// L1BlockCheckerIntegrator_OnResetState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnResetState' +type L1BlockCheckerIntegrator_OnResetState_Call struct { + *mock.Call +} + +// OnResetState is a helper method to define mock.On call +// - ctx context.Context +func (_e *L1BlockCheckerIntegrator_Expecter) OnResetState(ctx interface{}) *L1BlockCheckerIntegrator_OnResetState_Call { + return &L1BlockCheckerIntegrator_OnResetState_Call{Call: _e.mock.On("OnResetState", ctx)} +} + +func (_c *L1BlockCheckerIntegrator_OnResetState_Call) Run(run func(ctx context.Context)) *L1BlockCheckerIntegrator_OnResetState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L1BlockCheckerIntegrator_OnResetState_Call) Return() *L1BlockCheckerIntegrator_OnResetState_Call { + _c.Call.Return() + return _c +} + +func (_c *L1BlockCheckerIntegrator_OnResetState_Call) RunAndReturn(run func(context.Context)) *L1BlockCheckerIntegrator_OnResetState_Call { + _c.Call.Return(run) + return _c +} + +// OnStart provides a mock function with given fields: ctx +func (_m *L1BlockCheckerIntegrator) OnStart(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for OnStart") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// L1BlockCheckerIntegrator_OnStart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnStart' +type L1BlockCheckerIntegrator_OnStart_Call struct { + *mock.Call +} + +// OnStart is a helper method to define mock.On call +// - ctx context.Context +func (_e *L1BlockCheckerIntegrator_Expecter) OnStart(ctx interface{}) *L1BlockCheckerIntegrator_OnStart_Call { + return &L1BlockCheckerIntegrator_OnStart_Call{Call: _e.mock.On("OnStart", ctx)} +} + +func (_c *L1BlockCheckerIntegrator_OnStart_Call) Run(run func(ctx context.Context)) *L1BlockCheckerIntegrator_OnStart_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L1BlockCheckerIntegrator_OnStart_Call) Return(_a0 error) *L1BlockCheckerIntegrator_OnStart_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L1BlockCheckerIntegrator_OnStart_Call) RunAndReturn(run func(context.Context) error) *L1BlockCheckerIntegrator_OnStart_Call { + _c.Call.Return(run) + return _c +} + +// NewL1BlockCheckerIntegrator creates a new instance of L1BlockCheckerIntegrator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewL1BlockCheckerIntegrator(t interface { + mock.TestingT + Cleanup(func()) +}) *L1BlockCheckerIntegrator { + mock := &L1BlockCheckerIntegrator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/common/syncinterfaces/mocks/state_full_interface.go b/synchronizer/common/syncinterfaces/mocks/state_full_interface.go index f4790bc695..fa570dbe7f 100644 --- a/synchronizer/common/syncinterfaces/mocks/state_full_interface.go +++ b/synchronizer/common/syncinterfaces/mocks/state_full_interface.go @@ -821,6 +821,66 @@ func (_c *StateFullInterface_GetBatchByNumber_Call) RunAndReturn(run func(contex return _c } +// GetBlockByNumber provides a mock function with given fields: ctx, blockNumber, dbTx +func (_m *StateFullInterface) GetBlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) { + ret := _m.Called(ctx, blockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetBlockByNumber") + } + + var r0 *state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (*state.Block, error)); ok { + return rf(ctx, blockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.Block); ok { + r0 = rf(ctx, blockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, blockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StateFullInterface_GetBlockByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBlockByNumber' +type StateFullInterface_GetBlockByNumber_Call struct { + *mock.Call +} + +// GetBlockByNumber is a helper method to define mock.On call +// - ctx context.Context +// - blockNumber uint64 +// - dbTx pgx.Tx +func (_e *StateFullInterface_Expecter) GetBlockByNumber(ctx interface{}, blockNumber interface{}, dbTx interface{}) *StateFullInterface_GetBlockByNumber_Call { + return &StateFullInterface_GetBlockByNumber_Call{Call: _e.mock.On("GetBlockByNumber", ctx, blockNumber, dbTx)} +} + +func (_c *StateFullInterface_GetBlockByNumber_Call) Run(run func(ctx context.Context, blockNumber uint64, dbTx pgx.Tx)) *StateFullInterface_GetBlockByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(pgx.Tx)) + }) + return _c +} + +func (_c *StateFullInterface_GetBlockByNumber_Call) Return(_a0 *state.Block, _a1 error) *StateFullInterface_GetBlockByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StateFullInterface_GetBlockByNumber_Call) RunAndReturn(run func(context.Context, uint64, pgx.Tx) (*state.Block, error)) *StateFullInterface_GetBlockByNumber_Call { + _c.Call.Return(run) + return _c +} + // GetExitRootByGlobalExitRoot provides a mock function with given fields: ctx, ger, dbTx func (_m *StateFullInterface) GetExitRootByGlobalExitRoot(ctx context.Context, ger common.Hash, dbTx pgx.Tx) (*state.GlobalExitRoot, error) { ret := _m.Called(ctx, ger, dbTx) @@ -1805,6 +1865,66 @@ func (_c *StateFullInterface_GetPreviousBlock_Call) RunAndReturn(run func(contex return _c } +// GetPreviousBlockToBlockNumber provides a mock function with given fields: ctx, blockNumber, dbTx +func (_m *StateFullInterface) GetPreviousBlockToBlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) { + ret := _m.Called(ctx, blockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetPreviousBlockToBlockNumber") + } + + var r0 *state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (*state.Block, error)); ok { + return rf(ctx, blockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.Block); ok { + r0 = rf(ctx, blockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, blockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StateFullInterface_GetPreviousBlockToBlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviousBlockToBlockNumber' +type StateFullInterface_GetPreviousBlockToBlockNumber_Call struct { + *mock.Call +} + +// GetPreviousBlockToBlockNumber is a helper method to define mock.On call +// - ctx context.Context +// - blockNumber uint64 +// - dbTx pgx.Tx +func (_e *StateFullInterface_Expecter) GetPreviousBlockToBlockNumber(ctx interface{}, blockNumber interface{}, dbTx interface{}) *StateFullInterface_GetPreviousBlockToBlockNumber_Call { + return &StateFullInterface_GetPreviousBlockToBlockNumber_Call{Call: _e.mock.On("GetPreviousBlockToBlockNumber", ctx, blockNumber, dbTx)} +} + +func (_c *StateFullInterface_GetPreviousBlockToBlockNumber_Call) Run(run func(ctx context.Context, blockNumber uint64, dbTx pgx.Tx)) *StateFullInterface_GetPreviousBlockToBlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(pgx.Tx)) + }) + return _c +} + +func (_c *StateFullInterface_GetPreviousBlockToBlockNumber_Call) Return(_a0 *state.Block, _a1 error) *StateFullInterface_GetPreviousBlockToBlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StateFullInterface_GetPreviousBlockToBlockNumber_Call) RunAndReturn(run func(context.Context, uint64, pgx.Tx) (*state.Block, error)) *StateFullInterface_GetPreviousBlockToBlockNumber_Call { + _c.Call.Return(run) + return _c +} + // GetReorgedTransactions provides a mock function with given fields: ctx, batchNumber, dbTx func (_m *StateFullInterface) GetReorgedTransactions(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]*types.Transaction, error) { ret := _m.Called(ctx, batchNumber, dbTx) @@ -1988,6 +2108,67 @@ func (_c *StateFullInterface_GetStoredFlushID_Call) RunAndReturn(run func(contex return _c } +// GetUncheckedBlocks provides a mock function with given fields: ctx, fromBlockNumber, toBlockNumber, dbTx +func (_m *StateFullInterface) GetUncheckedBlocks(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx) ([]*state.Block, error) { + ret := _m.Called(ctx, fromBlockNumber, toBlockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetUncheckedBlocks") + } + + var r0 []*state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, pgx.Tx) ([]*state.Block, error)); ok { + return rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, pgx.Tx) []*state.Block); ok { + r0 = rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StateFullInterface_GetUncheckedBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUncheckedBlocks' +type StateFullInterface_GetUncheckedBlocks_Call struct { + *mock.Call +} + +// GetUncheckedBlocks is a helper method to define mock.On call +// - ctx context.Context +// - fromBlockNumber uint64 +// - toBlockNumber uint64 +// - dbTx pgx.Tx +func (_e *StateFullInterface_Expecter) GetUncheckedBlocks(ctx interface{}, fromBlockNumber interface{}, toBlockNumber interface{}, dbTx interface{}) *StateFullInterface_GetUncheckedBlocks_Call { + return &StateFullInterface_GetUncheckedBlocks_Call{Call: _e.mock.On("GetUncheckedBlocks", ctx, fromBlockNumber, toBlockNumber, dbTx)} +} + +func (_c *StateFullInterface_GetUncheckedBlocks_Call) Run(run func(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx)) *StateFullInterface_GetUncheckedBlocks_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64), args[3].(pgx.Tx)) + }) + return _c +} + +func (_c *StateFullInterface_GetUncheckedBlocks_Call) Return(_a0 []*state.Block, _a1 error) *StateFullInterface_GetUncheckedBlocks_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StateFullInterface_GetUncheckedBlocks_Call) RunAndReturn(run func(context.Context, uint64, uint64, pgx.Tx) ([]*state.Block, error)) *StateFullInterface_GetUncheckedBlocks_Call { + _c.Call.Return(run) + return _c +} + // OpenBatch provides a mock function with given fields: ctx, processingContext, dbTx func (_m *StateFullInterface) OpenBatch(ctx context.Context, processingContext state.ProcessingContext, dbTx pgx.Tx) error { ret := _m.Called(ctx, processingContext, dbTx) diff --git a/synchronizer/common/syncinterfaces/mocks/zkevm_client_ethereum_compatible_l2_block_getter.go b/synchronizer/common/syncinterfaces/mocks/zkevm_client_ethereum_compatible_l2_block_getter.go new file mode 100644 index 0000000000..58c2af0dff --- /dev/null +++ b/synchronizer/common/syncinterfaces/mocks/zkevm_client_ethereum_compatible_l2_block_getter.go @@ -0,0 +1,98 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_syncinterfaces + +import ( + context "context" + big "math/big" + + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// ZKEVMClientEthereumCompatibleL2BlockGetter is an autogenerated mock type for the ZKEVMClientEthereumCompatibleL2BlockGetter type +type ZKEVMClientEthereumCompatibleL2BlockGetter struct { + mock.Mock +} + +type ZKEVMClientEthereumCompatibleL2BlockGetter_Expecter struct { + mock *mock.Mock +} + +func (_m *ZKEVMClientEthereumCompatibleL2BlockGetter) EXPECT() *ZKEVMClientEthereumCompatibleL2BlockGetter_Expecter { + return &ZKEVMClientEthereumCompatibleL2BlockGetter_Expecter{mock: &_m.Mock} +} + +// BlockByNumber provides a mock function with given fields: ctx, number +func (_m *ZKEVMClientEthereumCompatibleL2BlockGetter) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for BlockByNumber") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Block, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Block); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockByNumber' +type ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call struct { + *mock.Call +} + +// BlockByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *ZKEVMClientEthereumCompatibleL2BlockGetter_Expecter) BlockByNumber(ctx interface{}, number interface{}) *ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call { + return &ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call{Call: _e.mock.On("BlockByNumber", ctx, number)} +} + +func (_c *ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call) Return(_a0 *types.Block, _a1 error) *ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Block, error)) *ZKEVMClientEthereumCompatibleL2BlockGetter_BlockByNumber_Call { + _c.Call.Return(run) + return _c +} + +// NewZKEVMClientEthereumCompatibleL2BlockGetter creates a new instance of ZKEVMClientEthereumCompatibleL2BlockGetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewZKEVMClientEthereumCompatibleL2BlockGetter(t interface { + mock.TestingT + Cleanup(func()) +}) *ZKEVMClientEthereumCompatibleL2BlockGetter { + mock := &ZKEVMClientEthereumCompatibleL2BlockGetter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/common/syncinterfaces/state.go b/synchronizer/common/syncinterfaces/state.go index 0aff583319..cafae4104e 100644 --- a/synchronizer/common/syncinterfaces/state.go +++ b/synchronizer/common/syncinterfaces/state.go @@ -28,6 +28,7 @@ type StateFullInterface interface { AddForcedBatch(ctx context.Context, forcedBatch *state.ForcedBatch, dbTx pgx.Tx) error AddBlock(ctx context.Context, block *state.Block, dbTx pgx.Tx) error Reset(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) error + GetBlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) GetPreviousBlock(ctx context.Context, offset uint64, dbTx pgx.Tx) (*state.Block, error) GetFirstUncheckedBlock(ctx context.Context, fromBlockNumber uint64, dbTx pgx.Tx) (*state.Block, error) UpdateCheckedBlockByNumber(ctx context.Context, blockNumber uint64, newCheckedStatus bool, dbTx pgx.Tx) error @@ -75,4 +76,6 @@ type StateFullInterface interface { UpdateForkIDBlockNumber(ctx context.Context, forkdID uint64, newBlockNumber uint64, updateMemCache bool, dbTx pgx.Tx) error GetLastL2BlockNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) GetL2BlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.L2Block, error) + GetUncheckedBlocks(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx) ([]*state.Block, error) + GetPreviousBlockToBlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) } diff --git a/synchronizer/config.go b/synchronizer/config.go index 0f7d822a60..ef51d41308 100644 --- a/synchronizer/config.go +++ b/synchronizer/config.go @@ -1,6 +1,8 @@ package synchronizer import ( + "fmt" + "github.com/0xPolygonHermez/zkevm-node/config/types" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l2_sync" ) @@ -22,6 +24,7 @@ type Config struct { // a modules 5, for instance, means check all l2block multiples of 5 (10,15,20,...) L1SyncCheckL2BlockNumberhModulus uint64 `mapstructure:"L1SyncCheckL2BlockNumberhModulus"` + L1BlockCheck L1BlockCheckConfig `mapstructure:"L1BlockCheck"` // L1SynchronizationMode define how to synchronize with L1: // - parallel: Request data to L1 in parallel, and process sequentially. The advantage is that executor is not blocked waiting for L1 data // - sequential: Request data to L1 and execute @@ -32,6 +35,35 @@ type Config struct { L2Synchronization l2_sync.Config `mapstructure:"L2Synchronization"` } +// L1BlockCheckConfig Configuration for L1 Block Checker +type L1BlockCheckConfig struct { + // Enable if is true then the check l1 Block Hash is active + Enable bool `mapstructure:"Enable"` + // L1SafeBlockPoint is the point that a block is considered safe enough to be checked + // it can be: finalized, safe,pending or latest + L1SafeBlockPoint string `mapstructure:"L1SafeBlockPoint" jsonschema:"enum=finalized,enum=safe, enum=pending,enum=latest"` + // L1SafeBlockOffset is the offset to add to L1SafeBlockPoint as a safe point + // it can be positive or negative + // Example: L1SafeBlockPoint= finalized, L1SafeBlockOffset= -10, then the safe block ten blocks before the finalized block + L1SafeBlockOffset int `mapstructure:"L1SafeBlockOffset"` + // ForceCheckBeforeStart if is true then the first time the system is started it will force to check all pending blocks + ForceCheckBeforeStart bool `mapstructure:"ForceCheckBeforeStart"` + + // PreCheckEnable if is true then the pre-check is active, will check blocks between L1SafeBlock and L1PreSafeBlock + PreCheckEnable bool `mapstructure:"PreCheckEnable"` + // L1PreSafeBlockPoint is the point that a block is considered safe enough to be checked + // it can be: finalized, safe,pending or latest + L1PreSafeBlockPoint string `mapstructure:"L1PreSafeBlockPoint" jsonschema:"enum=finalized,enum=safe, enum=pending,enum=latest"` + // L1PreSafeBlockOffset is the offset to add to L1PreSafeBlockPoint as a safe point + // it can be positive or negative + // Example: L1PreSafeBlockPoint= finalized, L1PreSafeBlockOffset= -10, then the safe block ten blocks before the finalized block + L1PreSafeBlockOffset int `mapstructure:"L1PreSafeBlockOffset"` +} + +func (c *L1BlockCheckConfig) String() string { + return fmt.Sprintf("Enable: %v, L1SafeBlockPoint: %s, L1SafeBlockOffset: %d, ForceCheckBeforeStart: %v", c.Enable, c.L1SafeBlockPoint, c.L1SafeBlockOffset, c.ForceCheckBeforeStart) +} + // L1ParallelSynchronizationConfig Configuration for parallel mode (if UL1SynchronizationMode equal to 'parallel') type L1ParallelSynchronizationConfig struct { // MaxClients Number of clients used to synchronize with L1 diff --git a/synchronizer/l1_check_block/async.go b/synchronizer/l1_check_block/async.go new file mode 100644 index 0000000000..4a2a45d924 --- /dev/null +++ b/synchronizer/l1_check_block/async.go @@ -0,0 +1,183 @@ +package l1_check_block + +import ( + "context" + "sync" + "time" + + "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces" +) + +// L1BlockChecker is an interface that defines the method to check L1 blocks +type L1BlockChecker interface { + Step(ctx context.Context) error +} + +const ( + defaultPeriodTime = time.Second +) + +// AsyncCheck is a wrapper for L1BlockChecker to become asynchronous +type AsyncCheck struct { + checker L1BlockChecker + mutex sync.Mutex + lastResult *syncinterfaces.IterationResult + onFinishCall func() + periodTime time.Duration + // Wg is a wait group to wait for the result + Wg sync.WaitGroup + ctx context.Context + cancelCtx context.CancelFunc + isRunning bool +} + +// NewAsyncCheck creates a new AsyncCheck +func NewAsyncCheck(checker L1BlockChecker) *AsyncCheck { + return &AsyncCheck{ + checker: checker, + periodTime: defaultPeriodTime, + } +} + +// SetPeriodTime sets the period time between relaunch checker.Step +func (a *AsyncCheck) SetPeriodTime(periodTime time.Duration) { + a.periodTime = periodTime +} + +// Run is a method that starts the async check +func (a *AsyncCheck) Run(ctx context.Context, onFinish func()) { + a.mutex.Lock() + defer a.mutex.Unlock() + a.onFinishCall = onFinish + if a.isRunning { + log.Infof("%s L1BlockChecker: already running, changing onFinish call", logPrefix) + return + } + a.lastResult = nil + a.ctx, a.cancelCtx = context.WithCancel(ctx) + a.launchChecker(a.ctx) +} + +// Stop is a method that stops the async check +func (a *AsyncCheck) Stop() { + a.cancelCtx() + a.Wg.Wait() +} + +// RunSynchronous is a method that forces the check to be synchronous before starting the async check +func (a *AsyncCheck) RunSynchronous(ctx context.Context) syncinterfaces.IterationResult { + return a.executeIteration(ctx) +} + +// GetResult returns the last result of the check: +// - Nil -> still running +// - Not nil -> finished, and this is the result. You must call again Run to start a new check +func (a *AsyncCheck) GetResult() *syncinterfaces.IterationResult { + a.mutex.Lock() + defer a.mutex.Unlock() + return a.lastResult +} + +// https://stackoverflow.com/questions/32840687/timeout-for-waitgroup-wait +// waitTimeout waits for the waitgroup for the specified max timeout. +// Returns true if waiting timed out. +func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { + c := make(chan struct{}) + go func() { + defer close(c) + wg.Wait() + }() + select { + case <-c: + return false // completed normally + case <-time.After(timeout): + return true // timed out + } +} + +// GetResultBlockingUntilAvailable wait the time specific in timeout, if reach timeout returns current +// result, if not, wait until the result is available. +// if timeout is 0, it waits indefinitely +func (a *AsyncCheck) GetResultBlockingUntilAvailable(timeout time.Duration) *syncinterfaces.IterationResult { + if timeout == 0 { + a.Wg.Wait() + } else { + waitTimeout(&a.Wg, timeout) + } + return a.GetResult() +} + +func (a *AsyncCheck) setResult(result syncinterfaces.IterationResult) { + a.mutex.Lock() + defer a.mutex.Unlock() + a.lastResult = &result +} + +func (a *AsyncCheck) launchChecker(ctx context.Context) { + // add waitGroup to wait for a result + a.Wg.Add(1) + a.isRunning = true + go func() { + log.Infof("%s L1BlockChecker: starting background process", logPrefix) + for { + result := a.step(ctx) + if result != nil { + a.setResult(*result) + // Result is set wg is done + break + } + } + log.Infof("%s L1BlockChecker: finished background process", logPrefix) + a.Wg.Done() + a.mutex.Lock() + onFinishCall := a.onFinishCall + a.isRunning = false + a.mutex.Unlock() + // call onFinish function with no mutex + if onFinishCall != nil { + onFinishCall() + } + }() +} + +// step is a method that executes until executeItertion +// returns an error or a reorg +func (a *AsyncCheck) step(ctx context.Context) *syncinterfaces.IterationResult { + select { + case <-ctx.Done(): + log.Debugf("%s L1BlockChecker: context done", logPrefix) + return &syncinterfaces.IterationResult{Err: ctx.Err()} + default: + result := a.executeIteration(ctx) + if result.ReorgDetected { + return &result + } + log.Debugf("%s L1BlockChecker:returned %s waiting %s to relaunch", logPrefix, result.String(), a.periodTime) + time.Sleep(a.periodTime) + } + return nil +} + +// executeIteration executes a single iteration of the checker +func (a *AsyncCheck) executeIteration(ctx context.Context) syncinterfaces.IterationResult { + res := syncinterfaces.IterationResult{} + log.Debugf("%s calling checker.Step(...)", logPrefix) + res.Err = a.checker.Step(ctx) + log.Debugf("%s returned checker.Step(...) %w", logPrefix, res.Err) + if res.Err != nil { + log.Errorf("%s Fail check L1 Blocks: %w", logPrefix, res.Err) + if common.IsReorgError(res.Err) { + // log error + blockNumber := common.GetReorgErrorBlockNumber(res.Err) + log.Infof("%s Reorg detected at block %d", logPrefix, blockNumber) + // It keeps blocked until the channel is read + res.BlockNumber = blockNumber + res.ReorgDetected = true + res.ReorgMessage = res.Err.Error() + res.Err = nil + } + } + return res +} diff --git a/synchronizer/l1_check_block/async_test.go b/synchronizer/l1_check_block/async_test.go new file mode 100644 index 0000000000..21358b1c8f --- /dev/null +++ b/synchronizer/l1_check_block/async_test.go @@ -0,0 +1,138 @@ +package l1_check_block_test + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block" + "github.com/stretchr/testify/require" +) + +var ( + errGenericToTestAsync = fmt.Errorf("error_async") + errReorgToTestAsync = common.NewReorgError(uint64(1234), fmt.Errorf("fake reorg to test")) + timeoutContextForAsyncTests = time.Second +) + +type mockChecker struct { + Wg *sync.WaitGroup + ErrorsToReturn []error +} + +func (m *mockChecker) Step(ctx context.Context) error { + defer m.Wg.Done() + err := m.ErrorsToReturn[0] + if len(m.ErrorsToReturn) > 0 { + m.ErrorsToReturn = m.ErrorsToReturn[1:] + } + return err +} + +// If checker.step() returns ok, the async object will relaunch the call +func TestAsyncRelaunchCheckerUntilReorgDetected(t *testing.T) { + mockChecker := &mockChecker{ErrorsToReturn: []error{nil, nil, errGenericToTestAsync, errReorgToTestAsync}, Wg: &sync.WaitGroup{}} + sut := l1_check_block.NewAsyncCheck(mockChecker) + sut.SetPeriodTime(0) + ctx, cancel := context.WithTimeout(context.Background(), timeoutContextForAsyncTests) + defer cancel() + mockChecker.Wg.Add(4) + + sut.Run(ctx, nil) + + mockChecker.Wg.Wait() + result := sut.GetResultBlockingUntilAvailable(0) + require.NotNil(t, result) + require.Equal(t, uint64(1234), result.BlockNumber) + require.Equal(t, true, result.ReorgDetected) + require.Equal(t, nil, result.Err) +} + +func TestAsyncGetResultIsNilUntilStops(t *testing.T) { + mockChecker := &mockChecker{ErrorsToReturn: []error{nil, nil, errGenericToTestAsync, errReorgToTestAsync}, Wg: &sync.WaitGroup{}} + sut := l1_check_block.NewAsyncCheck(mockChecker) + sut.SetPeriodTime(0) + ctx, cancel := context.WithTimeout(context.Background(), timeoutContextForAsyncTests) + defer cancel() + mockChecker.Wg.Add(4) + require.Nil(t, sut.GetResult(), "before start result is Nil") + + sut.Run(ctx, nil) + + require.Nil(t, sut.GetResult(), "after start result is Nil") + mockChecker.Wg.Wait() + result := sut.GetResultBlockingUntilAvailable(0) + require.NotNil(t, result) +} + +// RunSynchronous it returns the first result, doesnt mind if a reorg or not +func TestAsyncGRunSynchronousReturnTheFirstResult(t *testing.T) { + mockChecker := &mockChecker{ErrorsToReturn: []error{errGenericToTestAsync}, Wg: &sync.WaitGroup{}} + sut := l1_check_block.NewAsyncCheck(mockChecker) + sut.SetPeriodTime(0) + ctx, cancel := context.WithTimeout(context.Background(), timeoutContextForAsyncTests) + defer cancel() + mockChecker.Wg.Add(1) + + result := sut.RunSynchronous(ctx) + + require.NotNil(t, result) + require.Equal(t, uint64(0), result.BlockNumber) + require.Equal(t, false, result.ReorgDetected) + require.Equal(t, errGenericToTestAsync, result.Err) +} + +func TestAsyncGRunSynchronousDontAffectGetResult(t *testing.T) { + mockChecker := &mockChecker{ErrorsToReturn: []error{errGenericToTestAsync}, Wg: &sync.WaitGroup{}} + sut := l1_check_block.NewAsyncCheck(mockChecker) + sut.SetPeriodTime(0) + ctx, cancel := context.WithTimeout(context.Background(), timeoutContextForAsyncTests) + defer cancel() + mockChecker.Wg.Add(1) + + result := sut.RunSynchronous(ctx) + + require.NotNil(t, result) + require.Nil(t, sut.GetResult()) +} + +func TestAsyncStop(t *testing.T) { + mockChecker := &mockChecker{ErrorsToReturn: []error{nil, nil, errGenericToTestAsync, errReorgToTestAsync}, Wg: &sync.WaitGroup{}} + sut := l1_check_block.NewAsyncCheck(mockChecker) + sut.SetPeriodTime(0) + ctx, cancel := context.WithTimeout(context.Background(), timeoutContextForAsyncTests) + defer cancel() + require.Nil(t, sut.GetResult(), "before start result is Nil") + mockChecker.Wg.Add(4) + sut.Run(ctx, nil) + sut.Stop() + sut.Stop() + + result := sut.GetResultBlockingUntilAvailable(0) + require.NotNil(t, result) + mockChecker.Wg = &sync.WaitGroup{} + mockChecker.Wg.Add(4) + mockChecker.ErrorsToReturn = []error{nil, nil, errGenericToTestAsync, errReorgToTestAsync} + sut.Run(ctx, nil) + mockChecker.Wg.Wait() + result = sut.GetResultBlockingUntilAvailable(0) + require.NotNil(t, result) +} + +func TestAsyncMultipleRun(t *testing.T) { + mockChecker := &mockChecker{ErrorsToReturn: []error{nil, nil, errGenericToTestAsync, errReorgToTestAsync}, Wg: &sync.WaitGroup{}} + sut := l1_check_block.NewAsyncCheck(mockChecker) + sut.SetPeriodTime(0) + ctx, cancel := context.WithTimeout(context.Background(), timeoutContextForAsyncTests) + defer cancel() + require.Nil(t, sut.GetResult(), "before start result is Nil") + mockChecker.Wg.Add(4) + sut.Run(ctx, nil) + sut.Run(ctx, nil) + sut.Run(ctx, nil) + result := sut.GetResultBlockingUntilAvailable(0) + require.NotNil(t, result) +} diff --git a/synchronizer/l1_check_block/check_l1block.go b/synchronizer/l1_check_block/check_l1block.go new file mode 100644 index 0000000000..cd1204c5b3 --- /dev/null +++ b/synchronizer/l1_check_block/check_l1block.go @@ -0,0 +1,146 @@ +package l1_check_block + +import ( + "context" + "errors" + "fmt" + "math/big" + "time" + + "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/jackc/pgx/v4" +) + +// This object check old L1block to double-check that the L1block hash is correct +// - Get first not checked block +// - Get last block on L1 (safe/finalized/ or minus -n) + +// L1Requester is an interface for GETH client +type L1Requester interface { + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) +} + +// StateInterfacer is an interface for the state +type StateInterfacer interface { + GetFirstUncheckedBlock(ctx context.Context, fromBlockNumber uint64, dbTx pgx.Tx) (*state.Block, error) + UpdateCheckedBlockByNumber(ctx context.Context, blockNumber uint64, newCheckedStatus bool, dbTx pgx.Tx) error +} + +// SafeL1BlockNumberFetcher is an interface for fetching the L1 block number reference point (safe, finalized,...) +type SafeL1BlockNumberFetcher interface { + GetSafeBlockNumber(ctx context.Context, l1Client L1Requester) (uint64, error) + Description() string +} + +// CheckL1BlockHash is a struct that implements a checker of L1Block hash +type CheckL1BlockHash struct { + L1Client L1Requester + State StateInterfacer + SafeBlockNumberFetcher SafeL1BlockNumberFetcher +} + +// NewCheckL1BlockHash creates a new CheckL1BlockHash +func NewCheckL1BlockHash(l1Client L1Requester, state StateInterfacer, safeBlockNumberFetcher SafeL1BlockNumberFetcher) *CheckL1BlockHash { + return &CheckL1BlockHash{ + L1Client: l1Client, + State: state, + SafeBlockNumberFetcher: safeBlockNumberFetcher, + } +} + +// Name is a method that returns the name of the checker +func (p *CheckL1BlockHash) Name() string { + return logPrefix + " main_checker: " +} + +// Step is a method that checks the L1 block hash, run until all blocks are checked and returns +func (p *CheckL1BlockHash) Step(ctx context.Context) error { + stateBlock, err := p.State.GetFirstUncheckedBlock(ctx, uint64(0), nil) + if errors.Is(err, state.ErrNotFound) { + log.Debugf("%s: No unchecked blocks to check", p.Name()) + return nil + } + if err != nil { + return err + } + if stateBlock == nil { + log.Warnf("%s: function CheckL1Block receive a nil pointer", p.Name()) + return nil + } + safeBlockNumber, err := p.SafeBlockNumberFetcher.GetSafeBlockNumber(ctx, p.L1Client) + if err != nil { + return err + } + log.Debugf("%s: checking from block (%s) %d first block to check: %d....", p.Name(), p.SafeBlockNumberFetcher.Description(), safeBlockNumber, stateBlock.BlockNumber) + return p.doAllBlocks(ctx, *stateBlock, safeBlockNumber) +} + +func (p *CheckL1BlockHash) doAllBlocks(ctx context.Context, firstStateBlock state.Block, safeBlockNumber uint64) error { + var err error + startTime := time.Now() + stateBlock := &firstStateBlock + numBlocksChecked := 0 + for { + lastStateBlockNumber := stateBlock.BlockNumber + if stateBlock.BlockNumber > safeBlockNumber { + log.Debugf("%s: block %d to check is not still safe enough (%s) %d ", p.Name(), stateBlock.BlockNumber, p.SafeBlockNumberFetcher.Description(), safeBlockNumber, logPrefix) + return nil + } + err = p.doBlock(ctx, stateBlock) + if err != nil { + return err + } + numBlocksChecked++ + stateBlock, err = p.State.GetFirstUncheckedBlock(ctx, lastStateBlockNumber, nil) + if errors.Is(err, state.ErrNotFound) { + diff := time.Since(startTime) + log.Infof("%s: checked all blocks (%d) (using as safe Block Point(%s): %d) time:%s", p.Name(), numBlocksChecked, p.SafeBlockNumberFetcher.Description(), safeBlockNumber, diff) + return nil + } + } +} + +func (p *CheckL1BlockHash) doBlock(ctx context.Context, stateBlock *state.Block) error { + err := CheckBlockHash(ctx, stateBlock, p.L1Client, p.Name()) + if err != nil { + return err + } + log.Infof("%s: L1Block: %d hash: %s is correct marking as checked", p.Name(), stateBlock.BlockNumber, + stateBlock.BlockHash.String()) + err = p.State.UpdateCheckedBlockByNumber(ctx, stateBlock.BlockNumber, true, nil) + if err != nil { + log.Errorf("%s: Error updating block %d as checked. err: %s", p.Name(), stateBlock.BlockNumber, err.Error()) + return err + } + return nil +} + +// CheckBlockHash is a method that checks the L1 block hash +func CheckBlockHash(ctx context.Context, stateBlock *state.Block, L1Client L1Requester, checkerName string) error { + if stateBlock == nil { + log.Warn("%s function CheckL1Block receive a nil pointer", checkerName) + return nil + } + l1Block, err := L1Client.HeaderByNumber(ctx, big.NewInt(int64(stateBlock.BlockNumber))) + if err != nil { + return err + } + if l1Block == nil { + err = fmt.Errorf("%s request of block: %d to L1 returns a nil", checkerName, stateBlock.BlockNumber) + log.Error(err.Error()) + return err + } + if l1Block.Hash() != stateBlock.BlockHash { + msg := fmt.Sprintf("%s Reorg detected at block %d l1Block.Hash=%s != stateBlock.Hash=%s. ", checkerName, stateBlock.BlockNumber, + l1Block.Hash().String(), stateBlock.BlockHash.String()) + if l1Block.ParentHash != stateBlock.ParentHash { + msg += fmt.Sprintf(" ParentHash are also different. l1Block.ParentHash=%s != stateBlock.ParentHash=%s", l1Block.ParentHash.String(), stateBlock.ParentHash.String()) + } + log.Errorf(msg) + return common.NewReorgError(stateBlock.BlockNumber, fmt.Errorf(msg)) + } + return nil +} diff --git a/synchronizer/l1_check_block/check_l1block_test.go b/synchronizer/l1_check_block/check_l1block_test.go new file mode 100644 index 0000000000..e5090140a3 --- /dev/null +++ b/synchronizer/l1_check_block/check_l1block_test.go @@ -0,0 +1,128 @@ +package l1_check_block_test + +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/0xPolygonHermez/zkevm-node/state" + commonsync "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block" + mock_l1_check_block "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block/mocks" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +type testData struct { + mockL1Client *mock_l1_check_block.L1Requester + mockState *mock_l1_check_block.StateInterfacer + mockBlockNumberFetch *mock_l1_check_block.SafeL1BlockNumberFetcher + sut *l1_check_block.CheckL1BlockHash + ctx context.Context + stateBlock *state.Block +} + +func newTestData(t *testing.T) *testData { + mockL1Client := mock_l1_check_block.NewL1Requester(t) + mockState := mock_l1_check_block.NewStateInterfacer(t) + mockBlockNumberFetch := mock_l1_check_block.NewSafeL1BlockNumberFetcher(t) + mockBlockNumberFetch.EXPECT().Description().Return("mock").Maybe() + sut := l1_check_block.NewCheckL1BlockHash(mockL1Client, mockState, mockBlockNumberFetch) + require.NotNil(t, sut) + ctx := context.Background() + return &testData{ + mockL1Client: mockL1Client, + mockState: mockState, + mockBlockNumberFetch: mockBlockNumberFetch, + sut: sut, + ctx: ctx, + stateBlock: &state.Block{ + BlockNumber: 1234, + BlockHash: common.HexToHash("0xb07e1289b32edefd8f3c702d016fb73c81d5950b2ebc790ad9d2cb8219066b4c"), + }, + } +} + +func TestCheckL1BlockHashNoBlocksOnDB(t *testing.T) { + data := newTestData(t) + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, uint64(0), nil).Return(nil, state.ErrNotFound) + res := data.sut.Step(data.ctx) + require.NoError(t, res) +} + +func TestCheckL1BlockHashErrorGettingFirstUncheckedBlockFromDB(t *testing.T) { + data := newTestData(t) + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, uint64(0), nil).Return(nil, fmt.Errorf("error")) + res := data.sut.Step(data.ctx) + require.Error(t, res) +} + +func TestCheckL1BlockHashErrorGettingGetSafeBlockNumber(t *testing.T) { + data := newTestData(t) + + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, uint64(0), nil).Return(data.stateBlock, nil) + data.mockBlockNumberFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(0), fmt.Errorf("error")) + res := data.sut.Step(data.ctx) + require.Error(t, res) +} + +// The first block to check is below the safe point, nothing to do +func TestCheckL1BlockHashSafePointIsInFuture(t *testing.T) { + data := newTestData(t) + + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, uint64(0), nil).Return(data.stateBlock, nil) + data.mockBlockNumberFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(data.stateBlock.BlockNumber-1, nil) + + res := data.sut.Step(data.ctx) + require.NoError(t, res) +} + +func TestCheckL1BlockHashL1ClientReturnsANil(t *testing.T) { + data := newTestData(t) + + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, uint64(0), nil).Return(data.stateBlock, nil) + data.mockBlockNumberFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(data.stateBlock.BlockNumber+10, nil) + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlock.BlockNumber))).Return(nil, nil) + res := data.sut.Step(data.ctx) + require.Error(t, res) +} + +// Check a block that is OK +func TestCheckL1BlockHashMatchHashUpdateCheckMarkOnDB(t *testing.T) { + data := newTestData(t) + + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, uint64(0), nil).Return(data.stateBlock, nil) + data.mockBlockNumberFetch.EXPECT().Description().Return("mock") + data.mockBlockNumberFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(data.stateBlock.BlockNumber, nil) + l1Block := &types.Header{ + Number: big.NewInt(100), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlock.BlockNumber))).Return(l1Block, nil) + data.mockState.EXPECT().UpdateCheckedBlockByNumber(data.ctx, data.stateBlock.BlockNumber, true, nil).Return(nil) + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, mock.Anything, nil).Return(nil, state.ErrNotFound) + + res := data.sut.Step(data.ctx) + require.NoError(t, res) +} + +// The first block to check is equal to the safe point, must be processed +func TestCheckL1BlockHashMismatch(t *testing.T) { + data := newTestData(t) + + data.mockState.EXPECT().GetFirstUncheckedBlock(data.ctx, uint64(0), nil).Return(data.stateBlock, nil) + data.stateBlock.BlockHash = common.HexToHash("0x1234") // Wrong hash to trigger a mismatch + data.mockBlockNumberFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(data.stateBlock.BlockNumber, nil) + l1Block := &types.Header{ + Number: big.NewInt(100), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlock.BlockNumber))).Return(l1Block, nil) + + res := data.sut.Step(data.ctx) + require.Error(t, res) + resErr, ok := res.(*commonsync.ReorgError) + require.True(t, ok) + require.Equal(t, data.stateBlock.BlockNumber, resErr.BlockNumber) +} diff --git a/synchronizer/l1_check_block/common.go b/synchronizer/l1_check_block/common.go new file mode 100644 index 0000000000..a473c220a3 --- /dev/null +++ b/synchronizer/l1_check_block/common.go @@ -0,0 +1,5 @@ +package l1_check_block + +const ( + logPrefix = "checkL1block:" +) diff --git a/synchronizer/l1_check_block/integration.go b/synchronizer/l1_check_block/integration.go new file mode 100644 index 0000000000..82a962eb3f --- /dev/null +++ b/synchronizer/l1_check_block/integration.go @@ -0,0 +1,205 @@ +package l1_check_block + +import ( + "context" + "time" + + "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces" + "github.com/jackc/pgx/v4" +) + +// StateForL1BlockCheckerIntegration is an interface for the state +type StateForL1BlockCheckerIntegration interface { + GetPreviousBlockToBlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) +} + +// L1BlockCheckerIntegration is a struct that integrates the L1BlockChecker with the synchronizer +type L1BlockCheckerIntegration struct { + forceCheckOnStart bool + checker syncinterfaces.AsyncL1BlockChecker + preChecker syncinterfaces.AsyncL1BlockChecker + state StateForL1BlockCheckerIntegration + sync SyncCheckReorger + timeBetweenRetries time.Duration +} + +// SyncCheckReorger is an interface that defines the methods required from Synchronizer object +type SyncCheckReorger interface { + ExecuteReorgFromMismatchBlock(blockNumber uint64, reason string) error + OnDetectedMismatchL1BlockReorg() +} + +// NewL1BlockCheckerIntegration creates a new L1BlockCheckerIntegration +func NewL1BlockCheckerIntegration(checker syncinterfaces.AsyncL1BlockChecker, preChecker syncinterfaces.AsyncL1BlockChecker, state StateForL1BlockCheckerIntegration, sync SyncCheckReorger, forceCheckOnStart bool, timeBetweenRetries time.Duration) *L1BlockCheckerIntegration { + return &L1BlockCheckerIntegration{ + forceCheckOnStart: forceCheckOnStart, + checker: checker, + preChecker: preChecker, + state: state, + sync: sync, + timeBetweenRetries: timeBetweenRetries, + } +} + +// OnStart is a method that is called before starting the synchronizer +func (v *L1BlockCheckerIntegration) OnStart(ctx context.Context) error { + if v.forceCheckOnStart { + log.Infof("%s Forcing L1BlockChecker check before start", logPrefix) + result := v.runCheckerSync(ctx, v.checker) + if result.ReorgDetected { + v.executeResult(ctx, result) + } else { + log.Infof("%s Forcing L1BlockChecker check:OK ", logPrefix) + if v.preChecker != nil { + log.Infof("%s Forcing L1BlockChecker preCheck before start", logPrefix) + result = v.runCheckerSync(ctx, v.preChecker) + if result.ReorgDetected { + v.executeResult(ctx, result) + } else { + log.Infof("%s Forcing L1BlockChecker preCheck:OK", logPrefix) + } + } + } + } + v.launch(ctx) + return nil +} + +func (v *L1BlockCheckerIntegration) runCheckerSync(ctx context.Context, checker syncinterfaces.AsyncL1BlockChecker) syncinterfaces.IterationResult { + for { + result := checker.RunSynchronous(ctx) + if result.Err == nil { + return result + } else { + time.Sleep(v.timeBetweenRetries) + } + } +} + +// OnStartL1Sync is a method that is called before starting the L1 sync +func (v *L1BlockCheckerIntegration) OnStartL1Sync(ctx context.Context) bool { + return v.checkBackgroundResult(ctx, "before start L1 sync") +} + +// OnStartL2Sync is a method that is called before starting the L2 sync +func (v *L1BlockCheckerIntegration) OnStartL2Sync(ctx context.Context) bool { + return v.checkBackgroundResult(ctx, "before start 2 sync") +} + +// OnResetState is a method that is called after a resetState +func (v *L1BlockCheckerIntegration) OnResetState(ctx context.Context) { + log.Infof("%s L1BlockChecker: after a resetState relaunch background process", logPrefix) + v.launch(ctx) +} + +// CheckReorgWrapper is a wrapper over reorg function of synchronizer. +// it checks the result of the function and the result of background process and decides which return +func (v *L1BlockCheckerIntegration) CheckReorgWrapper(ctx context.Context, reorgFirstBlockOk *state.Block, errReportedByReorgFunc error) (*state.Block, error) { + resultBackground := v.getMergedResults() + if resultBackground != nil && resultBackground.ReorgDetected { + // Background process detected a reorg, decide which return + firstOkBlockBackgroundCheck, err := v.state.GetPreviousBlockToBlockNumber(ctx, resultBackground.BlockNumber, nil) + if err != nil { + log.Warnf("%s Error getting previous block to block number where a reorg have been detected %d: %s. So we reorgFunc values", logPrefix, resultBackground.BlockNumber, err) + return reorgFirstBlockOk, errReportedByReorgFunc + } + if reorgFirstBlockOk == nil || errReportedByReorgFunc != nil { + log.Infof("%s Background checker detects bad block at block %d (first block ok %d) and regular reorg function no. Returning it", logPrefix, + resultBackground.BlockNumber, firstOkBlockBackgroundCheck.BlockNumber) + return firstOkBlockBackgroundCheck, nil + } + if firstOkBlockBackgroundCheck.BlockNumber < reorgFirstBlockOk.BlockNumber { + // Background process detected a reorg at oldest block + log.Warnf("%s Background checker detects bad block at block %d (first block ok %d) and regular reorg function first block ok: %d. Returning from %d", + logPrefix, resultBackground.BlockNumber, firstOkBlockBackgroundCheck.BlockNumber, reorgFirstBlockOk.BlockNumber, firstOkBlockBackgroundCheck.BlockNumber) + return firstOkBlockBackgroundCheck, nil + } else { + // Regular reorg function detected a reorg at oldest block + log.Warnf("%s Background checker detects bad block at block %d (first block ok %d) and regular reorg function first block ok: %d. Executing from %d", + logPrefix, resultBackground.BlockNumber, firstOkBlockBackgroundCheck.BlockNumber, reorgFirstBlockOk.BlockNumber, reorgFirstBlockOk.BlockNumber) + return reorgFirstBlockOk, errReportedByReorgFunc + } + } + if resultBackground != nil && !resultBackground.ReorgDetected { + // Relaunch checker, if there is a reorg, It is going to be relaunched after (OnResetState) + v.launch(ctx) + } + // Background process doesnt have anything to we return the regular reorg function result + return reorgFirstBlockOk, errReportedByReorgFunc +} + +func (v *L1BlockCheckerIntegration) checkBackgroundResult(ctx context.Context, positionMessage string) bool { + log.Debugf("%s Checking L1BlockChecker %s", logPrefix, positionMessage) + result := v.getMergedResults() + if result != nil { + if result.ReorgDetected { + log.Warnf("%s Checking L1BlockChecker %s: reorg detected %s", logPrefix, positionMessage, result.String()) + v.executeResult(ctx, *result) + } + v.launch(ctx) + return result.ReorgDetected + } + return false +} + +func (v *L1BlockCheckerIntegration) getMergedResults() *syncinterfaces.IterationResult { + result := v.checker.GetResult() + var preResult *syncinterfaces.IterationResult + preResult = nil + if v.preChecker != nil { + preResult = v.preChecker.GetResult() + } + if preResult == nil { + return result + } + if result == nil { + return preResult + } + // result and preResult have values + if result.ReorgDetected && preResult.ReorgDetected { + // That is the common case, checker must detect oldest blocks than preChecker + if result.BlockNumber < preResult.BlockNumber { + return result + } + return preResult + } + if preResult.ReorgDetected { + return preResult + } + return result +} + +func (v *L1BlockCheckerIntegration) onFinishChecker() { + log.Infof("%s L1BlockChecker: finished background process, calling to synchronizer", logPrefix) + // Stop both processes + v.checker.Stop() + if v.preChecker != nil { + v.preChecker.Stop() + } + v.sync.OnDetectedMismatchL1BlockReorg() +} + +func (v *L1BlockCheckerIntegration) launch(ctx context.Context) { + log.Infof("%s L1BlockChecker: starting background process...", logPrefix) + v.checker.Run(ctx, v.onFinishChecker) + if v.preChecker != nil { + log.Infof("%s L1BlockChecker: starting background precheck process...", logPrefix) + v.preChecker.Run(ctx, v.onFinishChecker) + } +} + +func (v *L1BlockCheckerIntegration) executeResult(ctx context.Context, result syncinterfaces.IterationResult) bool { + if result.ReorgDetected { + for { + err := v.sync.ExecuteReorgFromMismatchBlock(result.BlockNumber, result.ReorgMessage) + if err == nil { + return true + } + log.Errorf("%s Error executing reorg: %s", logPrefix, err) + time.Sleep(v.timeBetweenRetries) + } + } + return false +} diff --git a/synchronizer/l1_check_block/integration_test.go b/synchronizer/l1_check_block/integration_test.go new file mode 100644 index 0000000000..de79c71351 --- /dev/null +++ b/synchronizer/l1_check_block/integration_test.go @@ -0,0 +1,298 @@ +package l1_check_block_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces" + mock_syncinterfaces "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces/mocks" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block" + mock_l1_check_block "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block/mocks" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +var ( + genericErrorToTest = fmt.Errorf("error") +) + +type testDataIntegration struct { + mockChecker *mock_syncinterfaces.AsyncL1BlockChecker + mockPreChecker *mock_syncinterfaces.AsyncL1BlockChecker + mockState *mock_l1_check_block.StateForL1BlockCheckerIntegration + mockSync *mock_l1_check_block.SyncCheckReorger + sut *l1_check_block.L1BlockCheckerIntegration + ctx context.Context + resultOk syncinterfaces.IterationResult + resultError syncinterfaces.IterationResult + resultReorg syncinterfaces.IterationResult +} + +func newDataIntegration(t *testing.T, forceCheckOnStart bool) *testDataIntegration { + return newDataIntegrationOnlyMainChecker(t, forceCheckOnStart) +} + +func newDataIntegrationWithPreChecker(t *testing.T, forceCheckOnStart bool) *testDataIntegration { + res := newDataIntegrationOnlyMainChecker(t, forceCheckOnStart) + res.mockPreChecker = mock_syncinterfaces.NewAsyncL1BlockChecker(t) + res.sut = l1_check_block.NewL1BlockCheckerIntegration(res.mockChecker, res.mockPreChecker, res.mockState, res.mockSync, forceCheckOnStart, time.Millisecond) + return res +} + +func newDataIntegrationOnlyMainChecker(t *testing.T, forceCheckOnStart bool) *testDataIntegration { + mockChecker := mock_syncinterfaces.NewAsyncL1BlockChecker(t) + mockSync := mock_l1_check_block.NewSyncCheckReorger(t) + mockState := mock_l1_check_block.NewStateForL1BlockCheckerIntegration(t) + sut := l1_check_block.NewL1BlockCheckerIntegration(mockChecker, nil, mockState, mockSync, forceCheckOnStart, time.Millisecond) + return &testDataIntegration{ + mockChecker: mockChecker, + mockPreChecker: nil, + mockSync: mockSync, + mockState: mockState, + sut: sut, + ctx: context.Background(), + resultReorg: syncinterfaces.IterationResult{ + ReorgDetected: true, + BlockNumber: 1234, + }, + resultOk: syncinterfaces.IterationResult{ + ReorgDetected: false, + }, + resultError: syncinterfaces.IterationResult{ + Err: genericErrorToTest, + ReorgDetected: false, + }, + } +} + +func TestIntegrationIfNoForceCheckOnlyLaunchBackgroudChecker(t *testing.T) { + data := newDataIntegration(t, false) + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +func TestIntegrationIfForceCheckRunsSynchronousOneTimeAndAfterLaunchBackgroudChecker(t *testing.T) { + data := newDataIntegration(t, true) + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultOk) + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +func TestIntegrationIfSyncCheckReturnsReorgExecuteIt(t *testing.T) { + data := newDataIntegration(t, true) + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultReorg) + data.mockSync.EXPECT().ExecuteReorgFromMismatchBlock(uint64(1234), "").Return(nil) + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +func TestIntegrationIfSyncCheckReturnErrorRetry(t *testing.T) { + data := newDataIntegration(t, true) + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultError).Once() + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultOk).Once() + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +func TestIntegrationIfSyncCheckReturnsReorgExecuteItAndFailsRetry(t *testing.T) { + data := newDataIntegration(t, true) + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultReorg) + data.mockSync.EXPECT().ExecuteReorgFromMismatchBlock(uint64(1234), mock.Anything).Return(genericErrorToTest).Once() + data.mockSync.EXPECT().ExecuteReorgFromMismatchBlock(uint64(1234), mock.Anything).Return(nil).Once() + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +// OnStart if check and preCheck execute both, and launch both in background +func TestIntegrationCheckAndPreCheckOnStartForceCheck(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultOk) + data.mockPreChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultOk) + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + data.mockPreChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +// OnStart if mainChecker returns reorg doesnt need to run preCheck +func TestIntegrationCheckAndPreCheckOnStartMainCheckerReturnReorg(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultReorg) + data.mockSync.EXPECT().ExecuteReorgFromMismatchBlock(uint64(1234), mock.Anything).Return(nil).Once() + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + data.mockPreChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +// If mainCheck is OK, but preCheck returns reorg, it should execute reorg +func TestIntegrationCheckAndPreCheckOnStartPreCheckerReturnReorg(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultOk) + data.mockPreChecker.EXPECT().RunSynchronous(data.ctx).Return(data.resultReorg) + data.mockSync.EXPECT().ExecuteReorgFromMismatchBlock(uint64(1234), mock.Anything).Return(nil).Once() + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + data.mockPreChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + err := data.sut.OnStart(data.ctx) + require.NoError(t, err) +} + +// The process is running on background, no results yet +func TestIntegrationCheckAndPreCheckOnOnCheckReorgRunningOnBackground(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(nil) + data.mockPreChecker.EXPECT().GetResult().Return(nil) + block, err := data.sut.CheckReorgWrapper(data.ctx, nil, nil) + require.Nil(t, block) + require.NoError(t, err) +} + +func TestIntegrationCheckAndPreCheckOnOnCheckReorgOneProcessHaveResultOK(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(&data.resultOk) + data.mockPreChecker.EXPECT().GetResult().Return(nil) + // One have been stopped, so must relaunch both + data.mockChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + data.mockPreChecker.EXPECT().Run(data.ctx, mock.Anything).Return() + block, err := data.sut.CheckReorgWrapper(data.ctx, nil, nil) + require.Nil(t, block) + require.NoError(t, err) +} + +func TestIntegrationCheckAndPreCheckOnOnCheckReorgMainCheckerReorg(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(&data.resultReorg) + data.mockPreChecker.EXPECT().GetResult().Return(nil) + data.mockState.EXPECT().GetPreviousBlockToBlockNumber(data.ctx, uint64(1234), nil).Return(&state.Block{ + BlockNumber: data.resultReorg.BlockNumber - 1, + }, nil) + // One have been stopped,but is going to be launched OnResetState call after the reset + block, err := data.sut.CheckReorgWrapper(data.ctx, nil, nil) + require.NotNil(t, block) + require.Equal(t, data.resultReorg.BlockNumber-1, block.BlockNumber) + require.NoError(t, err) +} + +func TestIntegrationCheckAndPreCheckOnOnCheckReorgPreCheckerReorg(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(nil) + data.mockPreChecker.EXPECT().GetResult().Return(&data.resultReorg) + data.mockState.EXPECT().GetPreviousBlockToBlockNumber(data.ctx, uint64(1234), nil).Return(&state.Block{ + BlockNumber: data.resultReorg.BlockNumber - 1, + }, nil) + // One have been stopped,but is going to be launched OnResetState call after the reset + + block, err := data.sut.CheckReorgWrapper(data.ctx, nil, nil) + require.NotNil(t, block) + require.Equal(t, data.resultReorg.BlockNumber-1, block.BlockNumber) + require.NoError(t, err) +} + +func TestIntegrationCheckAndPreCheckOnOnCheckReorgBothReorgWinOldest1(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + reorgMain := data.resultReorg + reorgMain.BlockNumber = 1235 + data.mockChecker.EXPECT().GetResult().Return(&reorgMain) + reorgPre := data.resultReorg + reorgPre.BlockNumber = 1236 + data.mockPreChecker.EXPECT().GetResult().Return(&reorgPre) + data.mockState.EXPECT().GetPreviousBlockToBlockNumber(data.ctx, uint64(1235), nil).Return(&state.Block{ + BlockNumber: 1234, + }, nil) + + // Both have been stopped,but is going to be launched OnResetState call after the reset + + block, err := data.sut.CheckReorgWrapper(data.ctx, nil, nil) + require.NotNil(t, block) + require.Equal(t, uint64(1234), block.BlockNumber) + require.NoError(t, err) +} + +func TestIntegrationCheckAndPreCheckOnOnCheckReorgBothReorgWinOldest2(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + reorgMain := data.resultReorg + reorgMain.BlockNumber = 1236 + data.mockChecker.EXPECT().GetResult().Return(&reorgMain) + reorgPre := data.resultReorg + reorgPre.BlockNumber = 1235 + data.mockPreChecker.EXPECT().GetResult().Return(&reorgPre) + data.mockState.EXPECT().GetPreviousBlockToBlockNumber(data.ctx, uint64(1235), nil).Return(&state.Block{ + BlockNumber: 1234, + }, nil) + // Both have been stopped,but is going to be launched OnResetState call after the reset + + block, err := data.sut.CheckReorgWrapper(data.ctx, nil, nil) + require.NotNil(t, block) + require.Equal(t, uint64(1234), block.BlockNumber) + require.NoError(t, err) +} + +func TestIntegrationCheckReorgWrapperBypassReorgFuncIfNoBackgroundData(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(nil) + data.mockPreChecker.EXPECT().GetResult().Return(nil) + reorgFuncBlock := &state.Block{ + BlockNumber: 1234, + } + reorgFuncErr := fmt.Errorf("error") + block, err := data.sut.CheckReorgWrapper(data.ctx, reorgFuncBlock, reorgFuncErr) + require.Equal(t, reorgFuncBlock, block) + require.Equal(t, reorgFuncErr, err) +} + +func TestIntegrationCheckReorgWrapperChooseOldestReorgFunc(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(nil) + data.mockPreChecker.EXPECT().GetResult().Return(&data.resultReorg) + data.mockState.EXPECT().GetPreviousBlockToBlockNumber(data.ctx, uint64(1234), nil).Return(&state.Block{ + BlockNumber: 1233, + }, nil) + + reorgFuncBlock := &state.Block{ + BlockNumber: 1230, + } + block, err := data.sut.CheckReorgWrapper(data.ctx, reorgFuncBlock, nil) + require.Equal(t, reorgFuncBlock, block) + require.NoError(t, err) +} + +func TestIntegrationCheckReorgWrapperChooseOldestBackgroundCheck(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(nil) + data.mockPreChecker.EXPECT().GetResult().Return(&data.resultReorg) + data.mockState.EXPECT().GetPreviousBlockToBlockNumber(data.ctx, uint64(1234), nil).Return(&state.Block{ + BlockNumber: 1233, + }, nil) + + reorgFuncBlock := &state.Block{ + BlockNumber: 1240, + } + block, err := data.sut.CheckReorgWrapper(data.ctx, reorgFuncBlock, nil) + require.Equal(t, uint64(1233), block.BlockNumber) + require.NoError(t, err) +} + +func TestIntegrationCheckReorgWrapperIgnoreReorgFuncIfError(t *testing.T) { + data := newDataIntegrationWithPreChecker(t, true) + data.mockChecker.EXPECT().GetResult().Return(nil) + data.mockPreChecker.EXPECT().GetResult().Return(&data.resultReorg) + data.mockState.EXPECT().GetPreviousBlockToBlockNumber(data.ctx, uint64(1234), nil).Return(&state.Block{ + BlockNumber: 1233, + }, nil) + + reorgFuncBlock := &state.Block{ + BlockNumber: 1230, + } + reorgFuncErr := fmt.Errorf("error") + block, err := data.sut.CheckReorgWrapper(data.ctx, reorgFuncBlock, reorgFuncErr) + require.Equal(t, uint64(1233), block.BlockNumber) + require.NoError(t, err) +} diff --git a/synchronizer/l1_check_block/mocks/l1_block_checker.go b/synchronizer/l1_check_block/mocks/l1_block_checker.go new file mode 100644 index 0000000000..6f0eab9acb --- /dev/null +++ b/synchronizer/l1_check_block/mocks/l1_block_checker.go @@ -0,0 +1,82 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_l1_check_block + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// L1BlockChecker is an autogenerated mock type for the L1BlockChecker type +type L1BlockChecker struct { + mock.Mock +} + +type L1BlockChecker_Expecter struct { + mock *mock.Mock +} + +func (_m *L1BlockChecker) EXPECT() *L1BlockChecker_Expecter { + return &L1BlockChecker_Expecter{mock: &_m.Mock} +} + +// Step provides a mock function with given fields: ctx +func (_m *L1BlockChecker) Step(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Step") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// L1BlockChecker_Step_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Step' +type L1BlockChecker_Step_Call struct { + *mock.Call +} + +// Step is a helper method to define mock.On call +// - ctx context.Context +func (_e *L1BlockChecker_Expecter) Step(ctx interface{}) *L1BlockChecker_Step_Call { + return &L1BlockChecker_Step_Call{Call: _e.mock.On("Step", ctx)} +} + +func (_c *L1BlockChecker_Step_Call) Run(run func(ctx context.Context)) *L1BlockChecker_Step_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L1BlockChecker_Step_Call) Return(_a0 error) *L1BlockChecker_Step_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L1BlockChecker_Step_Call) RunAndReturn(run func(context.Context) error) *L1BlockChecker_Step_Call { + _c.Call.Return(run) + return _c +} + +// NewL1BlockChecker creates a new instance of L1BlockChecker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewL1BlockChecker(t interface { + mock.TestingT + Cleanup(func()) +}) *L1BlockChecker { + mock := &L1BlockChecker{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/l1_check_block/mocks/l1_requester.go b/synchronizer/l1_check_block/mocks/l1_requester.go new file mode 100644 index 0000000000..713cc4a5ef --- /dev/null +++ b/synchronizer/l1_check_block/mocks/l1_requester.go @@ -0,0 +1,98 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_l1_check_block + +import ( + context "context" + big "math/big" + + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// L1Requester is an autogenerated mock type for the L1Requester type +type L1Requester struct { + mock.Mock +} + +type L1Requester_Expecter struct { + mock *mock.Mock +} + +func (_m *L1Requester) EXPECT() *L1Requester_Expecter { + return &L1Requester_Expecter{mock: &_m.Mock} +} + +// HeaderByNumber provides a mock function with given fields: ctx, number +func (_m *L1Requester) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for HeaderByNumber") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Header, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Header); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1Requester_HeaderByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByNumber' +type L1Requester_HeaderByNumber_Call struct { + *mock.Call +} + +// HeaderByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *L1Requester_Expecter) HeaderByNumber(ctx interface{}, number interface{}) *L1Requester_HeaderByNumber_Call { + return &L1Requester_HeaderByNumber_Call{Call: _e.mock.On("HeaderByNumber", ctx, number)} +} + +func (_c *L1Requester_HeaderByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *L1Requester_HeaderByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *L1Requester_HeaderByNumber_Call) Return(_a0 *types.Header, _a1 error) *L1Requester_HeaderByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1Requester_HeaderByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Header, error)) *L1Requester_HeaderByNumber_Call { + _c.Call.Return(run) + return _c +} + +// NewL1Requester creates a new instance of L1Requester. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewL1Requester(t interface { + mock.TestingT + Cleanup(func()) +}) *L1Requester { + mock := &L1Requester{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/l1_check_block/mocks/safe_l1_block_number_fetcher.go b/synchronizer/l1_check_block/mocks/safe_l1_block_number_fetcher.go new file mode 100644 index 0000000000..abb043afb4 --- /dev/null +++ b/synchronizer/l1_check_block/mocks/safe_l1_block_number_fetcher.go @@ -0,0 +1,139 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_l1_check_block + +import ( + context "context" + + l1_check_block "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block" + mock "github.com/stretchr/testify/mock" +) + +// SafeL1BlockNumberFetcher is an autogenerated mock type for the SafeL1BlockNumberFetcher type +type SafeL1BlockNumberFetcher struct { + mock.Mock +} + +type SafeL1BlockNumberFetcher_Expecter struct { + mock *mock.Mock +} + +func (_m *SafeL1BlockNumberFetcher) EXPECT() *SafeL1BlockNumberFetcher_Expecter { + return &SafeL1BlockNumberFetcher_Expecter{mock: &_m.Mock} +} + +// Description provides a mock function with given fields: +func (_m *SafeL1BlockNumberFetcher) Description() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Description") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// SafeL1BlockNumberFetcher_Description_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Description' +type SafeL1BlockNumberFetcher_Description_Call struct { + *mock.Call +} + +// Description is a helper method to define mock.On call +func (_e *SafeL1BlockNumberFetcher_Expecter) Description() *SafeL1BlockNumberFetcher_Description_Call { + return &SafeL1BlockNumberFetcher_Description_Call{Call: _e.mock.On("Description")} +} + +func (_c *SafeL1BlockNumberFetcher_Description_Call) Run(run func()) *SafeL1BlockNumberFetcher_Description_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SafeL1BlockNumberFetcher_Description_Call) Return(_a0 string) *SafeL1BlockNumberFetcher_Description_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SafeL1BlockNumberFetcher_Description_Call) RunAndReturn(run func() string) *SafeL1BlockNumberFetcher_Description_Call { + _c.Call.Return(run) + return _c +} + +// GetSafeBlockNumber provides a mock function with given fields: ctx, l1Client +func (_m *SafeL1BlockNumberFetcher) GetSafeBlockNumber(ctx context.Context, l1Client l1_check_block.L1Requester) (uint64, error) { + ret := _m.Called(ctx, l1Client) + + if len(ret) == 0 { + panic("no return value specified for GetSafeBlockNumber") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, l1_check_block.L1Requester) (uint64, error)); ok { + return rf(ctx, l1Client) + } + if rf, ok := ret.Get(0).(func(context.Context, l1_check_block.L1Requester) uint64); ok { + r0 = rf(ctx, l1Client) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, l1_check_block.L1Requester) error); ok { + r1 = rf(ctx, l1Client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSafeBlockNumber' +type SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call struct { + *mock.Call +} + +// GetSafeBlockNumber is a helper method to define mock.On call +// - ctx context.Context +// - l1Client l1_check_block.L1Requester +func (_e *SafeL1BlockNumberFetcher_Expecter) GetSafeBlockNumber(ctx interface{}, l1Client interface{}) *SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call { + return &SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call{Call: _e.mock.On("GetSafeBlockNumber", ctx, l1Client)} +} + +func (_c *SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call) Run(run func(ctx context.Context, l1Client l1_check_block.L1Requester)) *SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(l1_check_block.L1Requester)) + }) + return _c +} + +func (_c *SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call) Return(_a0 uint64, _a1 error) *SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call) RunAndReturn(run func(context.Context, l1_check_block.L1Requester) (uint64, error)) *SafeL1BlockNumberFetcher_GetSafeBlockNumber_Call { + _c.Call.Return(run) + return _c +} + +// NewSafeL1BlockNumberFetcher creates a new instance of SafeL1BlockNumberFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSafeL1BlockNumberFetcher(t interface { + mock.TestingT + Cleanup(func()) +}) *SafeL1BlockNumberFetcher { + mock := &SafeL1BlockNumberFetcher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/l1_check_block/mocks/state_for_l1_block_checker_integration.go b/synchronizer/l1_check_block/mocks/state_for_l1_block_checker_integration.go new file mode 100644 index 0000000000..32fbb30b86 --- /dev/null +++ b/synchronizer/l1_check_block/mocks/state_for_l1_block_checker_integration.go @@ -0,0 +1,100 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_l1_check_block + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + pgx "github.com/jackc/pgx/v4" + + state "github.com/0xPolygonHermez/zkevm-node/state" +) + +// StateForL1BlockCheckerIntegration is an autogenerated mock type for the StateForL1BlockCheckerIntegration type +type StateForL1BlockCheckerIntegration struct { + mock.Mock +} + +type StateForL1BlockCheckerIntegration_Expecter struct { + mock *mock.Mock +} + +func (_m *StateForL1BlockCheckerIntegration) EXPECT() *StateForL1BlockCheckerIntegration_Expecter { + return &StateForL1BlockCheckerIntegration_Expecter{mock: &_m.Mock} +} + +// GetPreviousBlockToBlockNumber provides a mock function with given fields: ctx, blockNumber, dbTx +func (_m *StateForL1BlockCheckerIntegration) GetPreviousBlockToBlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.Block, error) { + ret := _m.Called(ctx, blockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetPreviousBlockToBlockNumber") + } + + var r0 *state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (*state.Block, error)); ok { + return rf(ctx, blockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.Block); ok { + r0 = rf(ctx, blockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, blockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviousBlockToBlockNumber' +type StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call struct { + *mock.Call +} + +// GetPreviousBlockToBlockNumber is a helper method to define mock.On call +// - ctx context.Context +// - blockNumber uint64 +// - dbTx pgx.Tx +func (_e *StateForL1BlockCheckerIntegration_Expecter) GetPreviousBlockToBlockNumber(ctx interface{}, blockNumber interface{}, dbTx interface{}) *StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call { + return &StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call{Call: _e.mock.On("GetPreviousBlockToBlockNumber", ctx, blockNumber, dbTx)} +} + +func (_c *StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call) Run(run func(ctx context.Context, blockNumber uint64, dbTx pgx.Tx)) *StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(pgx.Tx)) + }) + return _c +} + +func (_c *StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call) Return(_a0 *state.Block, _a1 error) *StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call) RunAndReturn(run func(context.Context, uint64, pgx.Tx) (*state.Block, error)) *StateForL1BlockCheckerIntegration_GetPreviousBlockToBlockNumber_Call { + _c.Call.Return(run) + return _c +} + +// NewStateForL1BlockCheckerIntegration creates a new instance of StateForL1BlockCheckerIntegration. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStateForL1BlockCheckerIntegration(t interface { + mock.TestingT + Cleanup(func()) +}) *StateForL1BlockCheckerIntegration { + mock := &StateForL1BlockCheckerIntegration{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/l1_check_block/mocks/state_interfacer.go b/synchronizer/l1_check_block/mocks/state_interfacer.go new file mode 100644 index 0000000000..4855ba5eb1 --- /dev/null +++ b/synchronizer/l1_check_block/mocks/state_interfacer.go @@ -0,0 +1,149 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_l1_check_block + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + pgx "github.com/jackc/pgx/v4" + + state "github.com/0xPolygonHermez/zkevm-node/state" +) + +// StateInterfacer is an autogenerated mock type for the StateInterfacer type +type StateInterfacer struct { + mock.Mock +} + +type StateInterfacer_Expecter struct { + mock *mock.Mock +} + +func (_m *StateInterfacer) EXPECT() *StateInterfacer_Expecter { + return &StateInterfacer_Expecter{mock: &_m.Mock} +} + +// GetFirstUncheckedBlock provides a mock function with given fields: ctx, fromBlockNumber, dbTx +func (_m *StateInterfacer) GetFirstUncheckedBlock(ctx context.Context, fromBlockNumber uint64, dbTx pgx.Tx) (*state.Block, error) { + ret := _m.Called(ctx, fromBlockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetFirstUncheckedBlock") + } + + var r0 *state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (*state.Block, error)); ok { + return rf(ctx, fromBlockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.Block); ok { + r0 = rf(ctx, fromBlockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, fromBlockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StateInterfacer_GetFirstUncheckedBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFirstUncheckedBlock' +type StateInterfacer_GetFirstUncheckedBlock_Call struct { + *mock.Call +} + +// GetFirstUncheckedBlock is a helper method to define mock.On call +// - ctx context.Context +// - fromBlockNumber uint64 +// - dbTx pgx.Tx +func (_e *StateInterfacer_Expecter) GetFirstUncheckedBlock(ctx interface{}, fromBlockNumber interface{}, dbTx interface{}) *StateInterfacer_GetFirstUncheckedBlock_Call { + return &StateInterfacer_GetFirstUncheckedBlock_Call{Call: _e.mock.On("GetFirstUncheckedBlock", ctx, fromBlockNumber, dbTx)} +} + +func (_c *StateInterfacer_GetFirstUncheckedBlock_Call) Run(run func(ctx context.Context, fromBlockNumber uint64, dbTx pgx.Tx)) *StateInterfacer_GetFirstUncheckedBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(pgx.Tx)) + }) + return _c +} + +func (_c *StateInterfacer_GetFirstUncheckedBlock_Call) Return(_a0 *state.Block, _a1 error) *StateInterfacer_GetFirstUncheckedBlock_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StateInterfacer_GetFirstUncheckedBlock_Call) RunAndReturn(run func(context.Context, uint64, pgx.Tx) (*state.Block, error)) *StateInterfacer_GetFirstUncheckedBlock_Call { + _c.Call.Return(run) + return _c +} + +// UpdateCheckedBlockByNumber provides a mock function with given fields: ctx, blockNumber, newCheckedStatus, dbTx +func (_m *StateInterfacer) UpdateCheckedBlockByNumber(ctx context.Context, blockNumber uint64, newCheckedStatus bool, dbTx pgx.Tx) error { + ret := _m.Called(ctx, blockNumber, newCheckedStatus, dbTx) + + if len(ret) == 0 { + panic("no return value specified for UpdateCheckedBlockByNumber") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, bool, pgx.Tx) error); ok { + r0 = rf(ctx, blockNumber, newCheckedStatus, dbTx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// StateInterfacer_UpdateCheckedBlockByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCheckedBlockByNumber' +type StateInterfacer_UpdateCheckedBlockByNumber_Call struct { + *mock.Call +} + +// UpdateCheckedBlockByNumber is a helper method to define mock.On call +// - ctx context.Context +// - blockNumber uint64 +// - newCheckedStatus bool +// - dbTx pgx.Tx +func (_e *StateInterfacer_Expecter) UpdateCheckedBlockByNumber(ctx interface{}, blockNumber interface{}, newCheckedStatus interface{}, dbTx interface{}) *StateInterfacer_UpdateCheckedBlockByNumber_Call { + return &StateInterfacer_UpdateCheckedBlockByNumber_Call{Call: _e.mock.On("UpdateCheckedBlockByNumber", ctx, blockNumber, newCheckedStatus, dbTx)} +} + +func (_c *StateInterfacer_UpdateCheckedBlockByNumber_Call) Run(run func(ctx context.Context, blockNumber uint64, newCheckedStatus bool, dbTx pgx.Tx)) *StateInterfacer_UpdateCheckedBlockByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(bool), args[3].(pgx.Tx)) + }) + return _c +} + +func (_c *StateInterfacer_UpdateCheckedBlockByNumber_Call) Return(_a0 error) *StateInterfacer_UpdateCheckedBlockByNumber_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *StateInterfacer_UpdateCheckedBlockByNumber_Call) RunAndReturn(run func(context.Context, uint64, bool, pgx.Tx) error) *StateInterfacer_UpdateCheckedBlockByNumber_Call { + _c.Call.Return(run) + return _c +} + +// NewStateInterfacer creates a new instance of StateInterfacer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStateInterfacer(t interface { + mock.TestingT + Cleanup(func()) +}) *StateInterfacer { + mock := &StateInterfacer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/l1_check_block/mocks/state_pre_check_interfacer.go b/synchronizer/l1_check_block/mocks/state_pre_check_interfacer.go new file mode 100644 index 0000000000..2bf5522f60 --- /dev/null +++ b/synchronizer/l1_check_block/mocks/state_pre_check_interfacer.go @@ -0,0 +1,101 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_l1_check_block + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + pgx "github.com/jackc/pgx/v4" + + state "github.com/0xPolygonHermez/zkevm-node/state" +) + +// StatePreCheckInterfacer is an autogenerated mock type for the StatePreCheckInterfacer type +type StatePreCheckInterfacer struct { + mock.Mock +} + +type StatePreCheckInterfacer_Expecter struct { + mock *mock.Mock +} + +func (_m *StatePreCheckInterfacer) EXPECT() *StatePreCheckInterfacer_Expecter { + return &StatePreCheckInterfacer_Expecter{mock: &_m.Mock} +} + +// GetUncheckedBlocks provides a mock function with given fields: ctx, fromBlockNumber, toBlockNumber, dbTx +func (_m *StatePreCheckInterfacer) GetUncheckedBlocks(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx) ([]*state.Block, error) { + ret := _m.Called(ctx, fromBlockNumber, toBlockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetUncheckedBlocks") + } + + var r0 []*state.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, pgx.Tx) ([]*state.Block, error)); ok { + return rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, pgx.Tx) []*state.Block); ok { + r0 = rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*state.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, fromBlockNumber, toBlockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StatePreCheckInterfacer_GetUncheckedBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUncheckedBlocks' +type StatePreCheckInterfacer_GetUncheckedBlocks_Call struct { + *mock.Call +} + +// GetUncheckedBlocks is a helper method to define mock.On call +// - ctx context.Context +// - fromBlockNumber uint64 +// - toBlockNumber uint64 +// - dbTx pgx.Tx +func (_e *StatePreCheckInterfacer_Expecter) GetUncheckedBlocks(ctx interface{}, fromBlockNumber interface{}, toBlockNumber interface{}, dbTx interface{}) *StatePreCheckInterfacer_GetUncheckedBlocks_Call { + return &StatePreCheckInterfacer_GetUncheckedBlocks_Call{Call: _e.mock.On("GetUncheckedBlocks", ctx, fromBlockNumber, toBlockNumber, dbTx)} +} + +func (_c *StatePreCheckInterfacer_GetUncheckedBlocks_Call) Run(run func(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx)) *StatePreCheckInterfacer_GetUncheckedBlocks_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64), args[3].(pgx.Tx)) + }) + return _c +} + +func (_c *StatePreCheckInterfacer_GetUncheckedBlocks_Call) Return(_a0 []*state.Block, _a1 error) *StatePreCheckInterfacer_GetUncheckedBlocks_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StatePreCheckInterfacer_GetUncheckedBlocks_Call) RunAndReturn(run func(context.Context, uint64, uint64, pgx.Tx) ([]*state.Block, error)) *StatePreCheckInterfacer_GetUncheckedBlocks_Call { + _c.Call.Return(run) + return _c +} + +// NewStatePreCheckInterfacer creates a new instance of StatePreCheckInterfacer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStatePreCheckInterfacer(t interface { + mock.TestingT + Cleanup(func()) +}) *StatePreCheckInterfacer { + mock := &StatePreCheckInterfacer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/l1_check_block/mocks/sync_check_reorger.go b/synchronizer/l1_check_block/mocks/sync_check_reorger.go new file mode 100644 index 0000000000..bffd02cb87 --- /dev/null +++ b/synchronizer/l1_check_block/mocks/sync_check_reorger.go @@ -0,0 +1,111 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_l1_check_block + +import mock "github.com/stretchr/testify/mock" + +// SyncCheckReorger is an autogenerated mock type for the SyncCheckReorger type +type SyncCheckReorger struct { + mock.Mock +} + +type SyncCheckReorger_Expecter struct { + mock *mock.Mock +} + +func (_m *SyncCheckReorger) EXPECT() *SyncCheckReorger_Expecter { + return &SyncCheckReorger_Expecter{mock: &_m.Mock} +} + +// ExecuteReorgFromMismatchBlock provides a mock function with given fields: blockNumber, reason +func (_m *SyncCheckReorger) ExecuteReorgFromMismatchBlock(blockNumber uint64, reason string) error { + ret := _m.Called(blockNumber, reason) + + if len(ret) == 0 { + panic("no return value specified for ExecuteReorgFromMismatchBlock") + } + + var r0 error + if rf, ok := ret.Get(0).(func(uint64, string) error); ok { + r0 = rf(blockNumber, reason) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecuteReorgFromMismatchBlock' +type SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call struct { + *mock.Call +} + +// ExecuteReorgFromMismatchBlock is a helper method to define mock.On call +// - blockNumber uint64 +// - reason string +func (_e *SyncCheckReorger_Expecter) ExecuteReorgFromMismatchBlock(blockNumber interface{}, reason interface{}) *SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call { + return &SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call{Call: _e.mock.On("ExecuteReorgFromMismatchBlock", blockNumber, reason)} +} + +func (_c *SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call) Run(run func(blockNumber uint64, reason string)) *SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64), args[1].(string)) + }) + return _c +} + +func (_c *SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call) Return(_a0 error) *SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call) RunAndReturn(run func(uint64, string) error) *SyncCheckReorger_ExecuteReorgFromMismatchBlock_Call { + _c.Call.Return(run) + return _c +} + +// OnDetectedMismatchL1BlockReorg provides a mock function with given fields: +func (_m *SyncCheckReorger) OnDetectedMismatchL1BlockReorg() { + _m.Called() +} + +// SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnDetectedMismatchL1BlockReorg' +type SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call struct { + *mock.Call +} + +// OnDetectedMismatchL1BlockReorg is a helper method to define mock.On call +func (_e *SyncCheckReorger_Expecter) OnDetectedMismatchL1BlockReorg() *SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call { + return &SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call{Call: _e.mock.On("OnDetectedMismatchL1BlockReorg")} +} + +func (_c *SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call) Run(run func()) *SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call) Return() *SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call { + _c.Call.Return() + return _c +} + +func (_c *SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call) RunAndReturn(run func()) *SyncCheckReorger_OnDetectedMismatchL1BlockReorg_Call { + _c.Call.Return(run) + return _c +} + +// NewSyncCheckReorger creates a new instance of SyncCheckReorger. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSyncCheckReorger(t interface { + mock.TestingT + Cleanup(func()) +}) *SyncCheckReorger { + mock := &SyncCheckReorger{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/synchronizer/l1_check_block/pre_check_l1block.go b/synchronizer/l1_check_block/pre_check_l1block.go new file mode 100644 index 0000000000..431777f705 --- /dev/null +++ b/synchronizer/l1_check_block/pre_check_l1block.go @@ -0,0 +1,139 @@ +package l1_check_block + +// This make a pre-check of blocks but don't mark them as checked +// It checks blocks between a segment: example: +// real check point SAFE: +// pre check: (SAFE+1) -> (LATEST-32) +// It gets all pending blocks +// - Start cheking + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" + "github.com/jackc/pgx/v4" +) + +var ( + // ErrDeSync is an error that indicates that from the starting of verification to end something have been changed on state + ErrDeSync = errors.New("DeSync: a block hash is different from the state block hash") +) + +// StatePreCheckInterfacer is an interface for the state +type StatePreCheckInterfacer interface { + GetUncheckedBlocks(ctx context.Context, fromBlockNumber uint64, toBlockNumber uint64, dbTx pgx.Tx) ([]*state.Block, error) +} + +// PreCheckL1BlockHash is a struct that implements a checker of L1Block hash +type PreCheckL1BlockHash struct { + L1Client L1Requester + State StatePreCheckInterfacer + InitialSegmentBlockNumber SafeL1BlockNumberFetcher + EndSegmentBlockNumber SafeL1BlockNumberFetcher +} + +// NewPreCheckL1BlockHash creates a new CheckL1BlockHash +func NewPreCheckL1BlockHash(l1Client L1Requester, state StatePreCheckInterfacer, + initial, end SafeL1BlockNumberFetcher) *PreCheckL1BlockHash { + return &PreCheckL1BlockHash{ + L1Client: l1Client, + State: state, + InitialSegmentBlockNumber: initial, + EndSegmentBlockNumber: end, + } +} + +// Name is a method that returns the name of the checker +func (p *PreCheckL1BlockHash) Name() string { + return logPrefix + ":memory_check: " +} + +// Step is a method that checks the L1 block hash, run until all blocks are checked and returns +func (p *PreCheckL1BlockHash) Step(ctx context.Context) error { + from, err := p.InitialSegmentBlockNumber.GetSafeBlockNumber(ctx, p.L1Client) + if err != nil { + return err + } + to, err := p.EndSegmentBlockNumber.GetSafeBlockNumber(ctx, p.L1Client) + if err != nil { + return err + } + if from > to { + log.Warnf("%s: fromBlockNumber(%s) %d is greater than toBlockNumber(%s) %d, Check configuration", p.Name(), p.InitialSegmentBlockNumber.Description(), from, p.EndSegmentBlockNumber.Description(), to) + return nil + } + + blocksToCheck, err := p.State.GetUncheckedBlocks(ctx, from, to, nil) + if err != nil { + log.Warnf("%s can't get unchecked blocks, so it discard the reorg error", p.Name()) + return err + } + msg := fmt.Sprintf("%s: Checking blocks from (%s) %d to (%s) %d -> len(blocks)=%d", p.Name(), p.InitialSegmentBlockNumber.Description(), from, p.EndSegmentBlockNumber.Description(), to, len(blocksToCheck)) + if len(blocksToCheck) == 0 { + log.Debugf(msg) + return nil + } + log.Infof(msg) + startTime := time.Now() + for _, block := range blocksToCheck { + // check block + err = CheckBlockHash(ctx, block, p.L1Client, p.Name()) + if common.IsReorgError(err) { + // Double-check the state block that still is the same + log.Debugf("%s: Reorg detected at blockNumber: %d, checking that the block on State doesn't have change", p.Name(), block.BlockNumber) + isTheSame, errBlockIsTheSame := p.checkThatStateBlockIsTheSame(ctx, block) + if errBlockIsTheSame != nil { + log.Warnf("%s can't double-check that blockNumber %d haven't changed, so it discard the reorg error", p.Name(), block.BlockNumber) + return err + } + if !isTheSame { + log.Infof("%s: DeSync detected, blockNumber: %d is different now that when we started the check", p.Name(), block.BlockNumber) + return ErrDeSync + } + log.Infof("%s: Reorg detected and verified the state block, blockNumber: %d", p.Name(), block.BlockNumber) + return err + } + if err != nil { + return err + } + } + elapsed := time.Since(startTime) + log.Infof("%s: Checked blocks from (%s) %d to (%s) %d -> len(blocks):%d elapsed: %s", p.Name(), p.InitialSegmentBlockNumber.Description(), from, p.EndSegmentBlockNumber.Description(), to, len(blocksToCheck), elapsed.String()) + + return nil +} + +// CheckBlockHash is a method that checks the L1 block hash +// returns true if is the same +func (p *PreCheckL1BlockHash) checkThatStateBlockIsTheSame(ctx context.Context, block *state.Block) (bool, error) { + blocks, err := p.State.GetUncheckedBlocks(ctx, block.BlockNumber, block.BlockNumber, nil) + if err != nil { + log.Warnf("%s: Fails to get blockNumber %d in state .Err:%s", p.Name(), block.BlockNumber, err.Error()) + return false, err + } + if len(blocks) == 0 { + // The block is checked or deleted, so it is not the same + log.Debugf("%s: The blockNumber %d is no longer in the state (or checked or deleted)", p.Name(), block.BlockNumber) + return false, nil + } + stateBlock := blocks[0] + if stateBlock.BlockNumber != block.BlockNumber { + msg := fmt.Sprintf("%s: The blockNumber returned by state %d is different from the state blockNumber %d", + p.Name(), block.BlockNumber, stateBlock.BlockNumber) + log.Warn(msg) + return false, fmt.Errorf(msg) + } + if stateBlock.BlockHash != block.BlockHash { + msg := fmt.Sprintf("%s: The blockNumber %d differs the hash checked %s from current in state %s", + p.Name(), block.BlockNumber, block.BlockHash.String(), stateBlock.BlockHash.String()) + log.Warn(msg) + return false, nil + } + // The block is the same + return true, nil +} diff --git a/synchronizer/l1_check_block/pre_check_l1block_test.go b/synchronizer/l1_check_block/pre_check_l1block_test.go new file mode 100644 index 0000000000..39c359a513 --- /dev/null +++ b/synchronizer/l1_check_block/pre_check_l1block_test.go @@ -0,0 +1,144 @@ +package l1_check_block_test + +import ( + "context" + "math/big" + "testing" + + "github.com/0xPolygonHermez/zkevm-node/state" + commonsync "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block" + mock_l1_check_block "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block/mocks" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +type testPreCheckData struct { + sut *l1_check_block.PreCheckL1BlockHash + mockL1Client *mock_l1_check_block.L1Requester + mockState *mock_l1_check_block.StatePreCheckInterfacer + mockInitialFetch *mock_l1_check_block.SafeL1BlockNumberFetcher + mockEndFetch *mock_l1_check_block.SafeL1BlockNumberFetcher + ctx context.Context + stateBlocks []*state.Block +} + +func newPreCheckData(t *testing.T) *testPreCheckData { + mockL1Client := mock_l1_check_block.NewL1Requester(t) + mockState := mock_l1_check_block.NewStatePreCheckInterfacer(t) + mockInitialFetch := mock_l1_check_block.NewSafeL1BlockNumberFetcher(t) + mockEndFetch := mock_l1_check_block.NewSafeL1BlockNumberFetcher(t) + sut := l1_check_block.NewPreCheckL1BlockHash(mockL1Client, mockState, mockInitialFetch, mockEndFetch) + return &testPreCheckData{ + sut: sut, + mockL1Client: mockL1Client, + mockState: mockState, + mockInitialFetch: mockInitialFetch, + mockEndFetch: mockEndFetch, + ctx: context.Background(), + stateBlocks: []*state.Block{ + { + BlockNumber: 1234, + BlockHash: common.HexToHash("0xd77dd3a9ee6f9202ca5a75024b7d9cbd3d7436b2910d450f88c261c0089c0cd9"), + }, + { + BlockNumber: 1237, + BlockHash: common.HexToHash("0x8faffac37f561c18917c33ff3540262ecfbe11a367b4e1c48181326cd8ba347f"), + }, + }, + } +} + +// If from > to, it ignore because there are no blocks to check +func TestPreCheckL1BlockFromGreaterThanTo(t *testing.T) { + data := newPreCheckData(t) + data.mockInitialFetch.EXPECT().Description().Return("initial") + data.mockEndFetch.EXPECT().Description().Return("end") + data.mockInitialFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1234), nil) + data.mockEndFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1230), nil) + + res := data.sut.Step(data.ctx) + require.NoError(t, res) +} + +// No blocks on state -> nothing to do +func TestPreCheckL1BlockNoBlocksOnState(t *testing.T) { + data := newPreCheckData(t) + data.mockInitialFetch.EXPECT().Description().Return("initial") + data.mockEndFetch.EXPECT().Description().Return("end") + data.mockInitialFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1234), nil) + data.mockEndFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1250), nil) + data.mockState.EXPECT().GetUncheckedBlocks(data.ctx, uint64(1234), uint64(1250), nil).Return(nil, nil) + + res := data.sut.Step(data.ctx) + require.NoError(t, res) +} + +func TestPreCheckL1BlockBlocksMatch(t *testing.T) { + data := newPreCheckData(t) + data.mockInitialFetch.EXPECT().Description().Return("initial") + data.mockEndFetch.EXPECT().Description().Return("end") + data.mockInitialFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1234), nil) + data.mockEndFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1250), nil) + data.mockState.EXPECT().GetUncheckedBlocks(data.ctx, uint64(1234), uint64(1250), nil).Return(data.stateBlocks, nil) + l1Block1 := &types.Header{ + Number: big.NewInt(int64(data.stateBlocks[0].BlockNumber)), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlocks[0].BlockNumber))).Return(l1Block1, nil) + l1Block2 := &types.Header{ + Number: big.NewInt(int64(data.stateBlocks[1].BlockNumber)), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlocks[1].BlockNumber))).Return(l1Block2, nil) + //data.mockState.EXPECT().GetUncheckedBlocks(data.ctx, uint64(1237), uint64(1237), nil).Return(data.stateBlocks[0:1], nil) + + res := data.sut.Step(data.ctx) + require.NoError(t, res) +} + +func TestPreCheckL1BlockBlocksMismatch(t *testing.T) { + data := newPreCheckData(t) + data.mockInitialFetch.EXPECT().Description().Return("initial") + data.mockEndFetch.EXPECT().Description().Return("end") + data.mockInitialFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1234), nil) + data.mockEndFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1250), nil) + data.stateBlocks[1].BlockHash = common.HexToHash("0x12345678901234567890123456789012345678901234567890") + data.mockState.EXPECT().GetUncheckedBlocks(data.ctx, uint64(1234), uint64(1250), nil).Return(data.stateBlocks, nil) + l1Block1 := &types.Header{ + Number: big.NewInt(int64(data.stateBlocks[0].BlockNumber)), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlocks[0].BlockNumber))).Return(l1Block1, nil) + l1Block2 := &types.Header{ + Number: big.NewInt(int64(data.stateBlocks[1].BlockNumber)), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlocks[1].BlockNumber))).Return(l1Block2, nil) + data.mockState.EXPECT().GetUncheckedBlocks(data.ctx, uint64(1237), uint64(1237), nil).Return(data.stateBlocks[1:2], nil) + + res := data.sut.Step(data.ctx) + require.Error(t, res) + resErr, ok := res.(*commonsync.ReorgError) + require.True(t, ok, "The error must be ReorgError") + require.Equal(t, uint64(1237), resErr.BlockNumber) +} + +func TestPreCheckL1BlockBlocksMismatchButIsNoLongerInState(t *testing.T) { + data := newPreCheckData(t) + data.mockInitialFetch.EXPECT().Description().Return("initial") + data.mockEndFetch.EXPECT().Description().Return("end") + data.mockInitialFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1234), nil) + data.mockEndFetch.EXPECT().GetSafeBlockNumber(data.ctx, data.mockL1Client).Return(uint64(1250), nil) + data.stateBlocks[1].BlockHash = common.HexToHash("0x12345678901234567890123456789012345678901234567890") + data.mockState.EXPECT().GetUncheckedBlocks(data.ctx, uint64(1234), uint64(1250), nil).Return(data.stateBlocks, nil) + l1Block1 := &types.Header{ + Number: big.NewInt(int64(data.stateBlocks[0].BlockNumber)), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlocks[0].BlockNumber))).Return(l1Block1, nil) + l1Block2 := &types.Header{ + Number: big.NewInt(int64(data.stateBlocks[1].BlockNumber)), + } + data.mockL1Client.EXPECT().HeaderByNumber(data.ctx, big.NewInt(int64(data.stateBlocks[1].BlockNumber))).Return(l1Block2, nil) + data.mockState.EXPECT().GetUncheckedBlocks(data.ctx, uint64(1237), uint64(1237), nil).Return(nil, nil) + + res := data.sut.Step(data.ctx) + require.ErrorIs(t, res, l1_check_block.ErrDeSync) +} diff --git a/synchronizer/l1_check_block/safe_l1_block.go b/synchronizer/l1_check_block/safe_l1_block.go new file mode 100644 index 0000000000..7b767b4900 --- /dev/null +++ b/synchronizer/l1_check_block/safe_l1_block.go @@ -0,0 +1,120 @@ +package l1_check_block + +import ( + "context" + "fmt" + "math/big" + + "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/ethereum/go-ethereum/rpc" +) + +// L1BlockPoint is an enum that represents the point of the L1 block +type L1BlockPoint int + +const ( + // FinalizedBlockNumber is the finalized block number + FinalizedBlockNumber L1BlockPoint = 3 + // SafeBlockNumber is the safe block number + SafeBlockNumber L1BlockPoint = 2 + // PendingBlockNumber is the pending block number + PendingBlockNumber L1BlockPoint = 1 + // LastBlockNumber is the last block number + LastBlockNumber L1BlockPoint = 0 +) + +// ToString converts a L1BlockPoint to a string +func (v L1BlockPoint) ToString() string { + switch v { + case FinalizedBlockNumber: + return "finalized" + case SafeBlockNumber: + return "safe" + case PendingBlockNumber: + return "pending" + case LastBlockNumber: + return "latest" + } + return "Unknown" +} + +// StringToL1BlockPoint converts a string to a L1BlockPoint +func StringToL1BlockPoint(s string) L1BlockPoint { + switch s { + case "finalized": + return FinalizedBlockNumber + case "safe": + return SafeBlockNumber + case "pending": + return PendingBlockNumber + case "latest": + return LastBlockNumber + default: + return FinalizedBlockNumber + } +} + +// ToGethRequest converts a L1BlockPoint to a big.Int used for request to GETH +func (v L1BlockPoint) ToGethRequest() *big.Int { + switch v { + case FinalizedBlockNumber: + return big.NewInt(int64(rpc.FinalizedBlockNumber)) + case PendingBlockNumber: + return big.NewInt(int64(rpc.PendingBlockNumber)) + case SafeBlockNumber: + return big.NewInt(int64(rpc.SafeBlockNumber)) + case LastBlockNumber: + return nil + } + return big.NewInt(int64(v)) +} + +// SafeL1BlockNumberFetch is a struct that implements a safe L1 block number fetch +type SafeL1BlockNumberFetch struct { + // SafeBlockPoint is the block number that is reference to l1 Block + SafeBlockPoint L1BlockPoint + // Offset is a vaule add to the L1 block + Offset int +} + +// NewSafeL1BlockNumberFetch creates a new SafeL1BlockNumberFetch +func NewSafeL1BlockNumberFetch(safeBlockPoint L1BlockPoint, offset int) *SafeL1BlockNumberFetch { + return &SafeL1BlockNumberFetch{ + SafeBlockPoint: safeBlockPoint, + Offset: offset, + } +} + +// Description returns a string representation of SafeL1BlockNumberFetch +func (p *SafeL1BlockNumberFetch) Description() string { + return fmt.Sprintf("%s/%d", p.SafeBlockPoint.ToString(), p.Offset) +} + +// GetSafeBlockNumber gets the safe block number from L1 +func (p *SafeL1BlockNumberFetch) GetSafeBlockNumber(ctx context.Context, requester L1Requester) (uint64, error) { + l1SafePointBlock, err := requester.HeaderByNumber(ctx, p.SafeBlockPoint.ToGethRequest()) + if err != nil { + log.Errorf("%s: Error getting L1 block %d. err: %s", logPrefix, p.String(), err.Error()) + return uint64(0), err + } + result := l1SafePointBlock.Number.Uint64() + if p.Offset < 0 { + if result < uint64(-p.Offset) { + result = 0 + } else { + result += uint64(p.Offset) + } + } else { + result = l1SafePointBlock.Number.Uint64() + uint64(p.Offset) + } + if p.SafeBlockPoint == LastBlockNumber { + result = min(result, l1SafePointBlock.Number.Uint64()) + } + + return result, nil +} + +// String returns a string representation of SafeL1BlockNumberFetch +func (p *SafeL1BlockNumberFetch) String() string { + return fmt.Sprintf("SafeBlockPoint: %s, Offset: %d", p.SafeBlockPoint.ToString(), p.Offset) +} diff --git a/synchronizer/l1_check_block/safe_l1_block_test.go b/synchronizer/l1_check_block/safe_l1_block_test.go new file mode 100644 index 0000000000..4d3167adcd --- /dev/null +++ b/synchronizer/l1_check_block/safe_l1_block_test.go @@ -0,0 +1,113 @@ +package l1_check_block_test + +import ( + "context" + "math/big" + "testing" + + "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block" + mock_l1_check_block "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block/mocks" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGetSafeBlockNumber(t *testing.T) { + ctx := context.Background() + mockRequester := mock_l1_check_block.NewL1Requester(t) + //safeBlockPoint := big.NewInt(50) + offset := 10 + safeL1Block := l1_check_block.NewSafeL1BlockNumberFetch(l1_check_block.StringToL1BlockPoint("safe"), offset) + + mockRequester.EXPECT().HeaderByNumber(ctx, mock.Anything).Return(&types.Header{ + Number: big.NewInt(100), + }, nil) + blockNumber, err := safeL1Block.GetSafeBlockNumber(ctx, mockRequester) + assert.NoError(t, err) + expectedBlockNumber := uint64(100 + offset) + assert.Equal(t, expectedBlockNumber, blockNumber) +} + +func TestGetSafeBlockNumberMutliplesCases(t *testing.T) { + tests := []struct { + name string + blockPoint string + offset int + l1ReturnBlockNumber uint64 + expectedCallToGeth *big.Int + expectedBlockNumber uint64 + }{ + { + name: "SafeBlockNumber+10", + blockPoint: "safe", + offset: 10, + l1ReturnBlockNumber: 100, + expectedCallToGeth: big.NewInt(int64(rpc.SafeBlockNumber)), + expectedBlockNumber: 110, + }, + { + name: "FinalizedBlockNumber+10", + blockPoint: "finalized", + offset: 10, + l1ReturnBlockNumber: 100, + expectedCallToGeth: big.NewInt(int64(rpc.FinalizedBlockNumber)), + expectedBlockNumber: 110, + }, + { + name: "PendingBlockNumber+10", + blockPoint: "pending", + offset: 10, + l1ReturnBlockNumber: 100, + expectedCallToGeth: big.NewInt(int64(rpc.PendingBlockNumber)), + expectedBlockNumber: 110, + }, + { + name: "LastBlockNumber+10, can't add 10 to latest block number. So must return latest block number and ignore positive offset", + blockPoint: "latest", + offset: 10, + l1ReturnBlockNumber: 100, + expectedCallToGeth: nil, + expectedBlockNumber: 100, + }, + { + name: "FinalizedBlockNumber-1000. negative blockNumbers are not welcome. So must return 0", + blockPoint: "finalized", + offset: -1000, + l1ReturnBlockNumber: 100, + expectedCallToGeth: big.NewInt(int64(rpc.FinalizedBlockNumber)), + expectedBlockNumber: 0, + }, + { + name: "FinalizedBlockNumber(1000)-1000. is 0 ", + blockPoint: "finalized", + offset: -1000, + l1ReturnBlockNumber: 1000, + expectedCallToGeth: big.NewInt(int64(rpc.FinalizedBlockNumber)), + expectedBlockNumber: 0, + }, + { + name: "FinalizedBlockNumber(1001)-1000. is 1 ", + blockPoint: "finalized", + offset: -1000, + l1ReturnBlockNumber: 1001, + expectedCallToGeth: big.NewInt(int64(rpc.FinalizedBlockNumber)), + expectedBlockNumber: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + mockRequester := mock_l1_check_block.NewL1Requester(t) + safeL1Block := l1_check_block.NewSafeL1BlockNumberFetch(l1_check_block.StringToL1BlockPoint(tt.blockPoint), tt.offset) + + mockRequester.EXPECT().HeaderByNumber(ctx, tt.expectedCallToGeth).Return(&types.Header{ + Number: big.NewInt(int64(tt.l1ReturnBlockNumber)), + }, nil) + blockNumber, err := safeL1Block.GetSafeBlockNumber(ctx, mockRequester) + assert.NoError(t, err) + assert.Equal(t, tt.expectedBlockNumber, blockNumber) + }) + } +} diff --git a/synchronizer/l1_parallel_sync/l1_rollup_info_consumer.go b/synchronizer/l1_parallel_sync/l1_rollup_info_consumer.go index 5a457acbf0..4dd78632fd 100644 --- a/synchronizer/l1_parallel_sync/l1_rollup_info_consumer.go +++ b/synchronizer/l1_parallel_sync/l1_rollup_info_consumer.go @@ -3,12 +3,14 @@ package l1_parallel_sync import ( "context" "errors" + "fmt" "sync" "time" "github.com/0xPolygonHermez/zkevm-node/etherman" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/state" + syncCommon "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" "github.com/ethereum/go-ethereum/common" types "github.com/ethereum/go-ethereum/core/types" ) @@ -22,7 +24,6 @@ var ( errContextCanceled = errors.New("consumer:context canceled") errConsumerStopped = errors.New("consumer:stopped by request") errConsumerStoppedBecauseIsSynchronized = errors.New("consumer:stopped because is synchronized") - errL1Reorg = errors.New("consumer: L1 reorg detected") errConsumerAndProducerDesynchronized = errors.New("consumer: consumer and producer are desynchronized") ) @@ -155,13 +156,12 @@ func checkPreviousBlocks(rollupInfo rollupInfoByBlockRangeResult, cachedBlock *s } if cachedBlock.BlockNumber == rollupInfo.previousBlockOfRange.NumberU64() { if cachedBlock.BlockHash != rollupInfo.previousBlockOfRange.Hash() { - log.Errorf("consumer: Previous block %d hash is not the same", cachedBlock.BlockNumber) - return errL1Reorg - } - if cachedBlock.ParentHash != rollupInfo.previousBlockOfRange.ParentHash() { - log.Errorf("consumer: Previous block %d parentHash is not the same", cachedBlock.BlockNumber) - return errL1Reorg + err := fmt.Errorf("consumer: Previous block %d hash is not the same. state.Hash:%s != l1.Hash:%s", + cachedBlock.BlockNumber, cachedBlock.BlockHash, rollupInfo.previousBlockOfRange.Hash()) + log.Errorf(err.Error()) + return syncCommon.NewReorgError(cachedBlock.BlockNumber, err) } + log.Infof("consumer: Verified previous block %d not the same: OK", cachedBlock.BlockNumber) } return nil diff --git a/synchronizer/l2_sync/l2_shared/processor_trusted_batch_sync.go b/synchronizer/l2_sync/l2_shared/processor_trusted_batch_sync.go index db4ddd15e0..5463555d94 100644 --- a/synchronizer/l2_sync/l2_shared/processor_trusted_batch_sync.go +++ b/synchronizer/l2_sync/l2_shared/processor_trusted_batch_sync.go @@ -171,6 +171,10 @@ func (s *ProcessorTrustedBatchSync) AddPostChecker(checker PostClosedBatchChecke // ProcessTrustedBatch processes a trusted batch and return the new state func (s *ProcessorTrustedBatchSync) ProcessTrustedBatch(ctx context.Context, trustedBatch *types.Batch, status TrustedState, dbTx pgx.Tx, debugPrefix string) (*TrustedState, error) { + if trustedBatch == nil { + log.Errorf("%s trustedBatch is nil, it never should be nil", debugPrefix) + return nil, fmt.Errorf("%s trustedBatch is nil, it never should be nil", debugPrefix) + } log.Debugf("%s Processing trusted batch: %v", debugPrefix, trustedBatch.Number) stateCurrentBatch, statePreviousBatch := s.GetCurrentAndPreviousBatchFromCache(&status) if s.l1SyncChecker != nil { @@ -374,7 +378,9 @@ func (s *ProcessorTrustedBatchSync) GetModeForProcessBatch(trustedNodeBatch *typ result.OldAccInputHash = statePreviousBatch.AccInputHash result.Now = s.timeProvider.Now() result.DebugPrefix = fmt.Sprintf("%s mode %s:", debugPrefix, result.Mode) - + if result.BatchMustBeClosed { + result.DebugPrefix += " (must_be_closed)" + } if isTrustedBatchEmptyAndClosed(trustedNodeBatch) { if s.Cfg.AcceptEmptyClosedBatches { log.Infof("%s Batch %v: TrustedBatch Empty and closed, accepted due configuration", result.DebugPrefix, trustedNodeBatch.Number) diff --git a/synchronizer/l2_sync/l2_shared/tests/trusted_batches_retrieve_test.go b/synchronizer/l2_sync/l2_shared/tests/trusted_batches_retrieve_test.go new file mode 100644 index 0000000000..f050fa565f --- /dev/null +++ b/synchronizer/l2_sync/l2_shared/tests/trusted_batches_retrieve_test.go @@ -0,0 +1,117 @@ +package test_l2_shared + +import ( + "context" + "math/big" + "testing" + + "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" + mock_syncinterfaces "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces/mocks" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/l2_sync/l2_shared" + l2sharedmocks "github.com/0xPolygonHermez/zkevm-node/synchronizer/l2_sync/l2_shared/mocks" + syncMocks "github.com/0xPolygonHermez/zkevm-node/synchronizer/mocks" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +type testDataTrustedBatchRetrieve struct { + mockBatchProcessor *l2sharedmocks.BatchProcessor + mockZkEVMClient *mock_syncinterfaces.ZKEVMClientTrustedBatchesGetter + mockState *l2sharedmocks.StateInterface + mockSync *mock_syncinterfaces.SynchronizerFlushIDManager + mockTimer *common.MockTimerProvider + mockDbTx *syncMocks.DbTxMock + TrustedStateMngr *l2_shared.TrustedStateManager + sut *l2_shared.TrustedBatchesRetrieve + ctx context.Context +} + +func newTestDataTrustedBatchRetrieve(t *testing.T) *testDataTrustedBatchRetrieve { + mockBatchProcessor := l2sharedmocks.NewBatchProcessor(t) + mockZkEVMClient := mock_syncinterfaces.NewZKEVMClientTrustedBatchesGetter(t) + mockState := l2sharedmocks.NewStateInterface(t) + mockSync := mock_syncinterfaces.NewSynchronizerFlushIDManager(t) + mockTimer := &common.MockTimerProvider{} + mockDbTx := syncMocks.NewDbTxMock(t) + TrustedStateMngr := l2_shared.NewTrustedStateManager(mockTimer, 0) + sut := l2_shared.NewTrustedBatchesRetrieve(mockBatchProcessor, mockZkEVMClient, mockState, mockSync, *TrustedStateMngr) + ctx := context.TODO() + return &testDataTrustedBatchRetrieve{ + mockBatchProcessor: mockBatchProcessor, + mockZkEVMClient: mockZkEVMClient, + mockState: mockState, + mockSync: mockSync, + mockTimer: mockTimer, + mockDbTx: mockDbTx, + TrustedStateMngr: TrustedStateMngr, + sut: sut, + ctx: ctx, + } +} + +const ( + closedBatch = true + notClosedBatch = false +) + +// This test must do from 100 to 104. +// But the batch 100 is open on TrustedNode so it stop processing +func TestSyncTrustedBatchesToFromStopAfterFirstWIPBatch(t *testing.T) { + data := newTestDataTrustedBatchRetrieve(t) + data.mockZkEVMClient.EXPECT().BatchNumber(data.ctx).Return(uint64(102), nil) + + expectationsForSyncTrustedStateIteration(t, 100, notClosedBatch, data) + + err := data.sut.SyncTrustedState(data.ctx, 100, 104) + require.NoError(t, err) +} + +// This must process 100 (that is closed) +// and stop processing at 101 because is not yet close this batch +func TestSyncTrustedBatchesToFromStopAfterFirstWIPBatchCase2(t *testing.T) { + data := newTestDataTrustedBatchRetrieve(t) + data.mockZkEVMClient.EXPECT().BatchNumber(data.ctx).Return(uint64(102), nil) + + expectationsForSyncTrustedStateIteration(t, 100, closedBatch, data) + expectationsForSyncTrustedStateIteration(t, 101, notClosedBatch, data) + + err := data.sut.SyncTrustedState(data.ctx, 100, 104) + require.NoError(t, err) +} + +// This test must do from 100 to 102. Is for check manually that the logs +// That is not tested but must not emit the log: +// - Batch 101 is not closed. so we break synchronization from Trusted Node because can only have 1 WIP batch on state +func TestSyncTrustedBatchesToFromStopAfterFirstWIPBatchCase3(t *testing.T) { + data := newTestDataTrustedBatchRetrieve(t) + data.mockZkEVMClient.EXPECT().BatchNumber(data.ctx).Return(uint64(102), nil) + expectationsForSyncTrustedStateIteration(t, 100, closedBatch, data) + expectationsForSyncTrustedStateIteration(t, 101, closedBatch, data) + expectationsForSyncTrustedStateIteration(t, 102, notClosedBatch, data) + + err := data.sut.SyncTrustedState(data.ctx, 100, 102) + require.NoError(t, err) +} + +func expectationsForSyncTrustedStateIteration(t *testing.T, batchNumber uint64, closed bool, data *testDataTrustedBatchRetrieve) { + batch100 := &types.Batch{ + Number: types.ArgUint64(batchNumber), + Closed: closed, + } + data.mockZkEVMClient.EXPECT().BatchByNumber(data.ctx, big.NewInt(0).SetUint64(batchNumber)).Return(batch100, nil) + data.mockState.EXPECT().BeginStateTransaction(data.ctx).Return(data.mockDbTx, nil) + // Get Previous Batch 99 from State + stateBatch99 := &state.Batch{ + BatchNumber: batchNumber - 1, + } + data.mockState.EXPECT().GetBatchByNumber(data.ctx, uint64(batchNumber-1), data.mockDbTx).Return(stateBatch99, nil) + stateBatch100 := &state.Batch{ + BatchNumber: batchNumber, + } + data.mockState.EXPECT().GetBatchByNumber(data.ctx, uint64(batchNumber), data.mockDbTx).Return(stateBatch100, nil) + data.mockBatchProcessor.EXPECT().ProcessTrustedBatch(data.ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + data.mockSync.EXPECT().CheckFlushID(mock.Anything).Return(nil) + data.mockDbTx.EXPECT().Commit(data.ctx).Return(nil) +} diff --git a/synchronizer/l2_sync/l2_shared/trusted_batches_retrieve.go b/synchronizer/l2_sync/l2_shared/trusted_batches_retrieve.go index 70311c6966..b4031b4653 100644 --- a/synchronizer/l2_sync/l2_shared/trusted_batches_retrieve.go +++ b/synchronizer/l2_sync/l2_shared/trusted_batches_retrieve.go @@ -109,6 +109,16 @@ func isSyncrhonizedTrustedState(lastTrustedStateBatchNumber uint64, latestSynced return lastTrustedStateBatchNumber < latestSyncedBatch } +func sanityCheckBatchReturnedByTrusted(batch *types.Batch, expectedBatchNumber uint64) error { + if batch == nil { + return fmt.Errorf("batch %d is nil", expectedBatchNumber) + } + if uint64(batch.Number) != expectedBatchNumber { + return fmt.Errorf("batch %d is not the expected batch %d", batch.Number, expectedBatchNumber) + } + return nil +} + func (s *TrustedBatchesRetrieve) syncTrustedBatchesToFrom(ctx context.Context, latestSyncedBatch uint64, lastTrustedStateBatchNumber uint64) error { batchNumberToSync := max(latestSyncedBatch, s.firstBatchNumberToSync) for batchNumberToSync <= lastTrustedStateBatchNumber { @@ -120,6 +130,11 @@ func (s *TrustedBatchesRetrieve) syncTrustedBatchesToFrom(ctx context.Context, l log.Warnf("%s failed to get batch %d from trusted state. Error: %v", debugPrefix, batchNumberToSync, err) return err } + err = sanityCheckBatchReturnedByTrusted(batchToSync, batchNumberToSync) + if err != nil { + log.Warnf("%s sanity check over Batch returned by Trusted-RPC failed: %v", debugPrefix, err) + return err + } dbTx, err := s.state.BeginStateTransaction(ctx) if err != nil { @@ -161,6 +176,10 @@ func (s *TrustedBatchesRetrieve) syncTrustedBatchesToFrom(ctx context.Context, l s.TrustedStateMngr.Clear() } batchNumberToSync++ + if !batchToSync.Closed && batchNumberToSync <= lastTrustedStateBatchNumber { + log.Infof("%s Batch %d is not closed. so we break synchronization from Trusted Node because can only have 1 WIP batch on state", debugPrefix, batchToSync.Number) + return nil + } } log.Infof("syncTrustedState: Trusted state fully synchronized from %d to %d", latestSyncedBatch, lastTrustedStateBatchNumber) diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index d0b786e527..f29421aad6 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -16,6 +16,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/synchronizer/actions/processor_manager" syncCommon "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_check_block" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_parallel_sync" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1event_orders" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l2_sync/l2_shared" @@ -77,6 +78,7 @@ type ClientSynchronizer struct { l1EventProcessors *processor_manager.L1EventProcessors syncTrustedStateExecutor syncinterfaces.SyncTrustedStateExecutor halter syncinterfaces.CriticalErrorHandler + asyncL1BlockChecker syncinterfaces.L1BlockCheckerIntegrator } // NewSynchronizer creates and initializes an instance of Synchronizer @@ -123,6 +125,31 @@ func NewSynchronizer( syncBlockProtection: syncBlockProtection, halter: syncCommon.NewCriticalErrorHalt(eventLog, 5*time.Second), //nolint:gomnd } + if cfg.L1BlockCheck.Enable { + log.Infof("L1BlockChecker enabled: %s", cfg.L1BlockCheck.String()) + l1BlockChecker := l1_check_block.NewCheckL1BlockHash(ethMan, res.state, + l1_check_block.NewSafeL1BlockNumberFetch(l1_check_block.StringToL1BlockPoint(cfg.L1BlockCheck.L1SafeBlockPoint), cfg.L1BlockCheck.L1SafeBlockOffset)) + + var preCheckAsync syncinterfaces.AsyncL1BlockChecker + if cfg.L1BlockCheck.PreCheckEnable { + log.Infof("L1BlockChecker enabled precheck from: %s/%d to: %s/%d", + cfg.L1BlockCheck.L1SafeBlockPoint, cfg.L1BlockCheck.L1SafeBlockOffset, + cfg.L1BlockCheck.L1PreSafeBlockPoint, cfg.L1BlockCheck.L1PreSafeBlockOffset) + l1BlockPreChecker := l1_check_block.NewPreCheckL1BlockHash(ethMan, res.state, + l1_check_block.NewSafeL1BlockNumberFetch(l1_check_block.StringToL1BlockPoint(cfg.L1BlockCheck.L1SafeBlockPoint), cfg.L1BlockCheck.L1SafeBlockOffset), + l1_check_block.NewSafeL1BlockNumberFetch(l1_check_block.StringToL1BlockPoint(cfg.L1BlockCheck.L1PreSafeBlockPoint), cfg.L1BlockCheck.L1PreSafeBlockOffset), + ) + preCheckAsync = l1_check_block.NewAsyncCheck(l1BlockPreChecker) + } + + res.asyncL1BlockChecker = l1_check_block.NewL1BlockCheckerIntegration( + l1_check_block.NewAsyncCheck(l1BlockChecker), + preCheckAsync, + res.state, + res, + cfg.L1BlockCheck.ForceCheckBeforeStart, + time.Second) + } if !isTrustedSequencer { log.Info("Permissionless: creating and Initializing L2 synchronization components") @@ -156,7 +183,11 @@ func NewSynchronizer( log.Errorf("error getting last L2Block number from state. Error: %v", err) return nil, err } - l1checkerL2Blocks = actions.NewCheckL2BlockHash(res.state, res.zkEVMClientEthereumCompatible, initialL2Block, cfg.L1SyncCheckL2BlockNumberhModulus) + l1checkerL2Blocks, err = actions.NewCheckL2BlockHash(res.state, res.zkEVMClientEthereumCompatible, initialL2Block, cfg.L1SyncCheckL2BlockNumberhModulus) + if err != nil { + log.Error("error creating new instance of checkL2BlockHash. Error: ", err) + return nil, err + } } else { log.Infof("Trusted Node can't check L2Block hash, ignoring parameter") } @@ -251,6 +282,10 @@ func (s *ClientSynchronizer) Sync() error { // If there is no lastEthereumBlock means that sync from the beginning is necessary. If not, it continues from the retrieved ethereum block // Get the latest synced block. If there is no block on db, use genesis block log.Info("Sync started") + if s.asyncL1BlockChecker != nil { + _ = s.asyncL1BlockChecker.OnStart(s.ctx) + } + dbTx, err := s.state.BeginStateTransaction(s.ctx) if err != nil { log.Errorf("error creating db transaction to get latest block. Error: %v", err) @@ -402,6 +437,7 @@ func (s *ClientSynchronizer) Sync() error { continue } log.Infof("latestSequencedBatchNumber: %d, latestSyncedBatch: %d, lastVerifiedBatchNumber: %d", latestSequencedBatchNumber, latestSyncedBatch, lastVerifiedBatchNumber) + resetDone := false // Sync trusted state // latestSyncedBatch -> Last batch on DB // latestSequencedBatchNumber -> last batch on SMC @@ -409,6 +445,13 @@ func (s *ClientSynchronizer) Sync() error { startTrusted := time.Now() if s.syncTrustedStateExecutor != nil && !s.isTrustedSequencer { log.Info("Syncing trusted state (permissionless)") + //Sync Trusted State + log.Debug("Doing reorg check before L2 sync") + resetDone, lastEthBlockSynced, err = s.checkReorgAndExecuteReset(lastEthBlockSynced) + if resetDone || err != nil { + log.Infof("Reset done before L2 sync") + continue + } err = s.syncTrustedState(latestSyncedBatch) metrics.FullTrustedSyncTime(time.Since(startTrusted)) if err != nil { @@ -417,10 +460,14 @@ func (s *ClientSynchronizer) Sync() error { if errors.Is(err, syncinterfaces.ErrFatalDesyncFromL1) { l1BlockNumber := err.(*l2_shared.DeSyncPermissionlessAndTrustedNodeError).L1BlockNumber log.Error("Trusted and permissionless desync! reseting to last common point: L1Block (%d-1)", l1BlockNumber) - err = s.resetState(l1BlockNumber - 1) - if err != nil { - log.Errorf("error resetting the state to a discrepancy block. Retrying... Err: %v", err) - continue + for { + resetDone, lastEthBlockSynced, err = s.detectedReorgBadBlockExecuteReset(lastEthBlockSynced, syncCommon.GetReorgErrorBlockNumber(err)) + if resetDone { + break + } else { + log.Error("reorg isn't done, retrying...") + time.Sleep(time.Second) + } } } else if errors.Is(err, syncinterfaces.ErrMissingSyncFromL1) { log.Info("Syncing from trusted node need data from L1") @@ -437,6 +484,11 @@ func (s *ClientSynchronizer) Sync() error { waitDuration = s.cfg.SyncInterval.Duration } //Sync L1Blocks + resetDone, lastEthBlockSynced, err = s.checkReorgAndExecuteReset(lastEthBlockSynced) + if resetDone || err != nil { + continue + } + startL1 := time.Now() if s.l1SyncOrchestration != nil && (latestSyncedBatch < latestSequencedBatchNumber || !s.cfg.L1ParallelSynchronization.FallbackToSequentialModeOnSynchronized) { log.Infof("Syncing L1 blocks in parallel lastEthBlockSynced=%d", lastEthBlockSynced.BlockNumber) @@ -451,6 +503,19 @@ func (s *ClientSynchronizer) Sync() error { lastEthBlockSynced, err = s.syncBlocksSequential(lastEthBlockSynced) } metrics.FullL1SyncTime(time.Since(startL1)) + if syncCommon.IsReorgError(err) { + log.Warnf("error syncing blocks: %s", err.Error()) + for { + resetDone, lastEthBlockSynced, err = s.detectedReorgBadBlockExecuteReset(lastEthBlockSynced, syncCommon.GetReorgErrorBlockNumber(err)) + if resetDone { + break + } else { + log.Error("reorg isn't done, retrying...") + time.Sleep(time.Second) + } + } + continue + } if err != nil { log.Warn("error syncing blocks: ", err) s.CleanTrustedState() @@ -521,22 +586,6 @@ func sanityCheckForGenesisBlockRollupInfo(blocks []etherman.Block, order map[com // This function syncs the node from a specific block to the latest // lastEthBlockSynced -> last block synced in the db func (s *ClientSynchronizer) syncBlocksParallel(lastEthBlockSynced *state.Block) (*state.Block, error) { - // This function will read events fromBlockNum to latestEthBlock. Check reorg to be sure that everything is ok. - block, err := s.checkReorg(lastEthBlockSynced) - if err != nil { - log.Errorf("error checking reorgs. Retrying... Err: %v", err) - return lastEthBlockSynced, fmt.Errorf("error checking reorgs") - } - if block != nil { - log.Infof("reorg detected. Resetting the state from block %v to block %v", lastEthBlockSynced.BlockNumber, block.BlockNumber) - err = s.resetState(block.BlockNumber) - if err != nil { - log.Errorf("error resetting the state to a previous block. Retrying... Err: %v", err) - s.l1SyncOrchestration.Reset(lastEthBlockSynced.BlockNumber) - return lastEthBlockSynced, fmt.Errorf("error resetting the state to a previous block") - } - return block, nil - } log.Infof("Starting L1 sync orchestrator in parallel block: %d", lastEthBlockSynced.BlockNumber) return s.l1SyncOrchestration.Start(lastEthBlockSynced) } @@ -551,31 +600,21 @@ func (s *ClientSynchronizer) syncBlocksSequential(lastEthBlockSynced *state.Bloc } lastKnownBlock := header.Number - // This function will read events fromBlockNum to latestEthBlock. Check reorg to be sure that everything is ok. - block, err := s.checkReorg(lastEthBlockSynced) - if err != nil { - log.Errorf("error checking reorgs. Retrying... Err: %v", err) - return lastEthBlockSynced, fmt.Errorf("error checking reorgs") - } - if block != nil { - err = s.resetState(block.BlockNumber) - if err != nil { - log.Errorf("error resetting the state to a previous block. Retrying... Err: %v", err) - return lastEthBlockSynced, fmt.Errorf("error resetting the state to a previous block") - } - return block, nil - } - var fromBlock uint64 if lastEthBlockSynced.BlockNumber > 0 { - fromBlock = lastEthBlockSynced.BlockNumber + 1 + fromBlock = lastEthBlockSynced.BlockNumber } + toBlock := fromBlock + s.cfg.SyncChunkSize for { - toBlock := fromBlock + s.cfg.SyncChunkSize if toBlock > lastKnownBlock.Uint64() { + log.Debug("Setting toBlock to the lastKnownBlock") toBlock = lastKnownBlock.Uint64() } + if fromBlock > toBlock { + log.Debug("FromBlock is higher than toBlock. Skipping...") + return lastEthBlockSynced, nil + } log.Infof("Syncing block %d of %d", fromBlock, lastKnownBlock.Uint64()) log.Infof("Getting rollup info from block %d to block %d", fromBlock, toBlock) // This function returns the rollup information contained in the ethereum blocks and an extra param called order. @@ -590,8 +629,39 @@ func (s *ClientSynchronizer) syncBlocksSequential(lastEthBlockSynced *state.Bloc return lastEthBlockSynced, err } + var initBlockReceived *etherman.Block + if len(blocks) != 0 { + initBlockReceived = &blocks[0] + // First position of the array must be deleted + blocks = removeBlockElement(blocks, 0) + } else { + // Reorg detected + log.Infof("Reorg detected in block %d while querying GetRollupInfoByBlockRange. Rolling back to at least the previous block", fromBlock) + prevBlock, err := s.state.GetPreviousBlock(s.ctx, 1, nil) + if errors.Is(err, state.ErrNotFound) { + log.Warn("error checking reorg: previous block not found in db: ", err) + prevBlock = &state.Block{} + } else if err != nil { + log.Error("error getting previousBlock from db. Error: ", err) + return lastEthBlockSynced, err + } + blockReorged, err := s.checkReorg(prevBlock, nil) + if err != nil { + log.Error("error checking reorgs in previous blocks. Error: ", err) + return lastEthBlockSynced, err + } + if blockReorged == nil { + blockReorged = prevBlock + } + err = s.resetState(blockReorged.BlockNumber) + if err != nil { + log.Errorf("error resetting the state to a previous block. Retrying... Err: %v", err) + return lastEthBlockSynced, fmt.Errorf("error resetting the state to a previous block") + } + return blockReorged, nil + } // Check reorg again to be sure that the chain has not changed between the previous checkReorg and the call GetRollupInfoByBlockRange - block, err := s.checkReorg(lastEthBlockSynced) + block, err := s.checkReorg(lastEthBlockSynced, initBlockReceived) if err != nil { log.Errorf("error checking reorgs. Retrying... Err: %v", err) return lastEthBlockSynced, fmt.Errorf("error checking reorgs") @@ -619,47 +689,36 @@ func (s *ClientSynchronizer) syncBlocksSequential(lastEthBlockSynced *state.Bloc ReceivedAt: blocks[len(blocks)-1].ReceivedAt, } for i := range blocks { - log.Debug("Position: ", i, ". BlockNumber: ", blocks[i].BlockNumber, ". BlockHash: ", blocks[i].BlockHash) + log.Info("Position: ", i, ". New block. BlockNumber: ", blocks[i].BlockNumber, ". BlockHash: ", blocks[i].BlockHash) } } - fromBlock = toBlock + 1 if lastKnownBlock.Cmp(new(big.Int).SetUint64(toBlock)) < 1 { waitDuration = s.cfg.SyncInterval.Duration break } - if len(blocks) == 0 { // If there is no events in the checked blocks range and lastKnownBlock > fromBlock. - // Store the latest block of the block range. Get block info and process the block - fb, err := s.etherMan.EthBlockByNumber(s.ctx, toBlock) - if err != nil { - return lastEthBlockSynced, err - } - b := etherman.Block{ - BlockNumber: fb.NumberU64(), - BlockHash: fb.Hash(), - ParentHash: fb.ParentHash(), - ReceivedAt: time.Unix(int64(fb.Time()), 0), - } - err = s.ProcessBlockRange([]etherman.Block{b}, order) - if err != nil { - return lastEthBlockSynced, err - } - block := state.Block{ - BlockNumber: fb.NumberU64(), - BlockHash: fb.Hash(), - ParentHash: fb.ParentHash(), - ReceivedAt: time.Unix(int64(fb.Time()), 0), - } - lastEthBlockSynced = &block - log.Debug("Storing empty block. BlockNumber: ", b.BlockNumber, ". BlockHash: ", b.BlockHash) - } + + fromBlock = lastEthBlockSynced.BlockNumber + toBlock = toBlock + s.cfg.SyncChunkSize } return lastEthBlockSynced, nil } +func removeBlockElement(slice []etherman.Block, s int) []etherman.Block { + ret := make([]etherman.Block, 0) + ret = append(ret, slice[:s]...) + return append(ret, slice[s+1:]...) +} + // ProcessBlockRange process the L1 events and stores the information in the db func (s *ClientSynchronizer) ProcessBlockRange(blocks []etherman.Block, order map[common.Hash][]etherman.Order) error { + // Check the latest finalized block in L1 + finalizedBlockNumber, err := s.etherMan.GetFinalizedBlockNumber(s.ctx) + if err != nil { + log.Errorf("error getting finalized block number in L1. Error: %v", err) + return err + } // New info has to be included into the db using the state for i := range blocks { // Begin db transaction @@ -674,9 +733,13 @@ func (s *ClientSynchronizer) ProcessBlockRange(blocks []etherman.Block, order ma ParentHash: blocks[i].ParentHash, ReceivedAt: blocks[i].ReceivedAt, } + if blocks[i].BlockNumber <= finalizedBlockNumber { + b.Checked = true + } // Add block information err = s.state.AddBlock(s.ctx, &b, dbTx) if err != nil { + // If any goes wrong we ensure that the state is rollbacked log.Errorf("error storing block. BlockNumber: %d, error: %v", blocks[i].BlockNumber, err) rollbackErr := dbTx.Rollback(s.ctx) if rollbackErr != nil { @@ -694,7 +757,7 @@ func (s *ClientSynchronizer) ProcessBlockRange(blocks []etherman.Block, order ma log.Debug("EventOrder: ", element.Name, ". Batch Sequence: ", batchSequence, "forkId: ", forkId) } else { forkId = s.state.GetForkIDByBlockNumber(blocks[i].BlockNumber) - log.Debug("EventOrder: ", element.Name, ". BlockNumber: ", blocks[i].BlockNumber, "forkId: ", forkId) + log.Debug("EventOrder: ", element.Name, ". BlockNumber: ", blocks[i].BlockNumber, ". forkId: ", forkId) } forkIdTyped := actions.ForkIdType(forkId) // Process event received from l1 @@ -713,6 +776,7 @@ func (s *ClientSynchronizer) ProcessBlockRange(blocks []etherman.Block, order ma log.Debug("Checking FlushID to commit L1 data to db") err = s.checkFlushID(dbTx) if err != nil { + // If any goes wrong we ensure that the state is rollbacked log.Errorf("error checking flushID. Error: %v", err) rollbackErr := dbTx.Rollback(s.ctx) if rollbackErr != nil { @@ -723,6 +787,7 @@ func (s *ClientSynchronizer) ProcessBlockRange(blocks []etherman.Block, order ma } err = dbTx.Commit(s.ctx) if err != nil { + // If any goes wrong we ensure that the state is rollbacked log.Errorf("error committing state to store block. BlockNumber: %d, err: %v", blocks[i].BlockNumber, err) rollbackErr := dbTx.Rollback(s.ctx) if rollbackErr != nil { @@ -781,12 +846,118 @@ func (s *ClientSynchronizer) resetState(blockNumber uint64) error { log.Error("error committing the resetted state. Error: ", err) return err } + if s.asyncL1BlockChecker != nil { + s.asyncL1BlockChecker.OnResetState(s.ctx) + } if s.l1SyncOrchestration != nil { - s.l1SyncOrchestration.Reset(blockNumber) + lastBlock, err := s.state.GetLastBlock(s.ctx, nil) + if err != nil { + log.Errorf("error getting last block synced from db. Error: %v", err) + s.l1SyncOrchestration.Reset(blockNumber) + } else { + s.l1SyncOrchestration.Reset(lastBlock.BlockNumber) + } } return nil } +// OnDetectedMismatchL1BlockReorg function will be called when a reorg is detected (asynchronous call) +func (s *ClientSynchronizer) OnDetectedMismatchL1BlockReorg() { + log.Infof("Detected Reorg in background at block (mismatch)") + if s.l1SyncOrchestration != nil && s.l1SyncOrchestration.IsProducerRunning() { + log.Errorf("Stop synchronizer: because L1 sync parallel aborting background process") + s.l1SyncOrchestration.Abort() + } +} + +// ExecuteReorgFromMismatchBlock function will reset the state to the block before the bad block +func (s *ClientSynchronizer) ExecuteReorgFromMismatchBlock(blockNumber uint64, reason string) error { + log.Info("Detected reorg at block (mismatch): ", blockNumber, " reason: ", reason, " resetting the state to block:", blockNumber-1) + s.CleanTrustedState() + return s.resetState(blockNumber - 1) +} +func (s *ClientSynchronizer) detectedReorgBadBlockExecuteReset(lastEthBlockSynced *state.Block, badBlockNumber uint64) (bool, *state.Block, error) { + firstBlockOK, err := s.checkReorg(lastEthBlockSynced, nil) + if err != nil { + log.Warnf("error checking reorgs. using badBlock detected: %d Err: %v", badBlockNumber, err) + firstBlockOK = nil + } + if firstBlockOK != nil && firstBlockOK.BlockNumber >= badBlockNumber { + log.Warnf("Reorg detected firstBlockOk: %d. But oldest bad block detected: %d", firstBlockOK.BlockNumber, badBlockNumber) + firstBlockOK = nil + } + // We already known a bad block, reset from there + if firstBlockOK == nil { + firstBlockOK, err = s.state.GetPreviousBlockToBlockNumber(s.ctx, badBlockNumber, nil) + if err != nil { + log.Errorf("error getting previous block %d from db. Can't execute REORG. Error: %v", badBlockNumber, err) + return false, lastEthBlockSynced, err + } + } + newFirstBlock, err := s.executeReorgFromFirstValidBlock(lastEthBlockSynced, firstBlockOK) + if err != nil { + log.Errorf("error executing reorg. Retrying... Err: %v", err) + return false, lastEthBlockSynced, fmt.Errorf("error executing reorg. Err: %w", err) + } + return true, newFirstBlock, nil +} + +// checkReorgAndExecuteReset function will check if there is a reorg and execute the reset +// returns true is reset have been done +func (s *ClientSynchronizer) checkReorgAndExecuteReset(lastEthBlockSynced *state.Block) (bool, *state.Block, error) { + var err error + + block, err := s.checkReorg(lastEthBlockSynced, nil) + if err != nil { + log.Errorf("error checking reorgs. Retrying... Err: %v", err) + return false, lastEthBlockSynced, fmt.Errorf("error checking reorgs") + } + if block != nil { + newFirstBlock, err := s.executeReorgFromFirstValidBlock(lastEthBlockSynced, block) + if err != nil { + log.Errorf("error executing reorg. Retrying... Err: %v", err) + return false, lastEthBlockSynced, fmt.Errorf("error executing reorg. Err: %w", err) + } + return true, newFirstBlock, nil + } + + return false, lastEthBlockSynced, nil +} + +func (s *ClientSynchronizer) executeReorgFromFirstValidBlock(lastEthBlockSynced *state.Block, firstValidBlock *state.Block) (*state.Block, error) { + log.Infof("reorg detected. Resetting the state from block %v to block %v", lastEthBlockSynced.BlockNumber, firstValidBlock.BlockNumber) + s.CleanTrustedState() + err := s.resetState(firstValidBlock.BlockNumber) + if err != nil { + log.Errorf("error resetting the state to a previous block. Retrying... Err: %s", err.Error()) + return nil, fmt.Errorf("error resetting the state to a previous block. Err: %w", err) + } + newLastBlock, err := s.state.GetLastBlock(s.ctx, nil) + if err != nil { + log.Warnf("error getting last block synced from db, returning expected block %d. Error: %v", firstValidBlock.BlockNumber, err) + return firstValidBlock, nil + } + if newLastBlock.BlockNumber != firstValidBlock.BlockNumber { + log.Warnf("Doesnt match LastBlock on State and expecting one after a resetState. The block in state is %d and the expected block is %d", newLastBlock.BlockNumber, + firstValidBlock.BlockNumber) + return firstValidBlock, nil + } + return newLastBlock, nil +} + +func (s *ClientSynchronizer) checkReorg(latestBlock *state.Block, syncedBlock *etherman.Block) (*state.Block, error) { + if latestBlock == nil { + err := fmt.Errorf("lastEthBlockSynced is nil calling checkReorgAndExecuteReset") + log.Errorf("%s, it never have to happens", err.Error()) + return nil, err + } + block, errReturnedReorgFunction := s.newCheckReorg(latestBlock, syncedBlock) + if s.asyncL1BlockChecker != nil { + return s.asyncL1BlockChecker.CheckReorgWrapper(s.ctx, block, errReturnedReorgFunction) + } + return block, errReturnedReorgFunction +} + /* This function will check if there is a reorg. As input param needs the last ethereum block synced. Retrieve the block info from the blockchain @@ -795,34 +966,47 @@ If hash or hash parent don't match, reorg detected and the function will return must be reverted. Then, check the previous ethereum block synced, get block info from the blockchain and check hash and has parent. This operation has to be done until a match is found. */ -func (s *ClientSynchronizer) checkReorg(latestBlock *state.Block) (*state.Block, error) { + +func (s *ClientSynchronizer) newCheckReorg(latestStoredBlock *state.Block, syncedBlock *etherman.Block) (*state.Block, error) { // This function only needs to worry about reorgs if some of the reorganized blocks contained rollup info. - latestEthBlockSynced := *latestBlock - reorgedBlock := *latestBlock + latestStoredEthBlock := *latestStoredBlock + reorgedBlock := *latestStoredBlock var depth uint64 + block := syncedBlock for { - block, err := s.etherMan.EthBlockByNumber(s.ctx, reorgedBlock.BlockNumber) - if err != nil { - log.Errorf("error getting latest block synced from blockchain. Block: %d, error: %v", reorgedBlock.BlockNumber, err) - return nil, err - } - log.Infof("[checkReorg function] BlockNumber: %d BlockHash got from L1 provider: %s", block.Number().Uint64(), block.Hash().String()) - log.Infof("[checkReorg function] latestBlockNumber: %d latestBlockHash already synced: %s", latestBlock.BlockNumber, latestBlock.BlockHash.String()) - if block.NumberU64() != reorgedBlock.BlockNumber { - err = fmt.Errorf("wrong ethereum block retrieved from blockchain. Block numbers don't match. BlockNumber stored: %d. BlockNumber retrieved: %d", - reorgedBlock.BlockNumber, block.NumberU64()) - log.Error("error: ", err) - return nil, err + if block == nil { + log.Infof("[checkReorg function] Checking Block %d in L1", reorgedBlock.BlockNumber) + b, err := s.etherMan.EthBlockByNumber(s.ctx, reorgedBlock.BlockNumber) + if err != nil { + log.Errorf("error getting latest block synced from blockchain. Block: %d, error: %v", reorgedBlock.BlockNumber, err) + return nil, err + } + block = ðerman.Block{ + BlockNumber: b.Number().Uint64(), + BlockHash: b.Hash(), + ParentHash: b.ParentHash(), + } + if block.BlockNumber != reorgedBlock.BlockNumber { + err := fmt.Errorf("wrong ethereum block retrieved from blockchain. Block numbers don't match. BlockNumber stored: %d. BlockNumber retrieved: %d", + reorgedBlock.BlockNumber, block.BlockNumber) + log.Error("error: ", err) + return nil, err + } + } else { + log.Infof("[checkReorg function] Using block %d from GetRollupInfoByBlockRange", block.BlockNumber) } + log.Infof("[checkReorg function] BlockNumber: %d BlockHash got from L1 provider: %s", block.BlockNumber, block.BlockHash.String()) + log.Infof("[checkReorg function] reorgedBlockNumber: %d reorgedBlockHash already synced: %s", reorgedBlock.BlockNumber, reorgedBlock.BlockHash.String()) + // Compare hashes - if (block.Hash() != latestBlock.BlockHash || block.ParentHash() != latestBlock.ParentHash) && latestBlock.BlockNumber > s.genesis.RollupBlockNumber { - log.Infof("checkReorg: Bad block %d hashOk %t parentHashOk %t", latestBlock.BlockNumber, block.Hash() == latestBlock.BlockHash, block.ParentHash() == latestBlock.ParentHash) - log.Debug("[checkReorg function] => latestBlockNumber: ", latestBlock.BlockNumber) - log.Debug("[checkReorg function] => latestBlockHash: ", latestBlock.BlockHash) - log.Debug("[checkReorg function] => latestBlockHashParent: ", latestBlock.ParentHash) - log.Debug("[checkReorg function] => BlockNumber: ", latestBlock.BlockNumber, block.NumberU64()) - log.Debug("[checkReorg function] => BlockHash: ", block.Hash()) - log.Debug("[checkReorg function] => BlockHashParent: ", block.ParentHash()) + if (block.BlockHash != reorgedBlock.BlockHash || block.ParentHash != reorgedBlock.ParentHash) && reorgedBlock.BlockNumber > s.genesis.RollupBlockNumber { + log.Infof("checkReorg: Bad block %d hashOk %t parentHashOk %t", reorgedBlock.BlockNumber, block.BlockHash == reorgedBlock.BlockHash, block.ParentHash == reorgedBlock.ParentHash) + log.Debug("[checkReorg function] => latestBlockNumber: ", reorgedBlock.BlockNumber) + log.Debug("[checkReorg function] => latestBlockHash: ", reorgedBlock.BlockHash) + log.Debug("[checkReorg function] => latestBlockHashParent: ", reorgedBlock.ParentHash) + log.Debug("[checkReorg function] => BlockNumber: ", reorgedBlock.BlockNumber, block.BlockNumber) + log.Debug("[checkReorg function] => BlockHash: ", block.BlockHash) + log.Debug("[checkReorg function] => BlockHashParent: ", block.ParentHash) depth++ log.Debug("REORG: Looking for the latest correct ethereum block. Depth: ", depth) // Reorg detected. Getting previous block @@ -844,24 +1028,26 @@ func (s *ClientSynchronizer) checkReorg(latestBlock *state.Block) (*state.Block, return nil, errC } if errors.Is(err, state.ErrNotFound) { - log.Warn("error checking reorg: previous block not found in db: ", err) - return &state.Block{}, nil + log.Warn("error checking reorg: previous block not found in db. Reorg reached the genesis block: %v.Genesis block can't be reorged, using genesis block as starting point. Error: %v", reorgedBlock, err) + return &reorgedBlock, nil } else if err != nil { log.Error("error getting previousBlock from db. Error: ", err) return nil, err } reorgedBlock = *lb } else { - log.Debugf("checkReorg: Block %d hashOk %t parentHashOk %t", reorgedBlock.BlockNumber, block.Hash() == reorgedBlock.BlockHash, block.ParentHash() == reorgedBlock.ParentHash) + log.Debugf("checkReorg: Block %d hashOk %t parentHashOk %t", reorgedBlock.BlockNumber, block.BlockHash == reorgedBlock.BlockHash, block.ParentHash == reorgedBlock.ParentHash) break } + // This forces to get the block from L1 in the next iteration of the loop + block = nil } - if latestEthBlockSynced.BlockHash != reorgedBlock.BlockHash { - latestBlock = &reorgedBlock - log.Info("Reorg detected in block: ", latestEthBlockSynced.BlockNumber, " last block OK: ", latestBlock.BlockNumber) - return latestBlock, nil + if latestStoredEthBlock.BlockHash != reorgedBlock.BlockHash { + latestStoredBlock = &reorgedBlock + log.Info("Reorg detected in block: ", latestStoredEthBlock.BlockNumber, " last block OK: ", latestStoredBlock.BlockNumber) + return latestStoredBlock, nil } - log.Debugf("No reorg detected in block: %d. BlockHash: %s", latestEthBlockSynced.BlockNumber, latestEthBlockSynced.BlockHash.String()) + log.Debugf("No reorg detected in block: %d. BlockHash: %s", latestStoredEthBlock.BlockNumber, latestStoredEthBlock.BlockHash.String()) return nil, nil } diff --git a/synchronizer/synchronizer_test.go b/synchronizer/synchronizer_test.go index 349e509788..d8ac74d086 100644 --- a/synchronizer/synchronizer_test.go +++ b/synchronizer/synchronizer_test.go @@ -2,6 +2,7 @@ package synchronizer import ( context "context" + "math" "math/big" "testing" "time" @@ -121,13 +122,16 @@ func TestGivenPermissionlessNodeWhenSyncronizeFirstTimeABatchThenStoreItInALocal // but it used a feature that is not implemented in new one that is asking beyond the last block on L1 func TestForcedBatchEtrog(t *testing.T) { genesis := state.Genesis{ - RollupBlockNumber: uint64(123456), + RollupBlockNumber: uint64(0), } cfg := Config{ SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, SyncChunkSize: 10, L1SynchronizationMode: SequentialMode, SyncBlockProtection: "latest", + L1BlockCheck: L1BlockCheckConfig{ + Enable: false, + }, } m := mocks{ @@ -154,17 +158,21 @@ func TestForcedBatchEtrog(t *testing.T) { Run(func(args mock.Arguments) { ctx := args[0].(context.Context) parentHash := common.HexToHash("0x111") - ethHeader := ðTypes.Header{Number: big.NewInt(1), ParentHash: parentHash} - ethBlock := ethTypes.NewBlockWithHeader(ethHeader) - lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64()} + ethHeader0 := ðTypes.Header{Number: big.NewInt(0), ParentHash: parentHash} + ethBlock0 := ethTypes.NewBlockWithHeader(ethHeader0) + ethHeader1 := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash()} + ethBlock1 := ethTypes.NewBlockWithHeader(ethHeader1) + lastBlock0 := &state.Block{BlockHash: ethBlock0.Hash(), BlockNumber: ethBlock0.Number().Uint64(), ParentHash: ethBlock0.ParentHash()} + lastBlock1 := &state.Block{BlockHash: ethBlock1.Hash(), BlockNumber: ethBlock1.Number().Uint64(), ParentHash: ethBlock1.ParentHash()} m.State. On("GetForkIDByBatchNumber", mock.Anything). Return(uint64(7), nil). Maybe() + m.State. On("GetLastBlock", ctx, m.DbTx). - Return(lastBlock, nil). + Return(lastBlock0, nil). Once() m.State. @@ -200,14 +208,14 @@ func TestForcedBatchEtrog(t *testing.T) { Return(nil) m.Etherman. - On("EthBlockByNumber", ctx, lastBlock.BlockNumber). - Return(ethBlock, nil). - Once() + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Times(2) n := big.NewInt(rpc.LatestBlockNumber.Int64()) m.Etherman. On("HeaderByNumber", mock.Anything, n). - Return(ethHeader, nil). + Return(ethHeader1, nil). Once() t := time.Date(2024, 1, 1, 1, 0, 0, 0, time.UTC) @@ -226,7 +234,7 @@ func TestForcedBatchEtrog(t *testing.T) { } forceb := []etherman.ForcedBatch{{ - BlockNumber: lastBlock.BlockNumber, + BlockNumber: lastBlock1.BlockNumber, ForcedBatchNumber: 1, Sequencer: sequencedBatch.Coinbase, GlobalExitRoot: sequencedBatch.PolygonRollupBaseEtrogBatchData.ForcedGlobalExitRoot, @@ -234,16 +242,21 @@ func TestForcedBatchEtrog(t *testing.T) { ForcedAt: time.Unix(int64(sequencedBatch.PolygonRollupBaseEtrogBatchData.ForcedTimestamp), 0), }} - ethermanBlock := etherman.Block{ + ethermanBlock0 := etherman.Block{ + BlockNumber: 0, + ReceivedAt: t, + BlockHash: ethBlock0.Hash(), + } + ethermanBlock1 := etherman.Block{ BlockNumber: 1, ReceivedAt: t, - BlockHash: ethBlock.Hash(), + BlockHash: ethBlock1.Hash(), SequencedBatches: [][]etherman.SequencedBatch{{sequencedBatch}}, ForcedBatches: forceb, } - blocks := []etherman.Block{ethermanBlock} + blocks := []etherman.Block{ethermanBlock0, ethermanBlock1} order := map[common.Hash][]etherman.Order{ - ethBlock.Hash(): { + ethBlock1.Hash(): { { Name: etherman.ForcedBatchesOrder, Pos: 0, @@ -255,21 +268,16 @@ func TestForcedBatchEtrog(t *testing.T) { }, } - fromBlock := ethBlock.NumberU64() + 1 + fromBlock := ethBlock0.NumberU64() toBlock := fromBlock + cfg.SyncChunkSize - if toBlock > ethHeader.Number.Uint64() { - toBlock = ethHeader.Number.Uint64() + if toBlock > ethBlock1.NumberU64() { + toBlock = ethBlock1.NumberU64() } m.Etherman. On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). Return(blocks, order, nil). Once() - m.Etherman. - On("EthBlockByNumber", ctx, lastBlock.BlockNumber). - Return(ethBlock, nil). - Once() - m.ZKEVMClient. On("BatchNumber", ctx). Return(uint64(1), nil) @@ -280,10 +288,11 @@ func TestForcedBatchEtrog(t *testing.T) { Once() stateBlock := &state.Block{ - BlockNumber: ethermanBlock.BlockNumber, - BlockHash: ethermanBlock.BlockHash, - ParentHash: ethermanBlock.ParentHash, - ReceivedAt: ethermanBlock.ReceivedAt, + BlockNumber: ethermanBlock1.BlockNumber, + BlockHash: ethermanBlock1.BlockHash, + ParentHash: ethermanBlock1.ParentHash, + ReceivedAt: ethermanBlock1.ReceivedAt, + Checked: true, } executionResponse := executor.ProcessBatchResponseV2{ @@ -295,13 +304,18 @@ func TestForcedBatchEtrog(t *testing.T) { Return(&executionResponse, nil). Times(1) + m.Etherman. + On("GetFinalizedBlockNumber", ctx). + Return(ethBlock1.NumberU64(), nil). + Once() + m.State. On("AddBlock", ctx, stateBlock, m.DbTx). Return(nil). Once() fb := []state.ForcedBatch{{ - BlockNumber: lastBlock.BlockNumber, + BlockNumber: lastBlock1.BlockNumber, ForcedBatchNumber: 1, Sequencer: sequencedBatch.Coinbase, GlobalExitRoot: sequencedBatch.PolygonRollupBaseEtrogBatchData.ForcedGlobalExitRoot, @@ -336,7 +350,7 @@ func TestForcedBatchEtrog(t *testing.T) { BatchNumber: sequencedBatch.BatchNumber, TxHash: sequencedBatch.TxHash, Coinbase: sequencedBatch.Coinbase, - BlockNumber: ethermanBlock.BlockNumber, + BlockNumber: ethermanBlock1.BlockNumber, TimestampBatchEtrog: &t, L1InfoRoot: &forcedGER, } @@ -409,21 +423,20 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { Run(func(args mock.Arguments) { ctx := args[0].(context.Context) parentHash := common.HexToHash("0x111") - ethHeader := ðTypes.Header{Number: big.NewInt(1), ParentHash: parentHash} - ethBlock := ethTypes.NewBlockWithHeader(ethHeader) - lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64()} + ethHeader0 := ðTypes.Header{Number: big.NewInt(0), ParentHash: parentHash} + ethBlock0 := ethTypes.NewBlockWithHeader(ethHeader0) + ethHeader1 := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash()} + ethBlock1 := ethTypes.NewBlockWithHeader(ethHeader1) + lastBlock0 := &state.Block{BlockHash: ethBlock0.Hash(), BlockNumber: ethBlock0.Number().Uint64(), ParentHash: ethBlock0.ParentHash()} + lastBlock1 := &state.Block{BlockHash: ethBlock1.Hash(), BlockNumber: ethBlock1.Number().Uint64(), ParentHash: ethBlock1.ParentHash()} m.State. On("GetForkIDByBatchNumber", mock.Anything). Return(uint64(1), nil). Maybe() - m.State. - On("GetForkIDByBlockNumber", mock.Anything). - Return(uint64(1), nil). - Maybe() m.State. On("GetLastBlock", ctx, m.DbTx). - Return(lastBlock, nil). + Return(lastBlock0, nil). Once() m.State. @@ -461,15 +474,15 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { Return(nil). Once() + n := big.NewInt(rpc.LatestBlockNumber.Int64()) m.Etherman. - On("EthBlockByNumber", ctx, lastBlock.BlockNumber). - Return(ethBlock, nil). + On("HeaderByNumber", ctx, n). + Return(ethHeader1, nil). Once() - n := big.NewInt(rpc.LatestBlockNumber.Int64()) m.Etherman. - On("HeaderByNumber", ctx, n). - Return(ethHeader, nil). + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). Once() sequencedForceBatch := etherman.SequencedForceBatch{ @@ -485,7 +498,7 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { } forceb := []etherman.ForcedBatch{{ - BlockNumber: lastBlock.BlockNumber, + BlockNumber: lastBlock1.BlockNumber, ForcedBatchNumber: 1, Sequencer: sequencedForceBatch.Coinbase, GlobalExitRoot: sequencedForceBatch.PolygonRollupBaseEtrogBatchData.ForcedGlobalExitRoot, @@ -493,14 +506,21 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { ForcedAt: time.Unix(int64(sequencedForceBatch.PolygonRollupBaseEtrogBatchData.ForcedTimestamp), 0), }} - ethermanBlock := etherman.Block{ - BlockHash: ethBlock.Hash(), + ethermanBlock0 := etherman.Block{ + BlockNumber: ethBlock0.NumberU64(), + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + } + ethermanBlock1 := etherman.Block{ + BlockNumber: ethBlock1.NumberU64(), + BlockHash: ethBlock1.Hash(), + ParentHash: ethBlock1.ParentHash(), SequencedForceBatches: [][]etherman.SequencedForceBatch{{sequencedForceBatch}}, ForcedBatches: forceb, } - blocks := []etherman.Block{ethermanBlock} + blocks := []etherman.Block{ethermanBlock0, ethermanBlock1} order := map[common.Hash][]etherman.Order{ - ethBlock.Hash(): { + ethBlock1.Hash(): { { Name: etherman.ForcedBatchesOrder, Pos: 0, @@ -512,10 +532,10 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { }, } - fromBlock := ethBlock.NumberU64() + 1 + fromBlock := ethBlock0.NumberU64() toBlock := fromBlock + cfg.SyncChunkSize - if toBlock > ethHeader.Number.Uint64() { - toBlock = ethHeader.Number.Uint64() + if toBlock > ethBlock1.NumberU64() { + toBlock = ethBlock1.NumberU64() } m.Etherman. On("GetRollupInfoByBlockRange", ctx, fromBlock, &toBlock). @@ -523,8 +543,8 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { Once() m.Etherman. - On("EthBlockByNumber", ctx, lastBlock.BlockNumber). - Return(ethBlock, nil). + On("GetFinalizedBlockNumber", ctx). + Return(ethBlock1.NumberU64(), nil). Once() m.State. @@ -533,10 +553,11 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { Once() stateBlock := &state.Block{ - BlockNumber: ethermanBlock.BlockNumber, - BlockHash: ethermanBlock.BlockHash, - ParentHash: ethermanBlock.ParentHash, - ReceivedAt: ethermanBlock.ReceivedAt, + BlockNumber: ethermanBlock1.BlockNumber, + BlockHash: ethermanBlock1.BlockHash, + ParentHash: ethermanBlock1.ParentHash, + ReceivedAt: ethermanBlock1.ReceivedAt, + Checked: true, } m.State. @@ -544,8 +565,13 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { Return(nil). Once() + m.State. + On("GetForkIDByBlockNumber", stateBlock.BlockNumber). + Return(uint64(9), nil). + Once() + fb := []state.ForcedBatch{{ - BlockNumber: lastBlock.BlockNumber, + BlockNumber: lastBlock1.BlockNumber, ForcedBatchNumber: 1, Sequencer: sequencedForceBatch.Coinbase, GlobalExitRoot: sequencedForceBatch.PolygonRollupBaseEtrogBatchData.ForcedGlobalExitRoot, @@ -577,7 +603,7 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { processingContext := state.ProcessingContext{ BatchNumber: sequencedForceBatch.BatchNumber, Coinbase: sequencedForceBatch.Coinbase, - Timestamp: ethBlock.ReceivedAt, + Timestamp: ethBlock1.ReceivedAt, GlobalExitRoot: sequencedForceBatch.PolygonRollupBaseEtrogBatchData.ForcedGlobalExitRoot, ForcedBatchNum: &f, BatchL2Data: &sequencedForceBatch.PolygonRollupBaseEtrogBatchData.Transactions, @@ -592,7 +618,7 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { TxHash: sequencedForceBatch.TxHash, Coinbase: sequencedForceBatch.Coinbase, SequencerAddr: sequencedForceBatch.Coinbase, - BlockNumber: ethermanBlock.BlockNumber, + BlockNumber: ethermanBlock1.BlockNumber, } m.State. @@ -616,7 +642,10 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { m.DbTx. On("Commit", ctx). - Run(func(args mock.Arguments) { sync.Stop() }). + Run(func(args mock.Arguments) { + sync.Stop() + ctx.Done() + }). Return(nil). Once() }). @@ -890,3 +919,1365 @@ func expectedCallsForsyncTrustedState(t *testing.T, m *mocks, sync *ClientSynchr Return(nil). Once() } + +func TestReorg(t *testing.T) { + genesis := state.Genesis{ + RollupBlockNumber: uint64(0), + } + cfg := Config{ + SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, + SyncChunkSize: 3, + L1SynchronizationMode: SequentialMode, + SyncBlockProtection: "latest", + L1BlockCheck: L1BlockCheckConfig{ + Enable: false, + }, + } + + m := mocks{ + Etherman: mock_syncinterfaces.NewEthermanFullInterface(t), + State: mock_syncinterfaces.NewStateFullInterface(t), + Pool: mock_syncinterfaces.NewPoolInterface(t), + DbTx: syncMocks.NewDbTxMock(t), + ZKEVMClient: mock_syncinterfaces.NewZKEVMClientInterface(t), + EthTxManager: mock_syncinterfaces.NewEthTxManager(t), + } + ethermanForL1 := []syncinterfaces.EthermanFullInterface{m.Etherman} + sync, err := NewSynchronizer(false, m.Etherman, ethermanForL1, m.State, m.Pool, m.EthTxManager, m.ZKEVMClient, m.zkEVMClientEthereumCompatible, nil, genesis, cfg, false) + require.NoError(t, err) + + // state preparation + ctxMatchBy := mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil }) + forkIdInterval := state.ForkIDInterval{ + ForkId: 9, + FromBatchNumber: 0, + ToBatchNumber: math.MaxUint64, + } + m.State.EXPECT().GetForkIDInMemory(uint64(9)).Return(&forkIdInterval) + + m.State. + On("BeginStateTransaction", ctxMatchBy). + Run(func(args mock.Arguments) { + ctx := args[0].(context.Context) + parentHash := common.HexToHash("0x111") + ethHeader0 := ðTypes.Header{Number: big.NewInt(0), ParentHash: parentHash} + ethBlock0 := ethTypes.NewBlockWithHeader(ethHeader0) + ethHeader1bis := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash(), Time: 10, GasUsed: 20, Root: common.HexToHash("0x234")} + ethBlock1bis := ethTypes.NewBlockWithHeader(ethHeader1bis) + ethHeader2bis := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1bis.Hash()} + ethBlock2bis := ethTypes.NewBlockWithHeader(ethHeader2bis) + ethHeader3bis := ðTypes.Header{Number: big.NewInt(3), ParentHash: ethBlock2bis.Hash()} + ethBlock3bis := ethTypes.NewBlockWithHeader(ethHeader3bis) + ethHeader1 := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash()} + ethBlock1 := ethTypes.NewBlockWithHeader(ethHeader1) + ethHeader2 := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1.Hash()} + ethBlock2 := ethTypes.NewBlockWithHeader(ethHeader2) + ethHeader3 := ðTypes.Header{Number: big.NewInt(3), ParentHash: ethBlock2.Hash()} + ethBlock3 := ethTypes.NewBlockWithHeader(ethHeader3) + + lastBlock0 := &state.Block{BlockHash: ethBlock0.Hash(), BlockNumber: ethBlock0.Number().Uint64(), ParentHash: ethBlock0.ParentHash()} + lastBlock1 := &state.Block{BlockHash: ethBlock1.Hash(), BlockNumber: ethBlock1.Number().Uint64(), ParentHash: ethBlock1.ParentHash()} + + m.State. + On("GetForkIDByBatchNumber", mock.Anything). + Return(uint64(9), nil). + Maybe() + m.State. + On("GetLastBlock", ctx, m.DbTx). + Return(lastBlock1, nil). + Once() + + m.State. + On("GetLastBatchNumber", ctx, m.DbTx). + Return(uint64(10), nil). + Once() + + m.State. + On("SetInitSyncBatch", ctx, uint64(10), m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("GetLatestBatchNumber"). + Return(uint64(10), nil) + + var nilDbTx pgx.Tx + m.State. + On("GetLastBatchNumber", ctx, nilDbTx). + Return(uint64(10), nil) + + m.Etherman. + On("GetLatestVerifiedBatchNum"). + Return(uint64(10), nil) + + m.State. + On("SetLastBatchInfoSeenOnEthereum", ctx, uint64(10), uint64(10), nilDbTx). + Return(nil) + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + n := big.NewInt(rpc.LatestBlockNumber.Int64()) + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader3bis, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1, nil). + Once() + + ti := time.Date(2024, 1, 1, 1, 0, 0, 0, time.UTC) + + ethermanBlock1bis := etherman.Block{ + BlockNumber: 1, + ReceivedAt: ti, + BlockHash: ethBlock1bis.Hash(), + ParentHash: ethBlock1bis.ParentHash(), + } + ethermanBlock2bis := etherman.Block{ + BlockNumber: 2, + ReceivedAt: ti, + BlockHash: ethBlock2bis.Hash(), + ParentHash: ethBlock2bis.ParentHash(), + } + blocks := []etherman.Block{ethermanBlock1bis, ethermanBlock2bis} + order := map[common.Hash][]etherman.Order{} + + fromBlock := ethBlock1.NumberU64() + toBlock := fromBlock + cfg.SyncChunkSize + if toBlock > ethBlock3.NumberU64() { + toBlock = ethBlock3.NumberU64() + } + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + var depth uint64 = 1 + stateBlock0 := &state.Block{ + BlockNumber: ethBlock0.NumberU64(), + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + ReceivedAt: ti, + } + m.State. + On("GetPreviousBlock", ctx, depth, m.DbTx). + Return(stateBlock0, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + m.State. + On("Reset", ctx, ethBlock0.NumberU64(), m.DbTx). + Return(nil). + Once() + + m.EthTxManager. + On("Reorg", ctx, ethBlock0.NumberU64()+1, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader3bis, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + ethermanBlock0 := etherman.Block{ + BlockNumber: 0, + ReceivedAt: ti, + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + } + ethermanBlock3bis := etherman.Block{ + BlockNumber: 3, + ReceivedAt: ti, + BlockHash: ethBlock3bis.Hash(), + ParentHash: ethBlock3bis.ParentHash(), + } + fromBlock = 0 + blocks2 := []etherman.Block{ethermanBlock0, ethermanBlock1bis, ethermanBlock2bis, ethermanBlock3bis} + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks2, order, nil). + Once() + + m.Etherman. + On("GetFinalizedBlockNumber", ctx). + Return(ethBlock2bis.NumberU64(), nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock1bis := &state.Block{ + BlockNumber: ethermanBlock1bis.BlockNumber, + BlockHash: ethermanBlock1bis.BlockHash, + ParentHash: ethermanBlock1bis.ParentHash, + ReceivedAt: ethermanBlock1bis.ReceivedAt, + Checked: true, + } + m.State. + On("AddBlock", ctx, stateBlock1bis, m.DbTx). + Return(nil). + Once() + + m.State. + On("GetStoredFlushID", ctx). + Return(uint64(1), cProverIDExecution, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock2bis := &state.Block{ + BlockNumber: ethermanBlock2bis.BlockNumber, + BlockHash: ethermanBlock2bis.BlockHash, + ParentHash: ethermanBlock2bis.ParentHash, + ReceivedAt: ethermanBlock2bis.ReceivedAt, + Checked: true, + } + m.State. + On("AddBlock", ctx, stateBlock2bis, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock3bis := &state.Block{ + BlockNumber: ethermanBlock3bis.BlockNumber, + BlockHash: ethermanBlock3bis.BlockHash, + ParentHash: ethermanBlock3bis.ParentHash, + ReceivedAt: ethermanBlock3bis.ReceivedAt, + Checked: false, + } + m.State. + On("AddBlock", ctx, stateBlock3bis, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Run(func(args mock.Arguments) { + sync.Stop() + ctx.Done() + }). + Once() + }). + Return(m.DbTx, nil). + Once() + + err = sync.Sync() + require.NoError(t, err) +} + +func TestLatestSyncedBlockEmpty(t *testing.T) { + genesis := state.Genesis{ + RollupBlockNumber: uint64(0), + } + cfg := Config{ + SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, + SyncChunkSize: 3, + L1SynchronizationMode: SequentialMode, + SyncBlockProtection: "latest", + L1BlockCheck: L1BlockCheckConfig{ + Enable: false, + }, + } + + m := mocks{ + Etherman: mock_syncinterfaces.NewEthermanFullInterface(t), + State: mock_syncinterfaces.NewStateFullInterface(t), + Pool: mock_syncinterfaces.NewPoolInterface(t), + DbTx: syncMocks.NewDbTxMock(t), + ZKEVMClient: mock_syncinterfaces.NewZKEVMClientInterface(t), + EthTxManager: mock_syncinterfaces.NewEthTxManager(t), + } + ethermanForL1 := []syncinterfaces.EthermanFullInterface{m.Etherman} + sync, err := NewSynchronizer(false, m.Etherman, ethermanForL1, m.State, m.Pool, m.EthTxManager, m.ZKEVMClient, m.zkEVMClientEthereumCompatible, nil, genesis, cfg, false) + require.NoError(t, err) + + // state preparation + ctxMatchBy := mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil }) + forkIdInterval := state.ForkIDInterval{ + ForkId: 9, + FromBatchNumber: 0, + ToBatchNumber: math.MaxUint64, + } + m.State.EXPECT().GetForkIDInMemory(uint64(9)).Return(&forkIdInterval) + + m.State. + On("BeginStateTransaction", ctxMatchBy). + Run(func(args mock.Arguments) { + ctx := args[0].(context.Context) + parentHash := common.HexToHash("0x111") + ethHeader0 := ðTypes.Header{Number: big.NewInt(0), ParentHash: parentHash} + ethBlock0 := ethTypes.NewBlockWithHeader(ethHeader0) + ethHeader1 := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash()} + ethBlock1 := ethTypes.NewBlockWithHeader(ethHeader1) + ethHeader2 := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1.Hash()} + ethBlock2 := ethTypes.NewBlockWithHeader(ethHeader2) + ethHeader3 := ðTypes.Header{Number: big.NewInt(3), ParentHash: ethBlock2.Hash()} + ethBlock3 := ethTypes.NewBlockWithHeader(ethHeader3) + + lastBlock0 := &state.Block{BlockHash: ethBlock0.Hash(), BlockNumber: ethBlock0.Number().Uint64(), ParentHash: ethBlock0.ParentHash()} + lastBlock1 := &state.Block{BlockHash: ethBlock1.Hash(), BlockNumber: ethBlock1.Number().Uint64(), ParentHash: ethBlock1.ParentHash()} + + m.State. + On("GetForkIDByBatchNumber", mock.Anything). + Return(uint64(9), nil). + Maybe() + m.State. + On("GetLastBlock", ctx, m.DbTx). + Return(lastBlock1, nil). + Once() + + m.State. + On("GetLastBatchNumber", ctx, m.DbTx). + Return(uint64(10), nil). + Once() + + m.State. + On("SetInitSyncBatch", ctx, uint64(10), m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("GetLatestBatchNumber"). + Return(uint64(10), nil) + + var nilDbTx pgx.Tx + m.State. + On("GetLastBatchNumber", ctx, nilDbTx). + Return(uint64(10), nil) + + m.Etherman. + On("GetLatestVerifiedBatchNum"). + Return(uint64(10), nil) + + m.State. + On("SetLastBatchInfoSeenOnEthereum", ctx, uint64(10), uint64(10), nilDbTx). + Return(nil) + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + n := big.NewInt(rpc.LatestBlockNumber.Int64()) + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader3, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1, nil). + Once() + + blocks := []etherman.Block{} + order := map[common.Hash][]etherman.Order{} + + fromBlock := ethBlock1.NumberU64() + toBlock := fromBlock + cfg.SyncChunkSize + if toBlock > ethBlock3.NumberU64() { + toBlock = ethBlock3.NumberU64() + } + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + ti := time.Date(2024, 1, 1, 1, 0, 0, 0, time.UTC) + var depth uint64 = 1 + stateBlock0 := &state.Block{ + BlockNumber: ethBlock0.NumberU64(), + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + ReceivedAt: ti, + } + m.State. + On("GetPreviousBlock", ctx, depth, nil). + Return(stateBlock0, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + m.State. + On("Reset", ctx, ethBlock0.NumberU64(), m.DbTx). + Return(nil). + Once() + + m.EthTxManager. + On("Reorg", ctx, ethBlock0.NumberU64()+1, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader3, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + ethermanBlock0 := etherman.Block{ + BlockNumber: 0, + ReceivedAt: ti, + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + } + blocks = []etherman.Block{ethermanBlock0} + fromBlock = 0 + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + m.Etherman. + On("GetFinalizedBlockNumber", ctx). + Return(ethBlock3.NumberU64(), nil). + Run(func(args mock.Arguments) { + sync.Stop() + ctx.Done() + }). + Once() + }). + Return(m.DbTx, nil). + Once() + + err = sync.Sync() + require.NoError(t, err) +} + +func TestRegularReorg(t *testing.T) { + genesis := state.Genesis{ + RollupBlockNumber: uint64(0), + } + cfg := Config{ + SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, + SyncChunkSize: 3, + L1SynchronizationMode: SequentialMode, + SyncBlockProtection: "latest", + L1BlockCheck: L1BlockCheckConfig{ + Enable: false, + }, + } + + m := mocks{ + Etherman: mock_syncinterfaces.NewEthermanFullInterface(t), + State: mock_syncinterfaces.NewStateFullInterface(t), + Pool: mock_syncinterfaces.NewPoolInterface(t), + DbTx: syncMocks.NewDbTxMock(t), + ZKEVMClient: mock_syncinterfaces.NewZKEVMClientInterface(t), + EthTxManager: mock_syncinterfaces.NewEthTxManager(t), + } + ethermanForL1 := []syncinterfaces.EthermanFullInterface{m.Etherman} + sync, err := NewSynchronizer(false, m.Etherman, ethermanForL1, m.State, m.Pool, m.EthTxManager, m.ZKEVMClient, m.zkEVMClientEthereumCompatible, nil, genesis, cfg, false) + require.NoError(t, err) + + // state preparation + ctxMatchBy := mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil }) + forkIdInterval := state.ForkIDInterval{ + ForkId: 9, + FromBatchNumber: 0, + ToBatchNumber: math.MaxUint64, + } + m.State.EXPECT().GetForkIDInMemory(uint64(9)).Return(&forkIdInterval) + + m.State. + On("BeginStateTransaction", ctxMatchBy). + Run(func(args mock.Arguments) { + ctx := args[0].(context.Context) + parentHash := common.HexToHash("0x111") + ethHeader0 := ðTypes.Header{Number: big.NewInt(0), ParentHash: parentHash} + ethBlock0 := ethTypes.NewBlockWithHeader(ethHeader0) + ethHeader1bis := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash(), Time: 10, GasUsed: 20, Root: common.HexToHash("0x234")} + ethBlock1bis := ethTypes.NewBlockWithHeader(ethHeader1bis) + ethHeader2bis := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1bis.Hash()} + ethBlock2bis := ethTypes.NewBlockWithHeader(ethHeader2bis) + ethHeader1 := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash()} + ethBlock1 := ethTypes.NewBlockWithHeader(ethHeader1) + ethHeader2 := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1.Hash()} + ethBlock2 := ethTypes.NewBlockWithHeader(ethHeader2) + + lastBlock0 := &state.Block{BlockHash: ethBlock0.Hash(), BlockNumber: ethBlock0.Number().Uint64(), ParentHash: ethBlock0.ParentHash()} + lastBlock1 := &state.Block{BlockHash: ethBlock1.Hash(), BlockNumber: ethBlock1.Number().Uint64(), ParentHash: ethBlock1.ParentHash()} + + m.State. + On("GetForkIDByBatchNumber", mock.Anything). + Return(uint64(9), nil). + Maybe() + m.State. + On("GetLastBlock", ctx, m.DbTx). + Return(lastBlock1, nil). + Once() + + // After a ResetState get lastblock that must be block 0 + m.State. + On("GetLastBlock", ctx, nil). + Return(lastBlock0, nil). + Once() + + m.State. + On("GetLastBatchNumber", ctx, m.DbTx). + Return(uint64(10), nil). + Once() + + m.State. + On("SetInitSyncBatch", ctx, uint64(10), m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("GetLatestBatchNumber"). + Return(uint64(10), nil) + + var nilDbTx pgx.Tx + m.State. + On("GetLastBatchNumber", ctx, nilDbTx). + Return(uint64(10), nil) + + m.Etherman. + On("GetLatestVerifiedBatchNum"). + Return(uint64(10), nil) + + m.State. + On("SetLastBatchInfoSeenOnEthereum", ctx, uint64(10), uint64(10), nilDbTx). + Return(nil) + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + n := big.NewInt(rpc.LatestBlockNumber.Int64()) + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1bis, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + ti := time.Date(2024, 1, 1, 1, 0, 0, 0, time.UTC) + var depth uint64 = 1 + stateBlock0 := &state.Block{ + BlockNumber: ethBlock0.NumberU64(), + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + ReceivedAt: ti, + } + m.State. + On("GetPreviousBlock", ctx, depth, m.DbTx). + Return(stateBlock0, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + m.State. + On("Reset", ctx, ethBlock0.NumberU64(), m.DbTx). + Return(nil). + Once() + + m.EthTxManager. + On("Reorg", ctx, ethBlock0.NumberU64()+1, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader2bis, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + ethermanBlock0 := etherman.Block{ + BlockNumber: 0, + ReceivedAt: ti, + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + } + ethermanBlock1bis := etherman.Block{ + BlockNumber: 1, + ReceivedAt: ti, + BlockHash: ethBlock1bis.Hash(), + ParentHash: ethBlock1bis.ParentHash(), + } + ethermanBlock2bis := etherman.Block{ + BlockNumber: 2, + ReceivedAt: ti, + BlockHash: ethBlock2bis.Hash(), + ParentHash: ethBlock2bis.ParentHash(), + } + blocks := []etherman.Block{ethermanBlock0, ethermanBlock1bis, ethermanBlock2bis} + order := map[common.Hash][]etherman.Order{} + + fromBlock := ethBlock0.NumberU64() + toBlock := fromBlock + cfg.SyncChunkSize + if toBlock > ethBlock2.NumberU64() { + toBlock = ethBlock2.NumberU64() + } + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + m.Etherman. + On("GetFinalizedBlockNumber", ctx). + Return(ethBlock2bis.NumberU64(), nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock1bis := &state.Block{ + BlockNumber: ethermanBlock1bis.BlockNumber, + BlockHash: ethermanBlock1bis.BlockHash, + ParentHash: ethermanBlock1bis.ParentHash, + ReceivedAt: ethermanBlock1bis.ReceivedAt, + Checked: true, + } + m.State. + On("AddBlock", ctx, stateBlock1bis, m.DbTx). + Return(nil). + Once() + + m.State. + On("GetStoredFlushID", ctx). + Return(uint64(1), cProverIDExecution, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock2bis := &state.Block{ + BlockNumber: ethermanBlock2bis.BlockNumber, + BlockHash: ethermanBlock2bis.BlockHash, + ParentHash: ethermanBlock2bis.ParentHash, + ReceivedAt: ethermanBlock2bis.ReceivedAt, + Checked: true, + } + m.State. + On("AddBlock", ctx, stateBlock2bis, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Run(func(args mock.Arguments) { + sync.Stop() + ctx.Done() + }). + Return(nil). + Once() + }). + Return(m.DbTx, nil). + Once() + + err = sync.Sync() + require.NoError(t, err) +} + +func TestLatestSyncedBlockEmptyWithExtraReorg(t *testing.T) { + genesis := state.Genesis{ + RollupBlockNumber: uint64(0), + } + cfg := Config{ + SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, + SyncChunkSize: 3, + L1SynchronizationMode: SequentialMode, + SyncBlockProtection: "latest", + L1BlockCheck: L1BlockCheckConfig{ + Enable: false, + }, + } + + m := mocks{ + Etherman: mock_syncinterfaces.NewEthermanFullInterface(t), + State: mock_syncinterfaces.NewStateFullInterface(t), + Pool: mock_syncinterfaces.NewPoolInterface(t), + DbTx: syncMocks.NewDbTxMock(t), + ZKEVMClient: mock_syncinterfaces.NewZKEVMClientInterface(t), + EthTxManager: mock_syncinterfaces.NewEthTxManager(t), + } + ethermanForL1 := []syncinterfaces.EthermanFullInterface{m.Etherman} + sync, err := NewSynchronizer(false, m.Etherman, ethermanForL1, m.State, m.Pool, m.EthTxManager, m.ZKEVMClient, m.zkEVMClientEthereumCompatible, nil, genesis, cfg, false) + require.NoError(t, err) + + // state preparation + ctxMatchBy := mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil }) + forkIdInterval := state.ForkIDInterval{ + ForkId: 9, + FromBatchNumber: 0, + ToBatchNumber: math.MaxUint64, + } + m.State.EXPECT().GetForkIDInMemory(uint64(9)).Return(&forkIdInterval) + + m.State. + On("BeginStateTransaction", ctxMatchBy). + Run(func(args mock.Arguments) { + ctx := args[0].(context.Context) + parentHash := common.HexToHash("0x111") + ethHeader0 := ðTypes.Header{Number: big.NewInt(0), ParentHash: parentHash} + ethBlock0 := ethTypes.NewBlockWithHeader(ethHeader0) + ethHeader1 := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash()} + ethBlock1 := ethTypes.NewBlockWithHeader(ethHeader1) + ethHeader1bis := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash(), Time: 0, GasUsed: 10} + ethBlock1bis := ethTypes.NewBlockWithHeader(ethHeader1bis) + ethHeader2 := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1.Hash()} + ethBlock2 := ethTypes.NewBlockWithHeader(ethHeader2) + ethHeader3 := ðTypes.Header{Number: big.NewInt(3), ParentHash: ethBlock2.Hash()} + ethBlock3 := ethTypes.NewBlockWithHeader(ethHeader3) + + lastBlock0 := &state.Block{BlockHash: ethBlock0.Hash(), BlockNumber: ethBlock0.Number().Uint64(), ParentHash: ethBlock0.ParentHash()} + lastBlock1 := &state.Block{BlockHash: ethBlock1.Hash(), BlockNumber: ethBlock1.Number().Uint64(), ParentHash: ethBlock1.ParentHash()} + lastBlock2 := &state.Block{BlockHash: ethBlock2.Hash(), BlockNumber: ethBlock2.Number().Uint64(), ParentHash: ethBlock2.ParentHash()} + + m.State. + On("GetForkIDByBatchNumber", mock.Anything). + Return(uint64(9), nil). + Maybe() + m.State. + On("GetLastBlock", ctx, m.DbTx). + Return(lastBlock2, nil). + Once() + + m.State. + On("GetLastBatchNumber", ctx, m.DbTx). + Return(uint64(10), nil). + Once() + + m.State. + On("SetInitSyncBatch", ctx, uint64(10), m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("GetLatestBatchNumber"). + Return(uint64(10), nil) + + var nilDbTx pgx.Tx + m.State. + On("GetLastBatchNumber", ctx, nilDbTx). + Return(uint64(10), nil) + + m.Etherman. + On("GetLatestVerifiedBatchNum"). + Return(uint64(10), nil) + + m.State. + On("SetLastBatchInfoSeenOnEthereum", ctx, uint64(10), uint64(10), nilDbTx). + Return(nil) + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock2.BlockNumber). + Return(ethBlock2, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + n := big.NewInt(rpc.LatestBlockNumber.Int64()) + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader3, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock2.BlockNumber). + Return(ethBlock2, nil). + Once() + + blocks := []etherman.Block{} + order := map[common.Hash][]etherman.Order{} + + fromBlock := ethBlock2.NumberU64() + toBlock := fromBlock + cfg.SyncChunkSize + if toBlock > ethBlock3.NumberU64() { + toBlock = ethBlock3.NumberU64() + } + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + ti := time.Date(2024, 1, 1, 1, 0, 0, 0, time.UTC) + var depth uint64 = 1 + stateBlock1 := &state.Block{ + BlockNumber: ethBlock1.NumberU64(), + BlockHash: ethBlock1.Hash(), + ParentHash: ethBlock1.ParentHash(), + ReceivedAt: ti, + } + m.State. + On("GetPreviousBlock", ctx, depth, nil). + Return(stateBlock1, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1bis, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock0 := &state.Block{ + BlockNumber: ethBlock0.NumberU64(), + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + ReceivedAt: ti, + } + m.State. + On("GetPreviousBlock", ctx, depth, m.DbTx). + Return(stateBlock0, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + m.State. + On("Reset", ctx, ethBlock0.NumberU64(), m.DbTx). + Return(nil). + Once() + + m.EthTxManager. + On("Reorg", ctx, ethBlock0.NumberU64()+1, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader3, nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + ethermanBlock0 := etherman.Block{ + BlockNumber: 0, + ReceivedAt: ti, + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + } + ethermanBlock1bis := etherman.Block{ + BlockNumber: 1, + ReceivedAt: ti, + BlockHash: ethBlock1.Hash(), + ParentHash: ethBlock1.ParentHash(), + } + blocks = []etherman.Block{ethermanBlock0, ethermanBlock1bis} + fromBlock = 0 + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + m.Etherman. + On("GetFinalizedBlockNumber", ctx). + Return(ethBlock3.NumberU64(), nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock1bis := &state.Block{ + BlockNumber: ethermanBlock1bis.BlockNumber, + BlockHash: ethermanBlock1bis.BlockHash, + ParentHash: ethermanBlock1bis.ParentHash, + ReceivedAt: ethermanBlock1bis.ReceivedAt, + Checked: true, + } + m.State. + On("AddBlock", ctx, stateBlock1bis, m.DbTx). + Return(nil). + Once() + + m.State. + On("GetStoredFlushID", ctx). + Return(uint64(1), cProverIDExecution, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Run(func(args mock.Arguments) { + sync.Stop() + ctx.Done() + }). + Once() + }). + Return(m.DbTx, nil). + Once() + + err = sync.Sync() + require.NoError(t, err) +} + +func TestCallFromEmptyBlockAndReorg(t *testing.T) { + genesis := state.Genesis{ + RollupBlockNumber: uint64(0), + } + cfg := Config{ + SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, + SyncChunkSize: 3, + L1SynchronizationMode: SequentialMode, + SyncBlockProtection: "latest", + L1BlockCheck: L1BlockCheckConfig{ + Enable: false, + }, + } + + m := mocks{ + Etherman: mock_syncinterfaces.NewEthermanFullInterface(t), + State: mock_syncinterfaces.NewStateFullInterface(t), + Pool: mock_syncinterfaces.NewPoolInterface(t), + DbTx: syncMocks.NewDbTxMock(t), + ZKEVMClient: mock_syncinterfaces.NewZKEVMClientInterface(t), + EthTxManager: mock_syncinterfaces.NewEthTxManager(t), + } + ethermanForL1 := []syncinterfaces.EthermanFullInterface{m.Etherman} + sync, err := NewSynchronizer(false, m.Etherman, ethermanForL1, m.State, m.Pool, m.EthTxManager, m.ZKEVMClient, m.zkEVMClientEthereumCompatible, nil, genesis, cfg, false) + require.NoError(t, err) + + // state preparation + ctxMatchBy := mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil }) + forkIdInterval := state.ForkIDInterval{ + ForkId: 9, + FromBatchNumber: 0, + ToBatchNumber: math.MaxUint64, + } + m.State.EXPECT().GetForkIDInMemory(uint64(9)).Return(&forkIdInterval) + + m.State. + On("BeginStateTransaction", ctxMatchBy). + Run(func(args mock.Arguments) { + ctx := args[0].(context.Context) + parentHash := common.HexToHash("0x111") + ethHeader0 := ðTypes.Header{Number: big.NewInt(0), ParentHash: parentHash} + ethBlock0 := ethTypes.NewBlockWithHeader(ethHeader0) + ethHeader1bis := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash(), Time: 10, GasUsed: 20, Root: common.HexToHash("0x234")} + ethBlock1bis := ethTypes.NewBlockWithHeader(ethHeader1bis) + ethHeader2bis := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1bis.Hash()} + ethBlock2bis := ethTypes.NewBlockWithHeader(ethHeader2bis) + ethHeader1 := ðTypes.Header{Number: big.NewInt(1), ParentHash: ethBlock0.Hash()} + ethBlock1 := ethTypes.NewBlockWithHeader(ethHeader1) + ethHeader2 := ðTypes.Header{Number: big.NewInt(2), ParentHash: ethBlock1.Hash()} + ethBlock2 := ethTypes.NewBlockWithHeader(ethHeader2) + + lastBlock0 := &state.Block{BlockHash: ethBlock0.Hash(), BlockNumber: ethBlock0.Number().Uint64(), ParentHash: ethBlock0.ParentHash()} + lastBlock1 := &state.Block{BlockHash: ethBlock1.Hash(), BlockNumber: ethBlock1.Number().Uint64(), ParentHash: ethBlock1.ParentHash()} + + m.State. + On("GetForkIDByBatchNumber", mock.Anything). + Return(uint64(9), nil). + Maybe() + m.State. + On("GetLastBlock", ctx, m.DbTx). + Return(lastBlock1, nil). + Once() + + m.State. + On("GetLastBatchNumber", ctx, m.DbTx). + Return(uint64(10), nil). + Once() + + m.State. + On("SetInitSyncBatch", ctx, uint64(10), m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("GetLatestBatchNumber"). + Return(uint64(10), nil) + + var nilDbTx pgx.Tx + m.State. + On("GetLastBatchNumber", ctx, nilDbTx). + Return(uint64(10), nil) + + m.Etherman. + On("GetLatestVerifiedBatchNum"). + Return(uint64(10), nil) + + m.State. + On("SetLastBatchInfoSeenOnEthereum", ctx, uint64(10), uint64(10), nilDbTx). + Return(nil) + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + n := big.NewInt(rpc.LatestBlockNumber.Int64()) + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + Return(ethBlock1, nil). + Once() + + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader2bis, nil). + Once() + + // m.Etherman. + // On("EthBlockByNumber", ctx, lastBlock1.BlockNumber). + // Return(ethBlock1, nil). + // Once() + + ti := time.Date(2024, 1, 1, 1, 0, 0, 0, time.UTC) + + ethermanBlock0 := etherman.Block{ + BlockNumber: 0, + ReceivedAt: ti, + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + } + ethermanBlock2bis := etherman.Block{ + BlockNumber: 2, + ReceivedAt: ti, + BlockHash: ethBlock2bis.Hash(), + ParentHash: ethBlock2bis.ParentHash(), + } + blocks := []etherman.Block{ethermanBlock2bis} + order := map[common.Hash][]etherman.Order{} + + fromBlock := ethBlock1.NumberU64() + toBlock := fromBlock + cfg.SyncChunkSize + if toBlock > ethBlock2.NumberU64() { + toBlock = ethBlock2.NumberU64() + } + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + var depth uint64 = 1 + stateBlock0 := &state.Block{ + BlockNumber: ethBlock0.NumberU64(), + BlockHash: ethBlock0.Hash(), + ParentHash: ethBlock0.ParentHash(), + ReceivedAt: ti, + } + m.State. + On("GetPreviousBlock", ctx, depth, m.DbTx). + Return(stateBlock0, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + m.State. + On("Reset", ctx, ethBlock0.NumberU64(), m.DbTx). + Return(nil). + Once() + + m.EthTxManager. + On("Reorg", ctx, ethBlock0.NumberU64()+1, m.DbTx). + Return(nil). + Once() + + m.DbTx. + On("Commit", ctx). + Return(nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.ZKEVMClient. + On("BatchNumber", ctx). + Return(uint64(1), nil). + Once() + + m.Etherman. + On("EthBlockByNumber", ctx, lastBlock0.BlockNumber). + Return(ethBlock0, nil). + Once() + + m.Etherman. + On("HeaderByNumber", mock.Anything, n). + Return(ethHeader2bis, nil). + Once() + + blocks = []etherman.Block{ethermanBlock0, ethermanBlock2bis} + fromBlock = ethBlock0.NumberU64() + toBlock = fromBlock + cfg.SyncChunkSize + if toBlock > ethBlock2.NumberU64() { + toBlock = ethBlock2.NumberU64() + } + m.Etherman. + On("GetRollupInfoByBlockRange", mock.Anything, fromBlock, &toBlock). + Return(blocks, order, nil). + Once() + + m.Etherman. + On("GetFinalizedBlockNumber", ctx). + Return(ethBlock2bis.NumberU64(), nil). + Once() + + m.State. + On("BeginStateTransaction", ctx). + Return(m.DbTx, nil). + Once() + + stateBlock2bis := &state.Block{ + BlockNumber: ethermanBlock2bis.BlockNumber, + BlockHash: ethermanBlock2bis.BlockHash, + ParentHash: ethermanBlock2bis.ParentHash, + ReceivedAt: ethermanBlock2bis.ReceivedAt, + Checked: true, + } + m.State. + On("AddBlock", ctx, stateBlock2bis, m.DbTx). + Return(nil). + Once() + + m.State. + On("GetStoredFlushID", ctx). + Return(uint64(1), cProverIDExecution, nil). + Once() + + m.DbTx. + On("Commit", ctx). + Run(func(args mock.Arguments) { + sync.Stop() + ctx.Done() + }). + Return(nil). + Once() + }). + Return(m.DbTx, nil). + Once() + + err = sync.Sync() + require.NoError(t, err) +} diff --git a/test/Makefile b/test/Makefile index 339301c3d4..8914336d9d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -686,7 +686,7 @@ generate-mocks-sequencer: ## Generates mocks for sequencer , using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=stateInterface --dir=../sequencer --output=../sequencer --outpkg=sequencer --inpackage --structname=StateMock --filename=mock_state.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=txPool --dir=../sequencer --output=../sequencer --outpkg=sequencer --inpackage --structname=PoolMock --filename=mock_pool.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Tx --srcpkg=github.com/jackc/pgx/v4 --output=../sequencer --outpkg=sequencer --structname=DbTxMock --filename=mock_dbtx.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=etherman --dir=../sequencer --output=../sequencer --outpkg=sequencer --inpackage --structname=EthermanMock --filename=mock_etherman.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ethermanInterface --dir=../sequencer --output=../sequencer --outpkg=sequencer --inpackage --structname=EthermanMock --filename=mock_etherman.go .PHONY: generate-mocks-sequencesender generate-mocks-sequencesender: ## Generates mocks for sequencesender , using mockery tool @@ -719,18 +719,21 @@ generate-mocks-synchronizer: ## Generates mocks for synchronizer , using mockery rm -Rf ../synchronizer/l2_sync/l2_shared/mocks export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../synchronizer/l2_sync/l2_shared --output ../synchronizer/l2_sync/l2_shared/mocks --outpkg mock_l2_shared ${COMMON_MOCKERY_PARAMS} - + rm -Rf ../synchronizer/common/syncinterfaces/mocks export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../synchronizer/common/syncinterfaces --output ../synchronizer/common/syncinterfaces/mocks --outpkg mock_syncinterfaces ${COMMON_MOCKERY_PARAMS} - + rm -Rf ../synchronizer/actions/elderberry/mocks export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../synchronizer/actions/elderberry --output ../synchronizer/actions/elderberry/mocks --outpkg mock_elderberry ${COMMON_MOCKERY_PARAMS} - - + + rm -Rf ../synchronizer/l1_check_block/mocks + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../synchronizer/l1_check_block --output ../synchronizer/l1_check_block/mocks --outpkg mock_l1_check_block ${COMMON_MOCKERY_PARAMS} + + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Tx --srcpkg=github.com/jackc/pgx/v4 --output=../synchronizer/mocks --structname=DbTxMock --filename=mock_dbtx.go ${COMMON_MOCKERY_PARAMS} - -.PHONY: generate-mocks-etherman + +.PHONY: generate-mocks-etherman generate-mocks-etherman: ## Generates mocks for etherman , using mockery tool ## mocks for etherman export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=GasPricer --srcpkg=github.com/ethereum/go-ethereum --output=../etherman --outpkg=etherman --structname=etherscanMock --filename=mock_etherscan.go diff --git a/test/config/test.genesis.config.json b/test/config/test.genesis.config.json index 1f24ff2a62..79ea8ab645 100644 --- a/test/config/test.genesis.config.json +++ b/test/config/test.genesis.config.json @@ -14,8 +14,8 @@ "contractName": "PolygonZkEVMDeployer", "balance": "0", "nonce": "4", - "address": "0x51dbd54FCCb6b3A07738fd3E156D588e71f79973", - "bytecode": "0x6080604052600436106100705760003560e01c8063715018a61161004e578063715018a6146100e65780638da5cb5b146100fb578063e11ae6cb14610126578063f2fde38b1461013957600080fd5b80632b79805a146100755780634a94d4871461008a5780636d07dbf81461009d575b600080fd5b610088610083366004610927565b610159565b005b6100886100983660046109c7565b6101cb565b3480156100a957600080fd5b506100bd6100b8366004610a1e565b61020d565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100f257600080fd5b50610088610220565b34801561010757600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166100bd565b610088610134366004610a40565b610234565b34801561014557600080fd5b50610088610154366004610a90565b61029b565b610161610357565b600061016e8585856103d8565b905061017a8183610537565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a15050505050565b6101d3610357565b6101de83838361057b565b506040517f25adb19089b6a549831a273acdf7908cff8b7ee5f551f8d1d37996cf01c5df5b90600090a1505050565b600061021983836105a9565b9392505050565b610228610357565b61023260006105b6565b565b61023c610357565b60006102498484846103d8565b60405173ffffffffffffffffffffffffffffffffffffffff821681529091507fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a150505050565b6102a3610357565b73ffffffffffffffffffffffffffffffffffffffff811661034b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610354816105b6565b50565b60005473ffffffffffffffffffffffffffffffffffffffff163314610232576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610342565b600083471015610444576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e63650000006044820152606401610342565b81516000036104af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f6044820152606401610342565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116610219576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f79000000000000006044820152606401610342565b6060610219838360006040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c6564000081525061062b565b60606105a1848484604051806060016040528060298152602001610b3d6029913961062b565b949350505050565b6000610219838330610744565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060824710156106bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610342565b6000808673ffffffffffffffffffffffffffffffffffffffff1685876040516106e69190610acf565b60006040518083038185875af1925050503d8060008114610723576040519150601f19603f3d011682016040523d82523d6000602084013e610728565b606091505b50915091506107398783838761076e565b979650505050505050565b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b606083156108045782516000036107fd5773ffffffffffffffffffffffffffffffffffffffff85163b6107fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610342565b50816105a1565b6105a183838151156108195781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103429190610aeb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261088d57600080fd5b813567ffffffffffffffff808211156108a8576108a861084d565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156108ee576108ee61084d565b8160405283815286602085880101111561090757600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806000806080858703121561093d57600080fd5b8435935060208501359250604085013567ffffffffffffffff8082111561096357600080fd5b61096f8883890161087c565b9350606087013591508082111561098557600080fd5b506109928782880161087c565b91505092959194509250565b803573ffffffffffffffffffffffffffffffffffffffff811681146109c257600080fd5b919050565b6000806000606084860312156109dc57600080fd5b6109e58461099e565b9250602084013567ffffffffffffffff811115610a0157600080fd5b610a0d8682870161087c565b925050604084013590509250925092565b60008060408385031215610a3157600080fd5b50508035926020909101359150565b600080600060608486031215610a5557600080fd5b8335925060208401359150604084013567ffffffffffffffff811115610a7a57600080fd5b610a868682870161087c565b9150509250925092565b600060208284031215610aa257600080fd5b6102198261099e565b60005b83811015610ac6578181015183820152602001610aae565b50506000910152565b60008251610ae1818460208701610aab565b9190910192915050565b6020815260008251806020840152610b0a816040850160208701610aab565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c6564a2646970667358221220964619cee0e0baf94c6f8763f013be157da5d54c89e5cff4a8caf4266e13f13a64736f6c63430008140033", + "address": "0xFbD07134824dDEa24E4ae414c18ecbFa98169A24", + "bytecode": "0x60806040526004361061006e575f3560e01c8063715018a61161004c578063715018a6146100e25780638da5cb5b146100f6578063e11ae6cb1461011f578063f2fde38b14610132575f80fd5b80632b79805a146100725780634a94d487146100875780636d07dbf81461009a575b5f80fd5b610085610080366004610908565b610151565b005b6100856100953660046109a2565b6101c2565b3480156100a5575f80fd5b506100b96100b43660046109f5565b610203565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100ed575f80fd5b50610085610215565b348015610101575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff166100b9565b61008561012d366004610a15565b610228565b34801561013d575f80fd5b5061008561014c366004610a61565b61028e565b61015961034a565b5f6101658585856103ca565b90506101718183610527565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a15050505050565b6101ca61034a565b6101d583838361056a565b506040517f25adb19089b6a549831a273acdf7908cff8b7ee5f551f8d1d37996cf01c5df5b905f90a1505050565b5f61020e8383610598565b9392505050565b61021d61034a565b6102265f6105a4565b565b61023061034a565b5f61023c8484846103ca565b60405173ffffffffffffffffffffffffffffffffffffffff821681529091507fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a150505050565b61029661034a565b73ffffffffffffffffffffffffffffffffffffffff811661033e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610347816105a4565b50565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610226576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610335565b5f83471015610435576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e63650000006044820152606401610335565b81515f0361049f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f6044820152606401610335565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff811661020e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f79000000000000006044820152606401610335565b606061020e83835f6040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c65640000815250610618565b6060610590848484604051806060016040528060298152602001610b0860299139610618565b949350505050565b5f61020e83833061072d565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060824710156106aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610335565b5f808673ffffffffffffffffffffffffffffffffffffffff1685876040516106d29190610a9c565b5f6040518083038185875af1925050503d805f811461070c576040519150601f19603f3d011682016040523d82523d5f602084013e610711565b606091505b509150915061072287838387610756565b979650505050505050565b5f604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b606083156107eb5782515f036107e45773ffffffffffffffffffffffffffffffffffffffff85163b6107e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610335565b5081610590565b61059083838151156108005781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103359190610ab7565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f82601f830112610870575f80fd5b813567ffffffffffffffff8082111561088b5761088b610834565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156108d1576108d1610834565b816040528381528660208588010111156108e9575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f805f806080858703121561091b575f80fd5b8435935060208501359250604085013567ffffffffffffffff80821115610940575f80fd5b61094c88838901610861565b93506060870135915080821115610961575f80fd5b5061096e87828801610861565b91505092959194509250565b803573ffffffffffffffffffffffffffffffffffffffff8116811461099d575f80fd5b919050565b5f805f606084860312156109b4575f80fd5b6109bd8461097a565b9250602084013567ffffffffffffffff8111156109d8575f80fd5b6109e486828701610861565b925050604084013590509250925092565b5f8060408385031215610a06575f80fd5b50508035926020909101359150565b5f805f60608486031215610a27575f80fd5b8335925060208401359150604084013567ffffffffffffffff811115610a4b575f80fd5b610a5786828701610861565b9150509250925092565b5f60208284031215610a71575f80fd5b61020e8261097a565b5f5b83811015610a94578181015183820152602001610a7c565b50505f910152565b5f8251610aad818460208701610a7a565b9190910192915050565b602081525f8251806020840152610ad5816040850160208701610a7a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c6564a2646970667358221220330b94dc698c4d290bf55c23f13b473cde6a6bae0030cb902de18af54e35839f64736f6c63430008140033", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" } @@ -24,8 +24,8 @@ "contractName": "ProxyAdmin", "balance": "0", "nonce": "1", - "address": "0xe34Fe58DDa5b8c6D547E4857E987633aa86a5e90", - "bytecode": "0x60806040526004361061007b5760003560e01c80639623609d1161004e5780639623609d1461012b57806399a88ec41461013e578063f2fde38b1461015e578063f3b7dead1461017e57600080fd5b8063204e1c7a14610080578063715018a6146100c95780637eff275e146100e05780638da5cb5b14610100575b600080fd5b34801561008c57600080fd5b506100a061009b366004610608565b61019e565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100d557600080fd5b506100de610255565b005b3480156100ec57600080fd5b506100de6100fb36600461062c565b610269565b34801561010c57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166100a0565b6100de610139366004610694565b6102f7565b34801561014a57600080fd5b506100de61015936600461062c565b61038c565b34801561016a57600080fd5b506100de610179366004610608565b6103e8565b34801561018a57600080fd5b506100a0610199366004610608565b6104a4565b60008060008373ffffffffffffffffffffffffffffffffffffffff166040516101ea907f5c60da1b00000000000000000000000000000000000000000000000000000000815260040190565b600060405180830381855afa9150503d8060008114610225576040519150601f19603f3d011682016040523d82523d6000602084013e61022a565b606091505b50915091508161023957600080fd5b8080602001905181019061024d9190610788565b949350505050565b61025d6104f0565b6102676000610571565b565b6102716104f0565b6040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690638f283970906024015b600060405180830381600087803b1580156102db57600080fd5b505af11580156102ef573d6000803e3d6000fd5b505050505050565b6102ff6104f0565b6040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841690634f1ef28690349061035590869086906004016107a5565b6000604051808303818588803b15801561036e57600080fd5b505af1158015610382573d6000803e3d6000fd5b5050505050505050565b6103946104f0565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690633659cfe6906024016102c1565b6103f06104f0565b73ffffffffffffffffffffffffffffffffffffffff8116610498576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b6104a181610571565b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff166040516101ea907ff851a44000000000000000000000000000000000000000000000000000000000815260040190565b60005473ffffffffffffffffffffffffffffffffffffffff163314610267576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161048f565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff811681146104a157600080fd5b60006020828403121561061a57600080fd5b8135610625816105e6565b9392505050565b6000806040838503121561063f57600080fd5b823561064a816105e6565b9150602083013561065a816105e6565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156106a957600080fd5b83356106b4816105e6565b925060208401356106c4816105e6565b9150604084013567ffffffffffffffff808211156106e157600080fd5b818601915086601f8301126106f557600080fd5b81358181111561070757610707610665565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561074d5761074d610665565b8160405282815289602084870101111561076657600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b60006020828403121561079a57600080fd5b8151610625816105e6565b73ffffffffffffffffffffffffffffffffffffffff8316815260006020604081840152835180604085015260005b818110156107ef578581018301518582016060015282016107d3565b5060006060828601015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010192505050939250505056fea2646970667358221220c9867ffac53151bdb1305d8f5e3e883cd83e5270c7ec09cdc24e837b2e65239064736f6c63430008140033", + "address": "0xfADB60b5059e31614e02083fF6C021a24C31c891", + "bytecode": "0x608060405260043610610079575f3560e01c80639623609d1161004c5780639623609d1461012357806399a88ec414610136578063f2fde38b14610155578063f3b7dead14610174575f80fd5b8063204e1c7a1461007d578063715018a6146100c55780637eff275e146100db5780638da5cb5b146100fa575b5f80fd5b348015610088575f80fd5b5061009c6100973660046105e8565b610193565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100d0575f80fd5b506100d9610244565b005b3480156100e6575f80fd5b506100d96100f536600461060a565b610257565b348015610105575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff1661009c565b6100d961013136600461066e565b6102e0565b348015610141575f80fd5b506100d961015036600461060a565b610371565b348015610160575f80fd5b506100d961016f3660046105e8565b6103cd565b34801561017f575f80fd5b5061009c61018e3660046105e8565b610489565b5f805f8373ffffffffffffffffffffffffffffffffffffffff166040516101dd907f5c60da1b00000000000000000000000000000000000000000000000000000000815260040190565b5f60405180830381855afa9150503d805f8114610215576040519150601f19603f3d011682016040523d82523d5f602084013e61021a565b606091505b509150915081610228575f80fd5b8080602001905181019061023c919061075b565b949350505050565b61024c6104d3565b6102555f610553565b565b61025f6104d3565b6040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690638f283970906024015b5f604051808303815f87803b1580156102c6575f80fd5b505af11580156102d8573d5f803e3d5ffd5b505050505050565b6102e86104d3565b6040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841690634f1ef28690349061033e9086908690600401610776565b5f604051808303818588803b158015610355575f80fd5b505af1158015610367573d5f803e3d5ffd5b5050505050505050565b6103796104d3565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690633659cfe6906024016102af565b6103d56104d3565b73ffffffffffffffffffffffffffffffffffffffff811661047d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b61048681610553565b50565b5f805f8373ffffffffffffffffffffffffffffffffffffffff166040516101dd907ff851a44000000000000000000000000000000000000000000000000000000000815260040190565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610255576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610474565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff81168114610486575f80fd5b5f602082840312156105f8575f80fd5b8135610603816105c7565b9392505050565b5f806040838503121561061b575f80fd5b8235610626816105c7565b91506020830135610636816105c7565b809150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f805f60608486031215610680575f80fd5b833561068b816105c7565b9250602084013561069b816105c7565b9150604084013567ffffffffffffffff808211156106b7575f80fd5b818601915086601f8301126106ca575f80fd5b8135818111156106dc576106dc610641565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561072257610722610641565b8160405282815289602084870101111561073a575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f6020828403121561076b575f80fd5b8151610603816105c7565b73ffffffffffffffffffffffffffffffffffffffff831681525f602060408184015283518060408501525f5b818110156107be578581018301518582016060015282016107a2565b505f6060828601015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010192505050939250505056fea26469706673582212203083a4ccc2e42eed60bd19037f2efa77ed086dc7a5403f75bebb995dcba2221c64736f6c63430008140033", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f" } @@ -62,7 +62,7 @@ "address": "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa", "bytecode": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122012bb4f564f73959a03513dc74fc3c6e40e8386e6f02c16b78d6db00ce0aa16af64736f6c63430008090033", "storage": { - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000e34fe58dda5b8c6d547e4857e987633aa86a5e90", + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000fadb60b5059e31614e02083ff6c021a24c31c891", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000dc64a140aa3e981100a9beca4e685f962f0cf6c9" } }, @@ -89,7 +89,7 @@ "accountName": "keyless Deployer", "balance": "0", "nonce": "1", - "address": "0x28BB4e66addE1f042B77E04cf7D3784C1dcDBbA3" + "address": "0x694AB5383a002a4796f95530c14Cf0C25ec3EA03" }, { "accountName": "deployer", diff --git a/test/e2e/jsonrpc2_test.go b/test/e2e/jsonrpc2_test.go index b2a3a2598f..f8a0113814 100644 --- a/test/e2e/jsonrpc2_test.go +++ b/test/e2e/jsonrpc2_test.go @@ -456,22 +456,27 @@ func TestCallMissingParameters(t *testing.T) { expectedError: types.ErrorObject{Code: types.InvalidParamsErrorCode, Message: "missing value for required argument 0"}, }, { - name: "params has only first parameter", - params: []interface{}{map[string]interface{}{"value": "0x1"}}, - expectedError: types.ErrorObject{Code: types.InvalidParamsErrorCode, Message: "missing value for required argument 1"}, + name: "params has only first parameter", + params: []interface{}{map[string]interface{}{"value": "0x1", "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92267"}}, }, } for _, network := range networks { - log.Infof("Network %s", network.Name) - for _, testCase := range testCases { + t.Logf("Network %s", network.Name) + for tc, testCase := range testCases { + t.Logf("testCase %d", tc) t.Run(network.Name+testCase.name, func(t *testing.T) { response, err := client.JSONRPCCall(network.URL, "eth_call", testCase.params...) require.NoError(t, err) - require.NotNil(t, response.Error) - require.Nil(t, response.Result) - require.Equal(t, testCase.expectedError.Code, response.Error.Code) - require.Equal(t, testCase.expectedError.Message, response.Error.Message) + if (testCase.expectedError != types.ErrorObject{}) { + require.NotNil(t, response.Error) + require.Nil(t, response.Result) + require.Equal(t, testCase.expectedError.Code, response.Error.Code) + require.Equal(t, testCase.expectedError.Message, response.Error.Message) + } else { + require.Nil(t, response.Error) + require.NotNil(t, response.Result) + } }) } } @@ -620,24 +625,39 @@ func TestEstimateGas(t *testing.T) { txToMsg, err := sc.Increment(auth) require.NoError(t, err) - // add funds to address 0x000...001 used in the test - nonce, err := ethereumClient.NonceAt(ctx, auth.From, nil) - require.NoError(t, err) - value := big.NewInt(1000) - require.NoError(t, err) - tx = ethTypes.NewTx(ðTypes.LegacyTx{ - Nonce: nonce, - To: state.Ptr(common.HexToAddress("0x1")), - Value: value, - Gas: 24000, - GasPrice: gasPrice, - }) - signedTx, err := auth.Signer(auth.From, tx) - require.NoError(t, err) - err = ethereumClient.SendTransaction(ctx, signedTx) - require.NoError(t, err) - err = operations.WaitTxToBeMined(ctx, ethereumClient, signedTx, operations.DefaultTimeoutTxToBeMined) - require.NoError(t, err) + // addresses the test needs to have balance + addressesToAddBalance := map[common.Address]*big.Int{ + // add funds to address 0x111...111 which is the default address + // when estimating TXs without specifying the sender + common.HexToAddress("0x1111111111111111111111111111111111111111"): big.NewInt(3000000000000000), + + // add funds to address 0x000...001 + common.HexToAddress("0x1"): big.NewInt(1000), + } + + for addr, value := range addressesToAddBalance { + nonce, err := ethereumClient.NonceAt(ctx, auth.From, nil) + require.NoError(t, err) + value := value + require.NoError(t, err) + tx = ethTypes.NewTx(ðTypes.LegacyTx{ + Nonce: nonce, + To: state.Ptr(addr), + Value: value, + Gas: 24000, + GasPrice: gasPrice, + }) + signedTx, err := auth.Signer(auth.From, tx) + require.NoError(t, err) + err = ethereumClient.SendTransaction(ctx, signedTx) + require.NoError(t, err) + err = operations.WaitTxToBeMined(ctx, ethereumClient, signedTx, operations.DefaultTimeoutTxToBeMined) + require.NoError(t, err) + + balance, err := ethereumClient.BalanceAt(ctx, addr, nil) + require.NoError(t, err) + log.Debugf("%v balance: %v", addr.String(), balance.String()) + } type testCase struct { name string @@ -670,19 +690,15 @@ func TestEstimateGas(t *testing.T) { name: "with gasPrice set and without from address", address: nil, setGasPrice: true, - expectedError: types.NewRPCError(-32000, "gas required exceeds allowance"), + expectedError: nil, + }, + { + name: "with gasPrice and value set and address with enough balance", + address: state.Ptr(auth.From), + value: state.Ptr(int64(1)), + setGasPrice: true, + expectedError: types.NewRPCError(-32000, "execution reverted"), }, - // TODO: This test is failing due to geth bug - // we can uncomment it when updating geth version - // on l1 image, it's returning error code -32000 when - // it should be returning error code 3 due to execution message - // { - // name: "with gasPrice and value set and address with enough balance", - // address: state.Ptr(auth.From), - // value: state.Ptr(int64(1)), - // setGasPrice: true, - // expectedError: types.NewRPCError(3, "execution reverted"), - // }, { name: "with gasPrice and value set and address without enough balance", address: state.Ptr(common.HexToAddress("0x1")), @@ -697,13 +713,21 @@ func TestEstimateGas(t *testing.T) { setGasPrice: true, expectedError: types.NewRPCError(-32000, "insufficient funds for transfer"), }, - { - name: "with gasPrice and value set and without from address", - address: nil, - value: state.Ptr(int64(-1)), - setGasPrice: true, - expectedError: types.NewRPCError(-32000, "insufficient funds for transfer"), - }, + // TODO = Review the test below in future versions of geth. + // + // Geth is returning -32000, "insufficient funds for transfer" + // zkEVM is returning 3, "execution reverted" + // + // Since the tx has value, the method increment is not payable + // and the default account has balance, the tx should revert + // + // { + // name: "with gasPrice and value set and without from address", + // address: nil, + // value: state.Ptr(int64(-1)), + // setGasPrice: true, + // expectedError: types.NewRPCError(-32000, "insufficient funds for transfer"), + // }, { name: "without gasPrice set and address with enough balance", address: state.Ptr(auth.From), @@ -746,7 +770,6 @@ func TestEstimateGas(t *testing.T) { if testCase.value != nil { v := *testCase.value if v == -1 { //set the value as acc balance + 1 to force overflow - msg.Value = common.Big0.Add(balance, common.Big1) } else { msg.Value = big.NewInt(0).SetInt64(v) @@ -757,7 +780,10 @@ func TestEstimateGas(t *testing.T) { msg.GasPrice = gasPrice } - _, err = ethereumClient.EstimateGas(ctx, msg) + gas, err := ethereumClient.EstimateGas(ctx, msg) + t.Log("testCase: ", testCase.name) + t.Log("err: ", err) + t.Log("gas: ", gas) if testCase.expectedError != nil { rpcErr := err.(rpc.Error) errMsg := fmt.Sprintf("[%v] expected: %v %v found: %v %v", network.Name, testCase.expectedError.ErrorCode(), testCase.expectedError.Error(), rpcErr.ErrorCode(), rpcErr.Error()) diff --git a/tools/datastreamer/main.go b/tools/datastreamer/main.go index 41f3611f1f..975e4c7ecd 100644 --- a/tools/datastreamer/main.go +++ b/tools/datastreamer/main.go @@ -558,14 +558,13 @@ func decodeEntry(cliCtx *cli.Context) error { os.Exit(1) } - client.FromEntry = cliCtx.Uint64("entry") - err = client.ExecCommand(datastreamer.CmdEntry) + entry, err := client.ExecCommandGetEntry(cliCtx.Uint64("entry")) if err != nil { log.Error(err) os.Exit(1) } - printEntry(client.Entry) + printEntry(entry) return nil } @@ -597,35 +596,28 @@ func decodeL2Block(cliCtx *cli.Context) error { Value: l2BlockNumber, } - client.FromBookmark = bookMark.Encode() - err = client.ExecCommand(datastreamer.CmdBookmark) + firstEntry, err := client.ExecCommandGetBookmark(bookMark.Encode()) if err != nil { log.Error(err) os.Exit(1) } - - firstEntry := client.Entry printEntry(firstEntry) - client.FromEntry = firstEntry.Number + 1 - err = client.ExecCommand(datastreamer.CmdEntry) + secondEntry, err := client.ExecCommandGetEntry(firstEntry.Number + 1) if err != nil { log.Error(err) os.Exit(1) } - - secondEntry := client.Entry printEntry(secondEntry) i := uint64(2) //nolint:gomnd for secondEntry.Type == state.EntryTypeL2Tx { - client.FromEntry = firstEntry.Number + i - err = client.ExecCommand(datastreamer.CmdEntry) + entry, err := client.ExecCommandGetEntry(firstEntry.Number + i) if err != nil { log.Error(err) os.Exit(1) } - secondEntry = client.Entry + secondEntry = entry printEntry(secondEntry) i++ }