Does the wallet database store a table of all the actual output scripts (i.e. witness programs) derived from its keypool?
Kind of.
For descriptor wallets, the set of all scriptPubKeys (up to the keypool limit for each descriptor) is generated and held in memory. When determining whether a transaction output belongs to the wallet, the wallet checks whether the output's scriptPubKey is in that set. If it is, then the output belongs to the wallet.
One thing to note though is that this scriptPubKey set is not stored in the database. Once the wallet is unloaded, the set is forgotten. It will be recomputed every time the wallet is loaded.
For legacy wallets, it's a lot more complicated. It will determine the type of script and then do lookups for scripts and keys in the wallet depending on the type. For the most part, it does boil down to a finite set of scripts, but this can actually be a very large set that contains a lot of scripts that you might not expect it to.
I'm especially curious about scanning tx inputs to recognize coins we have spent. Do we only check outpoints stored in the walletdb?
Yes. For inputs, the wallet will only check to see if the referenced output exists in the wallet and that output belongs to the wallet. This also means that rescanning must be done serially. If the wallet scanned a transaction that contains a spend of an output that belongs to it, but it hadn't scanned the transaction creating that output yet, then it would not detect the spending transaction.
One unexpected aspect of the Bitcoin Core wallet is that it doesn't explicitly mark which outputs actually belong to it. If it needs to know whether an output belongs to the wallet, it will check the scriptPubKey against the in-memory scriptPubKey set. Similarly, it doesn't explicitly permanently mark outputs as being spent. If you were to remove the spending transaction from your wallet using removeprunedfunds
, the output would suddenly appear to be unspent and your wallet could make a transaction spending it. Of course, this would also make an invalid transaction.