This is a fairly high level of abstraction because I don't know exactly what the application is going to do or how you will structure the deployment. Three-part answer.
First, I would be leery about a loopy process. Try to avoid whenever possible and keep it well-bounded when it's unavoidable. The rationale includes the fact that estimating gas may be difficult, and at scale, it's possible the process won't be able to run at all due to the block gas limit.
It's often possible to get around that sort of thing by exposing a single step ... // or do something else ... as a function. You rely on something outside the contract, i.e. a node server or a UI client, etc., to iterate over a loop and call that function as often as necessary.
Second part. Iterating over a mapping itself isn't possible but something like that is often needed. A solution is to maintain an index and iterate over that.
address[] clusterContractIndex;
Mulling over how to present an example, I started developing a feeling that mapping by the block number isn't the way to go, so I've changed to mapping by address. There won't be any collision if multiples are created in the same block with this change. You may want to add the block number to the struct to record it somewhere as you work out the details. Note that this won't be necessary to get contracts created in a certain block.
As a general habit, consider the usefulness of some functions to make the mapping iterable:
function getContractCount() returns(uint count) {
return clusterContractIndex.length;
}
function getContractAtIndex(uint row) returns(address contract) {
return clusterContractIndex[row];
}
Inside the function that records them, write the structs to the mapping, and also push()
the keys to the index:
function newClusterContract(address newContract) {
clusterStructs[newContract] = contractStruct;
clusterContractIndex.push(newContract);
This is a cumulative add-only process. Now you can get the contracts in the order they were created, or by their address directly with:
function getContract(address contract) returns(contract details) {}
Third. Efficient search and filtering. I don't have a good on-chain search process, yet :-)
In practice, you may be able to eject that requirement and depend on offchain services such as a database or even an in-memory sort by a browser UI (depending on expected scale). The takeaway here is most things that look like the need for an onchain index/search/filter solution are really better handled by offchain processes.
Add an event emitter to the contract whenever state changes.
event LogNewContract(address contract);
And when it happens:
LogNewContract(address newContract);
When you get on to building the UI, you'll probably find that a watcher over this can keep a synced copy of the on-chain truths, quickly sort and filter things, and then fetch the details of a contract that's known to exist. From the sound of it, it only needs to finds any contracts that actually exist in a given range of block numbers. Clients get the blocknumber a contract was created in when the event arrives. It's a bonus piece of every event received.
Hope it helps.
p.s. You can think of the mapping as a table in which all possible addresses exist and all remaining columns are reliably initialized to 0. If you pull a struct from an "empty" address you never wrote to, then all fields in the struct will be all 0. 0, false, 0x0, empty string, depending on type.
UPDATE
I've created an Order Statistics Tree that resolves a number of problems such as finding the median or rank of a value in a sorted list while also providing a method to cap gas cost to an absolute maximum/worst-case limit at any scale.
This repo builds on Bokky Poobah's Red Black Tree which provides the basis for the self-balancing tree. https://github.com/rob-Hitchens/OrderStatisticsTree
var infuraApiKey =process.env.INFURA_API_KEY;
// var privateKey = process.env.PRIVATE_KEY;
var web3js = new web3(new web3.providers.HttpProvider("https://kovan.infura.io/v3/"+infuraApiKey));
web3js.eth.defaultAccount = myAddress;
var privateKey=new Buffer(process.env.PRIVATE_KEY, 'hex');
// var toAddress = 'ADRESS_TO_SEND_TRANSACTION';
//contract abi is the array that you can get from the ethereum wallet or etherscan
var contractABI =bonusABI;
var contractAddress =bonusAddress;
//creating contract object
var contract = web3js.eth.contract(contractABI).at(contractAddress);
var count;
var nounce;
var errcode="";
web3js.eth.getTransactionCount(myAddress, function(err, result) {
nounce=result;
var nounceHex = web3js.toHex(nounce);
var rawTransaction = {"from":myAddress,
"gasPrice":web3js.toHex(2*1e9),
"gasLimit":web3js.toHex(920000),
"to":contractAddress,
"data":contract.addBonus.getData(bonusType, target, year, month, day, token, bonus, bonusName, ineq),
"nonce":nounceHex}
var transaction = new Tx(rawTransaction);
transaction.sign(privateKey);
var serializedTx = transaction.serialize();
web3js.eth.sendRawTransaction('0x'+serializedTx.toString('hex'), function(err1, hash) {
if (!err1) {
errcode=hash;
}
else
errcode=err1;
});
})
Best Answer
I've noticed in your example you use a
string
for key. Solidity uses thekeccack256(key)
as lookup value. So there's the case you have two different string mapping to the same value. (This is rare because it will mean you have found a collition of sha3).If you have a mapping to a struct I'd use a boolean field to indicate an entry is used. An empty entry will such fields initially set to false.
If you have a mapping to a value like address, you can check directly against an special value like
address(0)
.