From a66305dae31d82d6edbca3976bfd5501a6b2e72a Mon Sep 17 00:00:00 2001 From: anshalshukla Date: Thu, 5 Sep 2024 14:03:10 +0530 Subject: [PATCH] fix: lint, add: milestone related tests in downloader --- accounts/abi/bind/backends/simulated_test.go | 3 +- cmd/devp2p/internal/ethtest/snap.go | 3 - consensus/bor/bor_test.go | 2 +- consensus/bor/heimdallgrpc/client.go | 2 +- core/blockchain.go | 4 +- core/parallel_state_processor.go | 2 + core/rawdb/chain_iterator.go | 2 - core/state_processor.go | 1 + core/state_transition.go | 1 - eth/catalyst/api.go | 1 - eth/downloader/bor_downloader.go | 14 ++- eth/downloader/bor_downloader_test.go | 92 ++++++++++++++++++++ eth/downloader/queue.go | 1 + eth/fetcher/tx_fetcher.go | 4 - eth/fetcher/tx_fetcher_test.go | 11 --- eth/filters/filter_system.go | 13 --- eth/filters/filter_system_test.go | 9 -- internal/cli/command.go | 2 +- internal/cli/server/chains/developer.go | 3 +- internal/ethapi/api.go | 3 +- miner/miner_test.go | 3 +- miner/pending.go | 5 ++ miner/worker.go | 2 - tests/fuzzers/txfetcher/txfetcher_fuzzer.go | 5 +- trie/secure_trie.go | 2 + trie/trie_test.go | 1 - 26 files changed, 122 insertions(+), 69 deletions(-) diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 860d4e59af..3e328f044d 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/leak" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" @@ -47,7 +46,7 @@ func TestSimulatedBackend(t *testing.T) { key, _ := crypto.GenerateKey() // nolint: gosec auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) genAlloc := make(types.GenesisAlloc) - genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)} + genAlloc[auth.From] = types.Account{Balance: big.NewInt(9223372036854775807)} sim := NewSimulatedBackend(genAlloc, gasLimit) defer sim.Close() diff --git a/cmd/devp2p/internal/ethtest/snap.go b/cmd/devp2p/internal/ethtest/snap.go index 1b8f8c62ab..6e45f2af2c 100644 --- a/cmd/devp2p/internal/ethtest/snap.go +++ b/cmd/devp2p/internal/ethtest/snap.go @@ -602,7 +602,6 @@ func hexToCompact(hex []byte) []byte { // TestSnapTrieNodes various forms of GetTrieNodes requests. func (s *Suite) TestSnapTrieNodes(t *utesting.T) { - var ( // This is the known address of the snap storage testing contract. storageAcct = common.HexToAddress("0x8bebc8ba651aee624937e7d897853ac30c95a067") @@ -867,13 +866,11 @@ func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error { res, ok := msg.(*snap.StorageRangesPacket) if !ok { - return fmt.Errorf("account range response wrong: %T %v", msg, msg) } // Ensure the ranges are monotonically increasing for i, slots := range res.Slots { - for j := 1; j < len(slots); j++ { if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 { return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:]) diff --git a/consensus/bor/bor_test.go b/consensus/bor/bor_test.go index fc08bfdc05..bde04702a8 100644 --- a/consensus/bor/bor_test.go +++ b/consensus/bor/bor_test.go @@ -53,7 +53,7 @@ func TestGenesisContractChange(t *testing.T) { } genspec := &core.Genesis{ - Alloc: map[common.Address]core.GenesisAccount{ + Alloc: map[common.Address]types.Account{ addr0: { Balance: big.NewInt(0), Code: []byte{0x1, 0x1}, diff --git a/consensus/bor/heimdallgrpc/client.go b/consensus/bor/heimdallgrpc/client.go index 7687b7f1c7..2f002b9b82 100644 --- a/consensus/bor/heimdallgrpc/client.go +++ b/consensus/bor/heimdallgrpc/client.go @@ -29,7 +29,7 @@ func NewHeimdallGRPCClient(address string) *HeimdallGRPCClient { grpc_retry.WithCodes(codes.Internal, codes.Unavailable, codes.Aborted, codes.NotFound), } - conn, err := grpc.Dial(address, + conn, err := grpc.NewClient(address, grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(opts...)), grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(opts...)), grpc.WithTransportCredentials(insecure.NewCredentials()), diff --git a/core/blockchain.go b/core/blockchain.go index cc036ade0e..b113f3f521 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1659,7 +1659,6 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // writeLive writes blockchain and corresponding receipt chain into active store. writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { - headers := make([]*types.Header, 0, len(blockChain)) var ( skipPresenceCheck = false @@ -2520,7 +2519,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // blockProcessingResult is a summary of block processing // used for updating the stats. -// nolint +// nolint : unused type blockProcessingResult struct { usedGas uint64 procTime time.Duration @@ -2529,6 +2528,7 @@ type blockProcessingResult struct { // processBlock executes and validates the given block. If there was no error // it writes the block and associated state to database. +// nolint : unused func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { if bc.logger != nil && bc.logger.OnBlockStart != nil { td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 98432966ce..e44b943273 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -364,6 +364,8 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat shouldDelayFeeCal = false statedb.StopPrefetcher() + + // nolint *statedb = *backupStateDB allLogs = []*types.Log{} diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index dede764200..6989e480b5 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -206,7 +206,6 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool // There is a passed channel, the whole procedure will be interrupted if any // signal received. func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool, report bool) { - // short circuit for invalid range if from >= to { return @@ -310,7 +309,6 @@ func indexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, inte // There is a passed channel, the whole procedure will be interrupted if any // signal received. func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool, report bool) { - // short circuit for invalid range if from >= to { return diff --git a/core/state_processor.go b/core/state_processor.go index 33061ae20f..b4fd3ec659 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -113,6 +113,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, fmt.Errorf("withdrawals before shanghai") } // Bor does not support withdrawals + // nolint if withdrawals != nil { withdrawals = nil } diff --git a/core/state_transition.go b/core/state_transition.go index 5a23fac59d..6ed13c880e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -481,7 +481,6 @@ func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*Executio ) if contractCreation { - // nolint : contextcheck ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 59cf1881a6..b35703a658 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -483,7 +483,6 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) } } else { - if params.Withdrawals != nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai")) } diff --git a/eth/downloader/bor_downloader.go b/eth/downloader/bor_downloader.go index 26ebdfff5e..b4a11bfad1 100644 --- a/eth/downloader/bor_downloader.go +++ b/eth/downloader/bor_downloader.go @@ -45,11 +45,10 @@ var ( MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request - maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) - maxHeadersProcess = 2048 // Number of header download results to import at once into the chain - maxResultsProcess = 2048 // Number of content download results to import at once into the chain - fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) - lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) + maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) + maxHeadersProcess = 2048 // Number of header download results to import at once into the chain + maxResultsProcess = 2048 // Number of content download results to import at once into the chain + fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) reorgProtThreshold = 48 // Threshold number of recent blocks to disable mini reorg protection reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs @@ -102,9 +101,8 @@ type Downloader struct { mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode mux *event.TypeMux // Event multiplexer to announce sync operation events - genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT) - queue *queue // Scheduler for selecting the hashes to download - peers *peerSet // Set of active peers from which download can proceed + queue *queue // Scheduler for selecting the hashes to download + peers *peerSet // Set of active peers from which download can proceed stateDB ethdb.Database // Database to state sync into (and deduplicate via) diff --git a/eth/downloader/bor_downloader_test.go b/eth/downloader/bor_downloader_test.go index 0a568bf35b..32bde123f6 100644 --- a/eth/downloader/bor_downloader_test.go +++ b/eth/downloader/bor_downloader_test.go @@ -41,6 +41,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/assert" ) // downloadTester is a test simulator for mocking out local block chain. @@ -1475,3 +1476,94 @@ func (w *whitelistFake) RemoveMilestoneID(milestoneId string) { func (w *whitelistFake) GetMilestoneIDsList() []string { return nil } + +// TestFakedSyncProgress67WhitelistMatch tests if in case of whitelisted +// checkpoint match with opposite peer, the sync should succeed. +func TestFakedSyncProgress68WhitelistMatch(t *testing.T) { + t.Parallel() + + protocol := uint(eth.ETH68) + mode := FullSync + + tester := newTester(t) + validate := func(count int) (bool, error) { + return true, nil + } + tester.downloader.ChainValidator = newWhitelistFake(validate) + + defer tester.terminate() + + chainA := testChainForkLightA.blocks + tester.newPeer("light", protocol, chainA[1:]) + + // Synchronise with the peer and make sure all blocks were retrieved + if err := tester.sync("light", nil, mode); err != nil { + t.Fatal("succeeded attacker synchronisation") + } +} + +// TestFakedSyncProgress67NoRemoteCheckpoint tests if in case of missing/invalid +// checkpointed blocks with opposite peer, the sync should fail initially but +// with the retry mechanism, it should succeed eventually. +func TestFakedSyncProgress68NoRemoteCheckpoint(t *testing.T) { + t.Parallel() + + protocol := uint(eth.ETH68) + mode := FullSync + + tester := newTester(t) + validate := func(count int) (bool, error) { + // only return the `ErrNoRemoteCheckpoint` error for the first call + if count == 0 { + return false, whitelist.ErrNoRemote + } + + return true, nil + } + + tester.downloader.ChainValidator = newWhitelistFake(validate) + + defer tester.terminate() + + chainA := testChainForkLightA.blocks + tester.newPeer("light", protocol, chainA[1:]) + + // Set the max validation threshold equal to chain length to enforce validation + tester.downloader.maxValidationThreshold = uint64(len(chainA) - 1) + + // Synchronise with the peer and make sure all blocks were retrieved + // Should fail in first attempt + err := tester.sync("light", nil, mode) + assert.Equal(t, whitelist.ErrNoRemote, err, "failed synchronisation") + + // Try syncing again, should succeed + if err := tester.sync("light", nil, mode); err != nil { + t.Fatal("succeeded attacker synchronisation") + } +} + +// TestFakedSyncProgress67BypassWhitelistValidation tests if peer validation +// via whitelist is bypassed when remote peer is far away or not +func TestFakedSyncProgress68BypassWhitelistValidation(t *testing.T) { + protocol := uint(eth.ETH68) + mode := FullSync + + tester := newTester(t) + validate := func(count int) (bool, error) { + return false, whitelist.ErrNoRemote + } + + tester.downloader.ChainValidator = newWhitelistFake(validate) + + defer tester.terminate() + + // 1223 length chain + chainA := testChainBase.blocks + tester.newPeer("light", protocol, chainA[1:]) + + // Although the validate function above returns an error (which says that + // remote peer doesn't have that block), sync will go through as the chain + // import length is 1223 which is more than the default threshold of 1024 + err := tester.sync("light", nil, mode) + assert.NoError(t, err, "failed synchronisation") +} diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 6d0226cc49..25e7ed84d8 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -90,6 +90,7 @@ func newFetchResult(header *types.Header, fastSync bool) *fetchResult { } // body returns a representation of the fetch result as a types.Body object. +// nolint : unused func (f *fetchResult) body() types.Body { return types.Body{ Transactions: f.Transactions, diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go index e74673342d..9fa432bacf 100644 --- a/eth/fetcher/tx_fetcher.go +++ b/eth/fetcher/tx_fetcher.go @@ -70,10 +70,6 @@ const ( // txGatherSlack is the interval used to collate almost-expired announces // with network fetches. txGatherSlack = 100 * time.Millisecond - - // maxTxArrivalWait is the longest acceptable duration for the txArrivalWait - // configuration value. Longer config values will default to this. - maxTxArrivalWait = 500 * time.Millisecond ) var ( diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go index 3751c6ed1f..11bb1b9558 100644 --- a/eth/fetcher/tx_fetcher_test.go +++ b/eth/fetcher/tx_fetcher_test.go @@ -2017,17 +2017,6 @@ func containsHashInAnnounces(slice []announce, hash common.Hash) bool { return false } -// containsHash returns whether a hash is contained within a hash slice. -func containsHash(slice []common.Hash, hash common.Hash) bool { - for _, have := range slice { - if have == hash { - return true - } - } - - return false -} - // Tests that a transaction is forgotten after the timeout. func TestTransactionForgotten(t *testing.T) { fetcher := NewTxFetcher( diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 371e0bb43a..ed6153989e 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -427,19 +427,6 @@ func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) { } } -func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) { - if len(ev) == 0 { - return - } - - for _, f := range filters[PendingLogsSubscription] { - matchedLogs := filterLogs(ev, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) - if len(matchedLogs) > 0 { - f.logs <- matchedLogs - } - } -} - func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { for _, f := range filters[PendingTransactionsSubscription] { f.txs <- ev.Txs diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 50288502d6..02592e6afa 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -708,12 +708,3 @@ func TestPendingTxFilterDeadlock(t *testing.T) { } } } - -func flattenLogs(pl [][]*types.Log) []*types.Log { - var logs []*types.Log - for _, l := range pl { - logs = append(logs, l...) - } - - return logs -} diff --git a/internal/cli/command.go b/internal/cli/command.go index 66ad650d9f..8c3bf02f0f 100644 --- a/internal/cli/command.go +++ b/internal/cli/command.go @@ -233,7 +233,7 @@ func (m *Meta2) NewFlagSet(n string) *flagset.Flagset { } func (m *Meta2) Conn() (*grpc.ClientConn, error) { - conn, err := grpc.Dial(m.addr, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.NewClient(m.addr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to connect to server: %v", err) } diff --git a/internal/cli/server/chains/developer.go b/internal/cli/server/chains/developer.go index 40d65005f9..fdc20a84f3 100644 --- a/internal/cli/server/chains/developer.go +++ b/internal/cli/server/chains/developer.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" ) @@ -29,7 +30,7 @@ func GetDeveloperChain(period uint64, gasLimitt uint64, faucet common.Address) * GasLimit: gasLimitt, BaseFee: big.NewInt(params.InitialBaseFee), Difficulty: big.NewInt(1), - Alloc: map[common.Address]core.GenesisAccount{ + Alloc: map[common.Address]types.Account{ common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0ac6ddd5fb..ea724ab625 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1882,7 +1882,7 @@ type TransactionAPI struct { } // returns block transactions along with state-sync transaction if present -// nolint: unparam +// nolint : unused func (api *TransactionAPI) getAllBlockTransactions(ctx context.Context, block *types.Block) (types.Transactions, bool) { txs := block.Transactions() @@ -2058,7 +2058,6 @@ func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash com // GetTransactionReceipt returns the transaction receipt for the given transaction hash. func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { - borTx := false found, tx, blockHash, blockNumber, index, err := api.b.GetTransaction(ctx, hash) diff --git a/miner/miner_test.go b/miner/miner_test.go index 26cf7169cc..1a3e8edc22 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -58,6 +58,7 @@ func (m *mockBackend) TxPool() *txpool.TxPool { return m.txPool } +// nolint : unused type testBlockChain struct { root common.Hash config *params.ChainConfig @@ -367,7 +368,7 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) { // GasLimit: gasLimit, // BaseFee: big.NewInt(params.InitialBaseFee), // Difficulty: big.NewInt(1), -// Alloc: map[common.Address]core.GenesisAccount{ +// Alloc: map[common.Address]types.Account{ // common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover // common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 // common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD diff --git a/miner/pending.go b/miner/pending.go index bb91fe8969..96f2065e40 100644 --- a/miner/pending.go +++ b/miner/pending.go @@ -14,8 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +//go:build ignore +// +build ignore + package miner +// nolint : unused + import ( "sync" "time" diff --git a/miner/worker.go b/miner/worker.go index 32c847aa52..267f9ec309 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -185,8 +185,6 @@ type newPayloadResult struct { block *types.Block fees *big.Int // total block fees sidecars []*types.BlobTxSidecar // collected blobs of blob transactions - stateDB *state.StateDB // StateDB after executing the transactions - receipts []*types.Receipt // Receipts collected during construction } // getWorkReq represents a request for getting a new sealing work with provided parameters. diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go index d8347bd297..431c2f64e0 100644 --- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go +++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go @@ -30,9 +30,8 @@ import ( ) var ( - peers []string - txs []*types.Transaction - testTxArrivalWait = 500 * time.Millisecond + peers []string + txs []*types.Transaction ) func init() { diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 6eb6defa45..23a771fef2 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -81,6 +81,8 @@ func NewStateTrie(id *ID, db database.Database) (*StateTrie, error) { if err != nil { return nil, err } + + // nolint tr := &StateTrie{trie: *trie, db: db} // link the preimage store if it's supported diff --git a/trie/trie_test.go b/trie/trie_test.go index e8303f6f5c..bdbe243797 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -903,7 +903,6 @@ func (s *spongeDb) Put(key []byte, value []byte) error { } else { s.keys = append(s.keys, string(key)) s.values[string(key)] = string(value) - } return nil }